## Copyright (C) 2011-2022, José Luis García Pallero, <jgpallero@gmail.com>
##
## This file is part of OctCLIP.
##
## OctCLIP is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or (at
## your option) any later version.
##
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function file}{[@var{p},@var{details}] =}oc_polybool(@var{sub},@var{clip})
## @deftypefnx {Function file}{[@var{p},@var{details}] =}oc_polybool(@var{sub},@var{clip},@var{op})
## @deftypefnx {Function file}{[@var{p},@var{details}] =}oc_polybool(@var{sub},@var{clip},@var{op},@var{hor})
##
## @cindex Performs boolean operations between two polygons.
##
## This function performs boolean operations between two polygons using the
## Greiner-Hormann algorithm as it is presented in
## http://davis.wpi.edu/~matt/courses/clipping/
##
## @var{sub} is a two column matrix containing the X and Y coordinates of the
## vertices for the subject polygon (it must be unique, although
## self-intersections are permitted).
##
## @var{clip} is a two column matrix containing the X and Y coordinates of the
## vertices for the clipper polygon(it must be unique, although
## self-intersections are permitted).
##
## @var{op} is a text string containing the operation to perform between
## @var{sub} and @var{clip}. Possible values are:
##
## @itemize @bullet
## @item @var{'AND'}
## Intersection of @var{sub} and @var{clip}. This value is set by default.
## @item @var{'OR'}
## Union of @var{sub} and @var{clip}.
## @item @var{'AB'}
## Operation @var{sub} - @var{clip}.
## @item @var{'BA'}
## Operation of @var{clip} - @var{sub}.
## @item @var{'XOR'}
## Exclusive disjunction between @var{clip} and @var{sub}. This operation is
## performed as the joining of 'AB' and 'BA' consecutively applied
## @end itemize
##
## @var{hor} is an identifier for performing (value 1, by default) or not
## (value 0) the searching for holes in the result of the operation OR. When OR
## is applied with non convex entities some of the resulting polygons can be
## actually holes. Activating this argument the possible holes are identified.
## If the operation is other than OR the value of this argument is irrelevant
##
## For the matrices @var{sub} and @var{clip}, the first point is not needed to
## be repeated at the end (but is permitted). Pairs of (NaN,NaN) coordinates in
## @var{sub} and/or @var{clip} are omitted, so they are treated as if each one
## stored a single polygon, i.e., this function does not admit boolean
## operations between multiple polygons of between polygons with holes, although
## polygons containing self-intersections are permitted
##
## @var{p} is a two column matrix containing the X and Y coordinates of the
## vertices of the resultant polygon(s). If the result consist of multiple
## polygons they are separated by rows os (NaN,NaN) values.
##
## @var{details} is a struct containing details of the computation. Its fields
## (IN LOWERCASE!) are:
##
## @itemize @bullet
## @item @var{poly}
## Three-column matrix with a number of rows equal to the number of polygons
## stored in the matrix @var{p}. The first column stores the row of @var{p}
## where the corresponding polygon starts, the second column the row of @var{p}
## where the polygon end, and the third column is a mark identifying if the
## polygon is a hole (value 0) or not (value 1). The values of the third column
## are relevant only in the case of the OR operation
## @item @var{nint}
## Number of intersections between @var{sub} and @var{clip}.
## @item @var{npert}
## Number of perturbed points of the @var{clip} polygon if any particular case
## (points in the border of the other polygon) occurs see
## http://davis.wpi.edu/~matt/courses/clipping/ for details.
## @end itemize
##
## This function does not check if the dimensions of @var{sub} and @var{clip}
## are correct.
##
## @end deftypefn

function [p,details] = oc_polybool(sub,clip,op,hor)

try
    functionName = 'oc_polybool';
    minArg = 2;
    maxArg = 4;

%*******************************************************************************
%NUMBER OF INPUT ARGUMENTS CHECKING
%*******************************************************************************

    %number of input arguments checking
    if (nargin<minArg)||(nargin>maxArg)
        error(['Incorrect number of input arguments (%d)\n\t         ',...
               'Correct number of input arguments = %d or %d'],...
              nargin,minArg,maxArg);
    end
    %values by default
    opDef = 'AND';
    horDef = 1;
    %check if we omit some input arguments
    if nargin<maxArg
        %hor by default
        hor = horDef;
        %check for op
        if nargin==minArg
            %op by default
            op = opDef;
        end
    end

