comparison scripts/plot/draw/reducepatch.m @ 22243:654de580bdb3

Add function "reducepatch" (patch #8912) * scripts/plot/draw/reducepatch.m: New function. * scripts/plot/draw/module.mk: Add reducepath.m to build system. * NEWS: Announce new function. * isosurface.m: Add "reducepatch" to @seealso. * __unimplemented__.m: Remove "reducepatch" from list. * plot.txi: Add to documentation.
author Markus Muetzel <markus.muetzel@gmx.de>
date Mon, 18 Jul 2016 19:54:34 +0200
parents
children 3d287a11ea18
comparison
equal deleted inserted replaced
22242:5417dad26a25 22243:654de580bdb3
1 ## Copyright (C) 2016 Markus Muetzel
2 ##
3 ## This file is part of Octave.
4 ##
5 ## Octave is free software; you can redistribute it and/or modify it
6 ## under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 3 of the License, or (at
8 ## your option) any later version.
9 ##
10 ## Octave is distributed in the hope that it will be useful, but
11 ## WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ## General Public License for more details.
14 ##
15 ## You should have received a copy of the GNU General Public License
16 ## along with Octave; see the file COPYING. If not, see
17 ## <http://www.gnu.org/licenses/>.
18
19 ## -*- texinfo -*-
20 ## @deftypefn {} {@var{reduced_fv} =} reducepatch (@var{fv})
21 ## @deftypefnx {} {@var{reduced_fv} =} reducepatch (@var{faces}, @var{vertices})
22 ## @deftypefnx {} {@var{reduced_fv} =} reducepatch (@var{patch_handle})
23 ## @deftypefnx {} {} reducepatch (@var{patch_handle})
24 ## @deftypefnx {} {@var{reduced_fv} =} reducepatch (@dots{}, @var{reduction_factor})
25 ## @deftypefnx {} {@var{reduced_fv} =} reducepatch (@dots{}, "fast")
26 ## @deftypefnx {} {@var{reduced_fv} =} reducepatch (@dots{}, "verbose")
27 ## @deftypefnx {} {[@var{reduced_faces}, @var{reduces_vertices}] =} reducepatch (@dots{})
28 ##
29 ## Reduce the number of faces and vertices in a surface or patch object while
30 ## retaining the overall shape of the patch.
31 ##
32 ## The input patch can be represented by a structure @var{fv} with the
33 ## fields @code{faces} and @code{vertices}, by two matrices @var{faces} and
34 ## @var{vertices} (see, e.g., the result of @code{isosurface}), or by a
35 ## handle to a patch object @var{patch_handle} (@pxref{XREFpatch,,patch}).
36 ##
37 ## The number of faces and vertices in the patch is reduced by iteratively
38 ## collapsing the shortest edge of the patch to its midpoint (as discussed,
39 ## e.g., here:
40 ## @url{http://libigl.github.io/libigl/tutorial/tutorial.html#meshdecimation}).
41 ##
42 ## Currently, only patches consisting of triangles are supported. The
43 ## resulting patch also consists only of triangles.
44 ##
45 ## If @code{reducepatch} is called with a handle to a valid patch
46 ## @var{patch_handle}, and without any output arguments, then the given
47 ## patch is updated immediately.
48 ##
49 ## If the @var{reduction_factor} is omitted, the resulting structure
50 ## @var{reduced_fv} includes approximately 50% of the faces of the original
51 ## patch. If @var{reduction_factor} is a fraction between 0 (excluded) and 1
52 ## (excluded), a patch with approximately the corresponding fraction of faces
53 ## is determined.
54 ## If @var{reduction_factor} is an integer greater than or equal to 1, the
55 ## resulting patch has approximately @var{reduction_factor} faces. Depending
56 ## on the geometry of the patch, the resulting number of faces can differ from
57 ## the given value of @var{reduction_factor}. This is especially true when
58 ## many shared vertices are detected.
59 ##
60 ## For the reduction, it is necessary that vertices of touching faces are
61 ## shared. Shared vertices are detected automatically. This detection can be
62 ## skipped by passing the optional string argument @qcode{"fast"}.
63 ##
64 ## With the optional string arguments @qcode{"verbose"}, additional status
65 ## messages are printed to the command window.
66 ##
67 ## Any string input arguments must be passed after all other arguments.
68 ##
69 ## If called with one output argument, the reduced faces and vertices are
70 ## returned in a structure @var{reduced_fv} with the fields @code{faces} and
71 ## @code{vertices} (see the one output option of @code{isosurface}).
72 ##
73 ## If called with two output arguments, the reduced faces and vertices are
74 ## returned in two separate matrices @var{reduced_faces} and
75 ## @var{reduced_vertices}.
76 ##
77 ## @seealso{isosurface, isonormals, reducevolume, patch}
78 ## @end deftypefn
79
80 ## Author: mmuetzel
81
82 ## FIXME: Convert faces to only triangles if necessary
83
84 function varargout = reducepatch (varargin)
85
86 if (nargin < 1 || nargin > 5)
87 print_usage ();
88 endif
89
90 [faces, vertices, max_faces, patch_handle, fast, verbose] = ...
91 __get_check_reducepatch_args__ (varargin{:});
92
93 if (verbose)
94 printf (["reducepatch: before reduction of faces: ", ...
95 "%5d faces, %5d vertices\n"], rows (faces), rows (vertices));
96 endif
97
98 if (max_faces >= rows (faces))
99 faces_reduced = faces;
100 vertices_reduced = vertices;
101 else
102 if (! fast)
103 [faces, vertices] = __unite_shared_vertices__ (faces, vertices);
104
105 if (verbose)
106 printf (["reducepatch: after detection of shared vertices: ", ...
107 "%5d faces, %5d vertices\n"], rows (faces), rows (vertices));
108 endif
109 endif
110
111 [faces_reduced, vertices_reduced] = ...
112 __reducepatch__ (vertices, faces, max_faces, verbose);
113 endif
114
115 if (verbose)
116 printf (["reducepatch: after reduction of faces: ", ...
117 "%5d faces, %5d vertices\n"], ...
118 rows (faces_reduced), rows (vertices_reduced));
119 endif
120
121 ## output
122 if (! isempty (patch_handle) && nargout == 0)
123 ## update patch object
124 set (patch_handle, "Faces", faces_reduced, "Vertices", vertices_reduced);
125 elseif (nargout == 2) # faces, vertices
126 varargout{1} = faces_reduced;
127 varargout{2} = vertices_reduced;
128 else # fv structure
129 varargout{1}.faces = faces_reduced;
130 varargout{1}.vertices = vertices_reduced;
131 endif
132
133 endfunction
134
135 ## assign input parameters and check their validity
136 function [faces, vertices, max_faces, patch_handle, fast, verbose] = ...
137 __get_check_reducepatch_args__ (varargin)
138
139 ## default values
140 faces = vertices = max_faces = patch_handle = [];
141 reduction_factor = 0.5;
142 fast = verbose = false;
143
144 num_string_inputs = 0;
145 ## check whether last 2 input arguments are strings and assign parameters
146 valid_vals = nargin:-1:nargin-1;
147 valid_vals(valid_vals < 1) = [];
148 for arg = varargin(valid_vals)
149 if (! ischar (arg{1}))
150 break; # no string arguments at end, exit checking
151 endif
152 switch (tolower (arg{1}))
153 case {"f", "fast"}
154 fast = true;
155 num_string_inputs += 1;
156
157 case {"v", "verbose"}
158 verbose = true;
159 num_string_inputs += 1;
160
161 case ""
162 num_string_inputs++;
163 ## silently ignore empty strings
164
165 otherwise
166 error ("reducepatch: parameter '%s' not supported", arg{1});
167
168 endswitch
169 endfor
170
171 ## get faces and vertices
172 i_fv = 1;
173 arg1 = varargin{1};
174 if (isstruct (arg1))
175 if (all (isfield (arg1, {"vertices", "faces"})))
176 vertices = arg1.vertices;
177 faces = arg1.faces;
178 else
179 error (["reducepatch: struct FV must contain the fields ", ...
180 "'vertices' and 'faces'."]);
181 endif
182 elseif (isscalar (arg1))
183 patch_handle = arg1;
184 if (ishghandle (patch_handle, "patch"))
185 vertices = get (patch_handle, "Vertices");
186 faces = get (patch_handle, "Faces");
187 else
188 error ("reducepatch: PATCH_HANDLE must be a valid handle to a patch");
189 endif
190 elseif (ismatrix (arg1))
191 faces = arg1;
192 if (nargin - num_string_inputs > 1 && ismatrix (varargin{2}))
193 vertices = varargin{2};
194 i_fv = 2;
195 else
196 error (["reducepatch: If first argument is a matrix containing ", ...
197 "FACES, second argument must be a matrix containing VERTICES"]);
198 endif
199 else
200 print_usage ();
201 endif
202
203 ## get reduction_factor
204 if (nargin - num_string_inputs > i_fv)
205 reduction_factor = varargin{i_fv + 1};
206 if (! isscalar (reduction_factor) || reduction_factor <= 0)
207 error ("reducepatch: REDUCTION_FACTOR must be a positive scalar");
208 endif
209 endif
210
211 ## check faces and vertices
212 if (columns (vertices) != 3)
213 error ("reducepatch: VERTICES must be an Mx3 matrix");
214 endif
215 if (columns (faces) != 3)
216 ## FIXME: Convert faces to only triangles if necessary
217 error ("reducepatch: Currently patches must consist of triangles only");
218 endif
219 if (any (mod (faces(:), 1)) || any (faces(:) < 1))
220 error ("reducepatch: FACES must consist of positive integer indices only");
221 endif
222 if (max (faces(:)) > rows (vertices))
223 error ("reducepatch: not enough VERTICES for given FACES");
224 endif
225
226 ## get max_faces
227 num_faces = rows (faces);
228 if (reduction_factor < 1)
229 max_faces = floor (num_faces * reduction_factor);
230 else
231 max_faces = reduction_factor;
232 endif
233
234 endfunction
235
236 ## actual function to reduce number of faces
237 function [faces_reduced, vertices_reduced] = ...
238 __reducepatch__ (vertices, faces, max_faces, verbose)
239
240 num_faces = rows (faces);
241 faces = sort (faces, 2);
242
243 ## get all unique edges
244 all_edges = sort ([faces(:,1) faces(:,2);
245 faces(:,2) faces(:,3);
246 faces(:,3) faces(:,1)], 2);
247 edges = unique (all_edges, "rows");
248 num_edges = rows (edges);
249
250 ## calculate length of edges
251 edge_length = norm (vertices(edges(:,1),:) - vertices(edges(:,2),:), ...
252 2, "rows");
253
254 if (verbose)
255 printf ("reducepatch: reducing number of faces");
256 ## do not spam the command window with dots if many faces are collapsed
257 verbose_stepwidth = (num_faces - max_faces) / 100; # max. 100 dots
258 verbose_counter = 0;
259 endif
260
261 ## reduce number of faces
262 clean_finish = true;
263 do
264 ## find shortest edge
265 i_shortest_edge = find (edge_length == min (edge_length), 1, "first");
266 if (any (isinf (vertices(edges(i_shortest_edge,:),:))))
267 warning (["reducepatch: shortest edge is marked as deleted. ", ...
268 "This should never happen."])
269 clean_finish = false;
270 break;
271 endif
272
273 start_point_idx = edges(i_shortest_edge,1);
274 end_point_idx = edges(i_shortest_edge,2);
275
276 ## collapse the edge to its midpoint
277 vertices(start_point_idx,:) = 0.5 * ...
278 (vertices(start_point_idx,:) + vertices(end_point_idx,:));
279 ## endpoint will be deleted later
280
281 ## remove collapsed edge info
282 edges(i_shortest_edge,:) = [];
283 edge_length(i_shortest_edge) = [];
284
285 ## unite collapsed edges of neighboring faces to one edge
286 faces_with_collapsed_edge = any (faces == start_point_idx, 2) & ...
287 any (faces == end_point_idx, 2);
288 edges_of_collapsed_faces = faces(faces_with_collapsed_edge,:);
289 ## get other vertex of collapsed faces
290 third_vertices = ...
291 edges_of_collapsed_faces(...
292 faces(faces_with_collapsed_edge,:) != start_point_idx & ...
293 faces(faces_with_collapsed_edge,:) != end_point_idx);
294 if (! isempty (third_vertices))
295 ## delete edge of the collapsed faces which also has end_point
296 ## (keep the one with start_point)
297 edges_to_delete = any (edges == end_point_idx, 2) & ...
298 any (any (bsxfun (@eq, edges, ...
299 reshape (third_vertices, 1, 1, [])), 3), 2);
300 edges(edges_to_delete,:) = [];
301 edge_length(edges_to_delete) = [];
302 endif
303
304 ## mark the faces that collapsed for later removal
305 faces(faces_with_collapsed_edge,:) = Inf;
306
307 ## update the lengths of the moved edges
308 edges_with_moved_point = any (edges == start_point_idx, 2);
309 edge_length(edges_with_moved_point) = ...
310 norm (vertices(edges(edges_with_moved_point,1),:) - ...
311 vertices(edges(edges_with_moved_point,2),:), 2, "rows");
312
313 ## replace all vertices that use end_point to use start_point
314 if (start_point_idx != end_point_idx)
315 edges(edges == end_point_idx) = start_point_idx;
316 faces(faces == end_point_idx) = start_point_idx;
317 vertices(end_point_idx,:) = Inf; # mark vertex for later removal
318 endif
319
320 ## Pretty print a row of dots while performing calulation
321 if (verbose && ++verbose_counter > verbose_stepwidth)
322 printf (".");
323 verbose_counter = 0;
324 endif
325
326 until (max_faces > num_faces - sum (isinf (faces(:,1))))
327
328 if (verbose)
329 printf ("\n");
330 endif
331
332 if (! clean_finish)
333 ## FIXME: What should be done in this case?
334 ## continue anyway?
335 endif
336
337 ## finally, remove vertices and faces
338 removed_vertices = isinf (vertices(:,1));
339 vertices_reduced = vertices(! removed_vertices,:);
340
341 faces_reduced = faces(! isinf (faces(:,1)),:);
342
343 ## renumber vertices in faces with subsequent numbers
344 vertex_renum = ones (rows (vertices), 1);
345 vertex_renum(removed_vertices) = 0;
346 vertex_renum = cumsum (vertex_renum);
347 faces_reduced = vertex_renum(faces_reduced);
348
349 endfunction
350
351
352 %!demo
353 %! [x,y,z] = meshgrid (-2:0.5:2, -2:0.5:2, -2:0.5:2);
354 %! val = x.^2 + y.^2 + z.^2;
355 %! fv = isosurface (x, y, z, val, 1);
356 %! figure;
357 %! ax1 = subplot (1, 2, 1);
358 %! patch (fv, "FaceColor", "g");
359 %! view (3); axis equal;
360 %! title ("Sphere with all faces");
361 %! ax2 = subplot(1, 2, 2);
362 %! patch (reducepatch (fv, 72), "FaceColor", "g");
363 %! view (3); axis equal;
364 %! title ("Sphere with reduced number of faces");
365 %! linkprop ([ax1, ax2], {"CameraPosition", "CameraUpVector"});
366
367 %!shared faces, vertices, fv, num_faces
368 %! [x,y,z] = meshgrid (-2:0.5:2, -2:0.5:2, -2:0.5:2);
369 %! val = x.^2 + y.^2 + z.^2;
370 %! [faces, vertices] = isosurface (x, y, z, val, 1);
371 %! fv.faces = faces; fv.vertices = vertices;
372 %! num_faces = rows (faces);
373
374 ## one input (structure), one output
375 %!test
376 %! fv_reduced = reducepatch (fv);
377 %! assert (size (fv_reduced.faces, 1), num_faces * .5, 3);
378 %! assert (size (fv_reduced.faces, 2), 3);
379 %! assert (size (fv_reduced.vertices, 2), 3);
380
381 ## two inputs (faces, vertices), one output
382 %!test
383 %! fv_reduced = reducepatch (faces, vertices);
384 %! assert (size (fv_reduced.faces, 1), num_faces * .5, 3);
385 %! assert (size (fv_reduced.faces, 2), 3);
386 %! assert (size (fv_reduced.vertices, 2), 3);
387
388 ## two inputs (structure, reduction_factor > 1), two outputs
389 %!test
390 %! [faces_reduced, vertices_reduced] = reducepatch (fv, 20);
391 %! assert (size (faces_reduced, 1), 20, 3);
392 %! assert (size (faces_reduced, 2), 3);
393 %! assert (size (vertices_reduced, 2), 3);
394
395 ## three inputs (faces, vertices, reduction_factor < 1), two outputs
396 %!test
397 %! [faces_reduced, vertices_reduced] = reducepatch (faces, vertices, .5);
398 %! assert (size (faces_reduced, 1), num_faces * .5, 3);
399 %! assert (size (faces_reduced, 2), 3);
400 %! assert (size (vertices_reduced, 2), 3);
401
402 ## two inputs (structure, reduction_factor < 1) + one string, no outputs
403 ## (update patch object in figure)
404 %!test
405 %! h_fig = figure ("visible", "off");
406 %! p = patch (fv);
407 %! reducepatch (p, .35, "f");
408 %! assert (size (get (p, "Faces"), 1), num_faces * .35, 3);
409 %! assert (size (get (p, "Faces"), 2), 3);
410 %! assert (size (get (p, "Vertices"), 2), 3);
411 %! close (h_fig);
412
413 ## three inputs (faces, vertices, reduction_factor > 1) + one empty
414 ## string, one output
415 %!test
416 %! fv_reduced = reducepatch (faces, vertices, 52, "");
417 %! assert (size (fv_reduced.faces, 1), 52, 3);
418 %! assert (size (fv_reduced.faces, 2), 3);
419 %! assert (size (fv_reduced.vertices, 2), 3);
420
421 ## two inputs (structure, reduction_factor < 1) + one string, two outputs
422 %!test
423 %! [faces_reduced, vertices_reduced] = reducepatch (fv, .4, "fast");
424 %! assert (size (faces_reduced, 1), num_faces * .4, 3);
425 %! assert (size (faces_reduced, 2), 3);
426 %! assert (size (vertices_reduced, 2), 3);
427
428 ## one input (structure) + two (empty) strings, two outputs
429 %!test
430 %! [faces_reduced, vertices_reduced] = reducepatch (fv, "f", "");
431 %! assert (size (faces_reduced, 1), num_faces * .5, 3);
432 %! assert (size (faces_reduced, 2), 3);
433 %! assert (size (vertices_reduced, 2), 3);
434
435 ## test for each error
436 %!error reducepatch ()
437 %!error reducepatch (fv, faces, vertices, .5, "f", "v")
438 %!error <reducepatch: parameter 'foo' not supported>
439 %! fv_reduced = reducepatch (faces, vertices, .7, "foo");
440 %!error <struct FV must contain the fields 'vertices' and 'faces'>
441 %! fv_incomplete.faces = faces;
442 %! reducepatch (fv_incomplete, .7);
443 %!error <PATCH_HANDLE must be a valid handle to a patch> reducepatch (pi, .7)
444 %!error <If first argument is a matrix containing FACES, second argument must be a matrix containing VERTICES> reducepatch (faces)
445 %!error <REDUCTION_FACTOR must be a positive scalar> reducepatch (fv, [.7 .5])
446 %!error <REDUCTION_FACTOR must be a positive scalar> reducepatch (fv, -5)
447 %!error <VERTICES must be an Mx3 matrix> reducepatch (faces, .7, "v")
448 %!error <reducepatch: Currently patches must consist of triangles only>
449 %! faces_new = NaN (size (faces, 1), 4);
450 %! faces_new(:,1:3) = faces;
451 %! faces_new(1,4) = 5;
452 %! reducepatch (faces_new, vertices);
453 %!error <reducepatch: not enough VERTICES for given FACES>
454 %! faces_new = faces;
455 %! faces_new(1,3) = size (vertices, 1) + 1;
456 %! reducepatch (faces_new, vertices);
457