@@ -303,6 +303,32 @@ def mesh_subdivide_catmullclark(mesh, k=1, fixed=None):
303303 >>> subd.number_of_faces() == mesh.number_of_faces() * 4 ** k
304304 True
305305
306+ The algorithm supports "integer creasing" as described in
307+ Subdivision Surfaces in Character Animation [1]_.
308+ Creases are supported through the optional edge attribute ``'crease'``,
309+ which can be set to an integer value that defines how sharp the crease is wrt
310+ the number of subdivision steps.
311+
312+ To add an infinitely sharp crease to an edge, set the ``'crease'`` attribute of the edge
313+ to a number higher than the number of subdivision steps.
314+
315+ >>> from compas.geometry import Box, dot_vectors
316+ >>> from compas.datastructures import Mesh
317+
318+ >>> cage = Mesh.from_shape(Box.from_width_height_depth(1, 1, 1))
319+ >>> cage.update_default_edge_attributes({'crease': 0})
320+ >>> top = sorted(cage.faces(), key=lambda face: dot_vectors(cage.face_normal(face), [0, 0, 1]))[-1]
321+ >>> cage.edges_attribute('crease', 5, keys=list(cage.face_halfedges(top)))
322+
323+ >>> subd = cage.subdivide(k=4)
324+
325+ References
326+ ----------
327+ .. [1] Tony DeRose, Michael Kass and Tien Truong.
328+ Subdivision Surfaces in Character Animation.
329+ Pixar Animation Studios.
330+ see https://graphics.pixar.com/library/Geri/paper.pdf
331+
306332 """
307333 cls = type (mesh )
308334 if not fixed :
@@ -314,9 +340,6 @@ def mesh_subdivide_catmullclark(mesh, k=1, fixed=None):
314340
315341 # keep track of original connectivity and vertex locations
316342
317- bkeys = set (subd .vertices_on_boundary ())
318- bkey_edgepoints = {key : [] for key in bkeys }
319-
320343 # apply quad meshivision scheme
321344 # keep track of the created edge points that are not on the boundary
322345 # keep track track of the new edge points on the boundary
@@ -330,17 +353,14 @@ def mesh_subdivide_catmullclark(mesh, k=1, fixed=None):
330353 for u , v in mesh .edges ():
331354
332355 w = subd .split_edge (u , v , allow_boundary = True )
356+ crease = mesh .edge_attribute ((u , v ), 'crease' ) or 0
333357
334- # document why this is necessary
335- # everything else in this loop is just quad subdivision
336- if u in bkeys and v in bkeys :
337-
338- bkey_edgepoints [u ].append (w )
339- bkey_edgepoints [v ].append (w )
340-
341- continue
342-
343- edgepoints .append (w )
358+ if crease :
359+ edgepoints .append ([w , True ])
360+ subd .edge_attribute ((u , w ), 'crease' , crease - 1 )
361+ subd .edge_attribute ((w , v ), 'crease' , crease - 1 )
362+ else :
363+ edgepoints .append ([w , False ])
344364
345365 fkey_xyz = {fkey : mesh .face_centroid (fkey ) for fkey in mesh .faces ()}
346366
@@ -370,12 +390,12 @@ def mesh_subdivide_catmullclark(mesh, k=1, fixed=None):
370390 # move each edge point to the average of the neighboring centroids and
371391 # the original end points
372392
373- for w in edgepoints :
374- x , y , z = centroid_points ([ key_xyz [ nbr ] for nbr in subd . halfedge [ w ]])
375-
376- subd .vertex [w ]['x' ] = x
377- subd .vertex [w ]['y' ] = y
378- subd .vertex [w ]['z' ] = z
393+ for w , crease in edgepoints :
394+ if not crease :
395+ x , y , z = centroid_points ([ key_xyz [ nbr ] for nbr in subd . halfedge [ w ]])
396+ subd .vertex [w ]['x' ] = x
397+ subd .vertex [w ]['y' ] = y
398+ subd .vertex [w ]['z' ] = z
379399
380400 # move each vertex to the weighted average of itself, the neighboring
381401 # centroids and the neighboring mipoints
@@ -384,32 +404,43 @@ def mesh_subdivide_catmullclark(mesh, k=1, fixed=None):
384404 if key in fixed :
385405 continue
386406
387- if key in bkeys :
388- nbrs = set (bkey_edgepoints [key ])
389- nbrs = [key_xyz [nbr ] for nbr in nbrs ]
390- e = 0.5
391- v = 0.5
392- E = [coord * e for coord in centroid_points (nbrs )]
393- V = [coord * v for coord in key_xyz [key ]]
394- x , y , z = [E [_ ] + V [_ ] for _ in range (3 )]
407+ nbrs = mesh .vertex_neighbors (key )
408+ creases = mesh .edges_attribute ('crease' , keys = [(key , nbr ) for nbr in nbrs ])
395409
396- else :
410+ C = sum (1 if crease else 0 for crease in creases )
411+
412+ if C < 2 :
397413 fnbrs = [mesh .face_centroid (fkey ) for fkey in mesh .vertex_faces (key ) if fkey is not None ]
398- nbrs = [key_xyz [nbr ] for nbr in subd .halfedge [key ]]
399- n = float (len (nbrs ))
400- f = 1.0 / n
401- e = 2.0 / n
402- v = (n - 3.0 ) / n
414+ enbrs = [key_xyz [nbr ] for nbr in subd .halfedge [key ]] # this should be the location of the original neighbour
415+ n = len (enbrs )
416+ v = n - 3.0
403417 F = centroid_points (fnbrs )
404- E = centroid_points (nbrs )
418+ E = centroid_points (enbrs )
405419 V = key_xyz [key ]
406- x = f * F [0 ] + e * E [0 ] + v * V [0 ]
407- y = f * F [1 ] + e * E [1 ] + v * V [1 ]
408- z = f * F [2 ] + e * E [2 ] + v * V [2 ]
409-
410- subd .vertex [key ]['x' ] = x
411- subd .vertex [key ]['y' ] = y
412- subd .vertex [key ]['z' ] = z
420+ x = (F [0 ] + 2.0 * E [0 ] + v * V [0 ]) / n
421+ y = (F [1 ] + 2.0 * E [1 ] + v * V [1 ]) / n
422+ z = (F [2 ] + 2.0 * E [2 ] + v * V [2 ]) / n
423+ subd .vertex [key ]['x' ] = x
424+ subd .vertex [key ]['y' ] = y
425+ subd .vertex [key ]['z' ] = z
426+
427+ elif C == 2 :
428+ V = key_xyz [key ]
429+ E = [0 , 0 , 0 ]
430+ for nbr , crease in zip (nbrs , creases ):
431+ if crease :
432+ x , y , z = key_xyz [nbr ]
433+ E [0 ] += x
434+ E [1 ] += y
435+ E [2 ] += z
436+ x = (6 * V [0 ] + E [0 ]) / 8
437+ y = (6 * V [1 ] + E [1 ]) / 8
438+ z = (6 * V [2 ] + E [2 ]) / 8
439+ subd .vertex [key ]['x' ] = x
440+ subd .vertex [key ]['y' ] = y
441+ subd .vertex [key ]['z' ] = z
442+ else :
443+ pass
413444
414445 mesh = subd
415446
@@ -746,23 +777,3 @@ def trimesh_subdivide_loop(mesh, k=1, fixed=None):
746777 from compas .datastructures import Mesh # noqa: F401
747778 from compas .geometry import Box # noqa: F401
748779 doctest .testmod (globs = globals ())
749-
750- # from compas.datastructures import Mesh
751- # from compas.geometry import Box
752- # from compas.utilities import print_profile
753- # from compas_viewers.multimeshviewer import MultiMeshViewer
754-
755- # subdivide = print_profile(mesh_subdivide_quad)
756-
757- # box = Box.from_width_height_depth(10.0, 10.0, 10.0)
758- # mesh = Mesh.from_shape(box)
759- # mesh.default_face_attributes.update(path=[])
760- # subd = subdivide(mesh, k=3)
761-
762- # print(subd.face_attribute(subd.get_any_face(), 'path'))
763-
764- # # print(mesh.number_of_faces())
765-
766- # viewer = MultiMeshViewer()
767- # viewer.meshes = [subd]
768- # viewer.show()
0 commit comments