## Copyright (C) 2016-2017 Juan Pablo Carbajal
##
## This program 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 this program. If not, see .
## -*- texinfo -*-
## @deftypefn {} {@var{ccw} =} isPolygonCCW (@var{p})
## @deftypefnx {} {@var{ccw} =} isPolygonCCW (@var{px}, @var{py})
## Returns true if the polygon @var{p} are oriented Counter-Clockwise.
##
## @var{p} is a N-by-2 array containing coordinates of vertices. The coordinates
## of the vertices of the polygon can also be given as two N-by-1 arrays
## @var{px}, @var{py}.
##
## Optional third argument @var{library} can be one of "geometry" or
## "clipper". In the latter case the potentially faster Clipper polygon
## library will be invoked to assess winding directions. The default is
## "geometry".
##
## If any polygon is self-crossing, the result is undefined.
##
## If @var{p} is a cell, each element is considered a polygon, the
## resulting @var{cww} is a cell of the same size.
##
## @seealso{polygonArea}
## @end deftypefn
## Author: Juan Pablo Carbajal
function ccw = isPolygonCCW (px, py=[], lib="geometry")
## case of wrong number of input arguments
if (nargin > 3 || nargin < 1)
print_usage ();
endif
if (ischar (py))
py = lower (py);
if (ismember (py, {"geometry", "clipper"}))
lib = py;
py = [];
else
error ('Octave:invalid-fun-call', "use of '%s' not implemented", lib);
endif
endif
if (! isempty (py))
if (! strcmp (typeinfo (px), typeinfo (py)))
error ('Octave:invalid-input-arg', 'X and Y should be of the same type');
endif
if (any (size (px) != size (py)) )
error ('Octave:invalid-input-arg', 'X and Y should be of the same size');
endif
endif
if (iscell (px))
## Cell Array Format
## Call this function on each cell
if (isempty (py))
py = cell (size (px));
endif
ccw = cellfun (@(u,v) isPolygonCCW (u,v,lib), px, py, "unif", 0);
else
## Input are matrices
## merge them to one
px = [px py];
if (isempty (px))
error ("isPolygonCW: empty input polygon encountered");
elseif (strcmpi (lib, "clipper"))
## Clipper can do all of them in one call
ccw = logical (isPolygonCW_Clipper (px));
return
endif
if (any (isnan (px(:))))
## Inputs are many polygons separated with NaN
## Split them and call this function on each of them
px = splitPolygons (px);
ccw = cellfun (@(u) isPolygonCCW (u,[],lib), px);
else ## Here do the actual work
ccw = polygonArea (px) > 0;
endif
endif
endfunction
%!shared pccw, pcw, pxccw, pyccw, pxnan, pynan, pnan
%! pccw = [0 0; 1 0; 1 1; 0 1];
%! pcw = reversePolygon (pccw);
%! pxccw = pccw(:,1);
%! pyccw = pccw(:,2);
%! pxnan = [2; 2; 0; 0; NaN; 0; 0; 2];
%! pynan = [0; 2; 2; 0; NaN; 0; 3; 0];
%! pnan = [pxnan pynan];
## Native testing
%!assert (isPolygonCCW (pccw));
%!assert (isPolygonCCW (pxccw, pyccw));
%!assert (isPolygonCCW ({pxccw, pxccw}, {pyccw, pyccw}), {true, true});
%!assert (~isPolygonCCW (pcw));
%!assert (isPolygonCCW ({pccw;pcw}), {true;false});
%!assert (isPolygonCCW(pnan), [true; false]);
%!assert (isPolygonCCW({pnan,pcw}),{[true;false], false});
## Clipper testing
%!assert (isPolygonCCW (pccw,[],"clipper"));
%!assert (isPolygonCCW (pxccw, pyccw,"clipper"));
%!assert (isPolygonCCW ({pxccw, pxccw}, {pyccw, pyccw},"clipper"), {true, true});
%!assert (~isPolygonCCW (pcw,[],"clipper"));
%!assert (isPolygonCCW ({pccw;pcw},[],"clipper"), {true;false});
%!xtest assert (isPolygonCCW(pnan,[],"clipper"), [true; false]);
%!xtest assert (isPolygonCCW({pnan,pcw},[],"clipper"),{[true; false], false});