1########################################################################
2##
3## Copyright (C) 2012-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {} shrinkfaces (@var{p}, @var{sf})
28## @deftypefnx {} {@var{nfv} =} shrinkfaces (@var{p}, @var{sf})
29## @deftypefnx {} {@var{nfv} =} shrinkfaces (@var{fv}, @var{sf})
30## @deftypefnx {} {@var{nfv} =} shrinkfaces (@var{f}, @var{v}, @var{sf})
31## @deftypefnx {} {[@var{nf}, @var{nv}] =} shrinkfaces (@dots{})
32##
33## Reduce the size of faces in a patch by the shrink factor @var{sf}.
34##
35## The patch object can be specified by a graphics handle (@var{p}), a patch
36## structure (@var{fv}) with the fields @qcode{"faces"} and @qcode{"vertices"},
37## or as two separate matrices (@var{f}, @var{v}) of faces and vertices.
38##
39## The shrink factor @var{sf} is a positive number specifying the percentage
40## of the original area the new face will occupy.  If no factor is given the
41## default is 0.3 (a reduction to 30% of the original size).  A factor greater
42## than 1.0 will result in the expansion of faces.
43##
44## Given a patch handle as the first input argument and no output parameters,
45## perform the shrinking of the patch faces in place and redraw the patch.
46##
47## If called with one output argument, return a structure with fields
48## @qcode{"faces"}, @qcode{"vertices"}, and @qcode{"facevertexcdata"}
49## containing the data after shrinking.  This structure can be used directly
50## as an input argument to the @code{patch} function.
51##
52## @strong{Caution:}: Performing the shrink operation on faces which are not
53## convex can lead to undesirable results.
54##
55## Example: a triangulated 3/4 circle and the corresponding shrunken version.
56##
57## @example
58## @group
59## [phi r] = meshgrid (linspace (0, 1.5*pi, 16), linspace (1, 2, 4));
60## tri = delaunay (phi(:), r(:));
61## v = [r(:).*sin(phi(:)) r(:).*cos(phi(:))];
62## clf ()
63## p = patch ("Faces", tri, "Vertices", v, "FaceColor", "none");
64## fv = shrinkfaces (p);
65## patch (fv)
66## axis equal
67## grid on
68## @end group
69## @end example
70##
71## @seealso{patch}
72## @end deftypefn
73
74function [nf, nv] = shrinkfaces (varargin)
75
76  if (nargin < 1 || nargin > 3 || nargout > 2)
77    print_usage ();
78  endif
79
80  sf = 0.3;
81  colors = [];
82  p = varargin{1};
83
84  if (isscalar (p) && isgraphics (p, "patch") && nargin < 3)
85    faces = get (p, "Faces");
86    vertices = get (p, "Vertices");
87    colors = get (p, "FaceVertexCData");
88    if (nargin == 2)
89      sf = varargin{2};
90    endif
91  elseif (isstruct (p) && nargin < 3)
92    faces = p.faces;
93    vertices = p.vertices;
94    if (isfield (p, "facevertexcdata"))
95      colors = p.facevertexcdata;
96    endif
97    if (nargin == 2)
98      sf = varargin{2};
99    endif
100  elseif (ismatrix (p) && nargin >= 2 && ismatrix (varargin{2}))
101    faces = p;
102    vertices = varargin{2};
103    if (nargin == 3)
104      sf = varargin{3};
105    endif
106  else
107    print_usage ();
108  endif
109
110  if (! isscalar (sf) || sf <= 0)
111    error ("shrinkfaces: scale factor must be a positive scalar");
112  endif
113
114  nc = columns (vertices);
115  if (nc < 2 || nc > 3)
116    error ("shrinkfaces: only 2-D and 3-D patches are supported");
117  endif
118
119  m = columns (faces);
120  if (m < 3)
121    error ("shrinkfaces: faces must consist of at least 3 vertices");
122  endif
123
124  v = vertices(faces'(:), :);
125  if (isempty (colors) || rows (colors) == rows (faces))
126    c = colors;
127  elseif (rows (colors) == rows (vertices))
128    c = colors(faces'(:), :);
129  else
130    c = [];  # Discard inconsistent color data.
131  endif
132  sv = rows (v);
133  ## We have to deal with a possibly very large number of vertices, so use
134  ## sparse as midpoint (1/m, ..., 1/m) in generalized barycentric coordinates.
135  midpoints = full (kron (speye (sv / m), ones (m, m) / m) * sparse (v));
136  v = sqrt (sf) * (v - midpoints) + midpoints;
137  f = reshape (1:sv, m, sv / m)';
138
139  switch (nargout)
140    case 0
141      if (ishghandle (p))
142        ## avoid exceptions
143        set (p, "FaceVertexCData", [], "CData", [],
144                "Vertices", v, "Faces", f, "FaceVertexCData", c);
145      else
146        nf = struct ("faces", f, "vertices", v, "facevertexcdata", c);
147      endif
148    case 1
149      nf = struct ("faces", f, "vertices", v, "facevertexcdata", c);
150    case 2
151      nf = f;
152      nv = v;
153  endswitch
154
155endfunction
156
157
158%!demo
159%! clf;
160%! faces = [1 2 3; 1 3 4];
161%! vertices = [0 0; 1 0; 1 1; 0 1];
162%! patch ("Faces", faces, "Vertices", vertices, "FaceColor", "none");
163%! fv = shrinkfaces (faces, vertices, 0.25);
164%! patch (fv);
165%! axis auto;   # Kludge required for Octave
166%! axis equal;
167%! title ("shrinkfaces() on triangular shapes");
168
169%!demo
170%! clf;
171%! faces = [1 2 3 4; 5 6 7 8];
172%! vertices = [0 0; 1 0; 2 1; 1 1; 2 0; 3 0; 4 1; 3.5 1];
173%! patch ("Faces", faces, "Vertices", vertices, "FaceColor", "none");
174%! fv = shrinkfaces (faces, vertices, 0.25);
175%! patch (fv);
176%! axis auto;   # Kludge required for Octave
177%! axis equal;
178%! grid on;
179%! title ("shrinkfaces() on rhomboid shapes");
180
181%!demo
182%! clf;
183%! faces = [1 2 3 4];
184%! vertices = [-1 2; 0 0; 1 2; 0 1];
185%! patch ("Faces", faces, "Vertices", vertices, "FaceColor", "none");
186%! fv = shrinkfaces (faces, vertices, 0.25);
187%! patch (fv);
188%! axis auto;   # Kludge required for Octave
189%! axis equal;
190%! grid on;
191%! title ("shrinkfaces() does not work on concave shapes");
192
193%!demo
194%! clf;
195%! [phi r] = meshgrid (linspace (0, 1.5*pi, 16), linspace (1, 2, 4));
196%! tri = delaunay (phi(:), r(:));
197%! v = [r(:).*sin(phi(:)) r(:).*cos(phi(:))];
198%! p = patch ("Faces", tri, "Vertices", v, "FaceColor", "none");
199%! fv = shrinkfaces (p);
200%! patch (fv);
201%! axis auto;   # Kludge required for Octave
202%! axis equal;
203%! grid on;
204%! title ("shrinkfaces() on 2-D complex shapes tessellated with triangles");
205
206%!demo
207%! clf;
208%! N = 10;  # N intervals per axis
209%! [x, y, z] = meshgrid (linspace (-4,4,N+1));
210%! val = x.^3 + y.^3 + z.^3;
211%! fv = isosurface (x, y, z, val, 3, z, "noshare");
212%!
213%! p = patch ("Faces", fv.faces, "Vertices", fv.vertices, "FaceVertexCData", ...
214%!            fv.facevertexcdata, "FaceColor", "interp", "EdgeColor", "black");
215%! axis auto;   # Kludge required for Octave
216%! axis equal;
217%! view (115, 30);
218%! drawnow;
219%! shrinkfaces (p, 0.6);
220%! title ("shrinkfaces() on 3-D complex shapes");
221
222%!shared faces, vertices, nfv, nfv2
223%! faces = [1 2 3];
224%! vertices = [0 0 0; 1 0 0; 1 1 0];
225%! nfv = shrinkfaces (faces, vertices, 0.7);
226%! nfv2 = shrinkfaces (nfv, 1/0.7);
227%!assert (isfield (nfv, "faces"))
228%!assert (isfield (nfv, "vertices"))
229%!assert (size (nfv.faces), [1 3])
230%!assert (size (nfv.vertices), [3 3])
231%!assert (norm (nfv2.vertices - vertices), 0, 2*eps)
232
233## Test input validation
234%!error shrinkfaces ()
235%!error shrinkfaces (1,2,3,4)
236%!error [a,b,c] = shrinkfaces (1)
237%!error <scale factor must be a positive scalar> shrinkfaces (nfv, ones (2))
238%!error <scale factor must be a positive scalar> shrinkfaces (nfv, 0)
239%!error <only 2-D and 3-D patches are supported> shrinkfaces (faces, ones (3,1))
240%!error <only 2-D and 3-D patches are supported> shrinkfaces (faces, ones (3,4))
241%!error <faces must consist of at least 3 vertices> shrinkfaces (faces(1:2), vertices)
242