%*******************************************************************************
%INPUT ARGUMENTS CHECKING
%*******************************************************************************

    %checking input arguments
    [op] = checkInputArguments(sub,clip,op);
catch
    %error message
    error('\n\tIn function %s:\n\t -%s ',functionName,lasterr);
end

%*******************************************************************************
%COMPUTATION
%*******************************************************************************

try
    %calling oct function
    [p,pp,nInt,nPert] = _oc_polybool(sub,clip,op,hor);
    %creation of the output struct
    details.poly = pp;
    details.nint = nInt;
    details.npert = nPert;
catch
    %error message
    error('\n\tIn function %s:\n\tIn function %s ',functionName,lasterr);
end




%*******************************************************************************
%AUXILIARY FUNCTION
%*******************************************************************************




function [outOp] = checkInputArguments(sub,clip,inOp)

%sub must be matrix type
if isnumeric(sub)
    %a dimensions
    [rowSub,colSub] = size(sub);
else
    error('The first input argument is not numeric');
end
%clip must be matrix type
if isnumeric(clip)
    %b dimensions
    [rowClip,colClip] = size(clip);
else
    error('The second input argument is not numeric');
end
%checking dimensions
if (colSub~=2)||(colClip~=2)
    error('The columns of input arguments must be 2');
end
%operation must be a text string
if ~ischar(inOp)
    error('The third input argument is not a text string');
else
    %upper case
    outOp = upper(inOp);
    %check values
    if (~strcmp(outOp,'AND'))&&(~strcmp(outOp,'OR'))&& ...
        (~strcmp(outOp,'AB'))&&(~strcmp(outOp,'BA'))&&(~strcmp(outOp,'XOR'))
        error('The third input argument is not correct');
    end
end




%*****END OF FUNCIONS*****




%*****FUNCTION TESTS*****




