Mercurial > octave
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 |