%tests for input arguments
%!error(oc_polybool)
%!error(oc_polybool(1))
%!error(oc_polybool(1,2,3,4,5))
%!error(oc_polybool('string',2,3))
%!error(oc_polybool(1,'string',3))
%!error(oc_polybool(1,2,3))
%demo program
%!demo
%!  %subject polygon
%!  clSub = [9.0 7.5
%!           9.0 3.0
%!           2.0 3.0
%!           2.0 4.0
%!           8.0 4.0
%!           8.0 5.0
%!           2.0 5.0
%!           2.0 6.0
%!           8.0 6.0
%!           8.0 7.0
%!           2.0 7.0
%!           2.0 7.5
%!           9.0 7.5];
%!  %clipper polygon
%!  clClip = [2.5 1.0
%!            7.0 1.0
%!            7.0 8.0
%!            6.0 8.0
%!            6.0 2.0
%!            5.0 2.0
%!            5.0 8.0
%!            4.0 8.0
%!            4.0 2.0
%!            3.0 2.0
%!            3.0 8.0
%!            2.5 8.0
%!            2.5 1.0];
%!  %limits for the plots
%!  clXLim = [1.5 11.75];
%!  clYLim = [0.5  8.50];
%!  %compute intersection
%!  [pI,detI] = oc_polybool(clSub,clClip,'and');
%!  %compute union
%!  [pU,detU] = oc_polybool(clSub,clClip,'or',1);
%!  %compute A-B
%!  [pA,detA] = oc_polybool(clSub,clClip,'ab');
%!  %compute B-A
%!  [pB,detB] = oc_polybool(clSub,clClip,'ba');
%!  %compute XOR
%!  [pX,detX] = oc_polybool(clSub,clClip,'xor');
%!  %plotting
%!  figure(1);
%!  %plot window for original data
%!  subplot(3,2,1);
%!  plot(clSub(:,1),clSub(:,2),clClip(:,1),clClip(:,2));
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('Original polygons');
%!  legend('Subject polygon','Clipper polygon','location','southeast');
%!  %plot window for intersection
%!  subplot(3,2,2);
%!  hold('on');
%!  for i=1:size(detI.poly,1)
%!      pS = detI.poly(i,1);
%!      pE = detI.poly(i,2);
%!      fill(pI(pS:pE,1),pI(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP intersection');
%!  %plot window for union
%!  subplot(3,2,3);
%!  hold('on');
%!  for i=1:size(detU.poly,1)
%!      pS = detU.poly(i,1);
%!      pE = detU.poly(i,2);
%!      if detU.poly(i,3)~=0
%!          fill(pU(pS:pE,1),pU(pS:pE,2),'r');
%!      else
%!          hax = fill(pU(pS:pE,1),pU(pS:pE,2),'b');
%!          legend(hax,'Holes');
%!      end
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP union');
%!  %plot window for A-B
%!  subplot(3,2,4);
%!  hold('on');
%!  for i=1:size(detA.poly,1)
%!      pS = detA.poly(i,1);
%!      pE = detA.poly(i,2);
%!      fill(pA(pS:pE,1),pA(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP A-B');
%!  %plot window for B-A
%!  subplot(3,2,5);
%!  hold('on');
%!  for i=1:size(detB.poly,1)
%!      pS = detB.poly(i,1);
%!      pE = detB.poly(i,2);
%!      fill(pB(pS:pE,1),pB(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP B-A');
%!  %plot window for XOR
%!  subplot(3,2,6);
%!  hold('on');
%!  for i=1:size(detX.poly,1)
%!      pS = detX.poly(i,1);
%!      pE = detX.poly(i,2);
%!      fill(pX(pS:pE,1),pX(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP XOR');
%!  %input message
%!  disp('Press ENTER to continue ...');
%!  pause();
%!
%!  %subject polygon
%!  clSub = [1000.0 1000.0
%!           2000.0 1000.0
%!           2000.0 2800.0
%!            800.0 2800.0
%!           2400.0 1900.0
%!           1000.0 1000.0];
%!  %clipper polygon
%!  clClip = [ 300.0 2500.0
%!            2500.0 2600.0
%!            1600.0  600.0
%!            1300.0 3100.0
%!             500.0  900.0
%!             300.0 2500.0];
%!  %limits for the plots
%!  auxLim = [clSub;clClip];
%!  clXLim = [min(auxLim(:,1)) max(auxLim(:,1))];
%!  clYLim = [min(auxLim(:,2)) max(auxLim(:,2))];
%!  %compute intersection
%!  [pI,detI] = oc_polybool(clSub,clClip,'and');
%!  %compute union
%!  [pU,detU] = oc_polybool(clSub,clClip,'or',1);
%!  %compute A-B
%!  [pA,detA] = oc_polybool(clSub,clClip,'ab');
%!  %compute B-A
%!  [pB,detB] = oc_polybool(clSub,clClip,'ba');
%!  %compute XOR
%!  [pX,detX] = oc_polybool(clSub,clClip,'xor');
%!  %plotting
%!  figure(2);
%!  %plot window for original data
%!  subplot(3,2,1);
%!  plot(clSub(:,1),clSub(:,2),clClip(:,1),clClip(:,2));
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('Original polygons');
%!  legend('Subject polygon','Clipper polygon','location','southeast');
%!  %plot window for intersection
%!  subplot(3,2,2);
%!  hold('on');
%!  for i=1:size(detI.poly,1)
%!      pS = detI.poly(i,1);
%!      pE = detI.poly(i,2);
%!      fill(pI(pS:pE,1),pI(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP intersection');
%!  %plot window for union
%!  subplot(3,2,3);
%!  hold('on');
%!  for i=1:size(detU.poly,1)
%!      pS = detU.poly(i,1);
%!      pE = detU.poly(i,2);
%!      if detU.poly(i,3)~=0
%!          fill(pU(pS:pE,1),pU(pS:pE,2),'r');
%!      else
%!          hax = fill(pU(pS:pE,1),pU(pS:pE,2),'b');
%!          legend(hax,'Holes');
%!      end
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP union');
%!  %plot window for A-B
%!  subplot(3,2,4);
%!  hold('on');
%!  for i=1:size(detA.poly,1)
%!      pS = detA.poly(i,1);
%!      pE = detA.poly(i,2);
%!      fill(pA(pS:pE,1),pA(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP A-B');
%!  %plot window for B-A
%!  subplot(3,2,5);
%!  hold('on');
%!  for i=1:size(detB.poly,1)
%!      pS = detB.poly(i,1);
%!      pE = detB.poly(i,2);
%!      fill(pB(pS:pE,1),pB(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP B-A');
%!  %plot window for XOR
%!  subplot(3,2,6);
%!  hold('on');
%!  for i=1:size(detX.poly,1)
%!      pS = detX.poly(i,1);
%!      pE = detX.poly(i,2);
%!      fill(pX(pS:pE,1),pX(pS:pE,2),'r');
%!  end
%!  hold('off');
%!  axis('equal');
%!  xlim(clXLim);
%!  ylim(clYLim);
%!  title('OctCLIP XOR');




%*****END OF TESTS*****
