From ba1ec89c2799e57dee5069366516c35d3411b313 Mon Sep 17 00:00:00 2001 From: "Stephen M. McQuay" Date: Wed, 9 May 2012 00:32:34 -0600 Subject: [PATCH] In the middle of implementing Catmull-Clark --- surf/__init__.py | 3 +- surf/geometry.py | 60 ++---- surf/subd/cc.py | 364 +++++++++---------------------------- surf/test/__init__.py | 2 + surf/test/edge.py | 22 ++- surf/test/polymesh.py | 6 +- surf/test/subd/__init__.py | 5 + surf/test/subd/cc.py | 24 +++ 8 files changed, 142 insertions(+), 344 deletions(-) create mode 100644 surf/test/subd/__init__.py create mode 100644 surf/test/subd/cc.py diff --git a/surf/__init__.py b/surf/__init__.py index ccb3543..67ffd7c 100644 --- a/surf/__init__.py +++ b/surf/__init__.py @@ -1,7 +1,6 @@ -from .geometry import Vertex, Edge, PolygonMesh +from .geometry import Vertex, PolygonMesh __all__ = [ Vertex.__name__, - Edge.__name__, PolygonMesh.__name__, ] diff --git a/surf/geometry.py b/surf/geometry.py index 19356ad..20f721a 100644 --- a/surf/geometry.py +++ b/surf/geometry.py @@ -25,11 +25,10 @@ def cross(a, b): k = a.x * b.y - a.y * b.x return Vertex(i, j, k) - -def _centroid(verts): - xs = [vertex.x for vertex in verts] - ys = [vertex.y for vertex in verts] - zs = [vertex.z for vertex in verts] +def centroid(verts): + xs = [v.x for v in verts] + ys = [v.y for v in verts] + zs = [v.z for v in verts] # average each vertex component x = sum(xs) / len(xs) @@ -38,7 +37,6 @@ def _centroid(verts): return Vertex(x, y, z) - class Vertex(object): ''' A vertex is a position along with other information such as color, normal @@ -86,52 +84,12 @@ class Vertex(object): return Vertex(-self.x, -self.y, -self.z) def __unicode__(self): - return pprint.pformat([self.x, self.y, self.z]) + return u'V{0}'.format([self.x, self.y, self.z]) __str__ = __unicode__ __repr__ = __unicode__ -class Edge(object): - def __init__(self, v1, v2): - self.v1 = v1 - self.v2 = v2 - - @property - def centroid(self): - return _centroid([self.v1, self.v2]) - - def __unicode__(self): - return pprint.pformat((self.v1, self.v2)) - - __str__ = __unicode__ - __repr__ = __unicode__ - - -class Face(object): - ''' - A face is a closed set of edges, in which a triangle face has three edges, - and a quad face has four edges. Blender stores a face as a collection of - the verts that compose it, so we shall store them thusly as well. - - Blender historicaly only supported faces with an edge count of three or - four. We will cross the N-gon bridge in the future. - ''' - - def __init__(self, verts=None): - self.verts = verts or [] - - def __unicode__(self): - return pprint.pformat(self.verts) - - __str__ = __unicode__ - __repr__ = __unicode__ - - @property - def centroid(self): - return _centroid(self.verts) - - class PolygonMesh(object): ''' A polygon object is a collection of the following lists: @@ -146,7 +104,7 @@ class PolygonMesh(object): ''' def __init__(self, *args, **kwargs): - self.vertices = kwargs['vertices'] + self.vertices = [Vertex(*v) for v in kwargs['vertices']] self.edges = kwargs['edges'] self.faces = kwargs['faces'] @@ -168,7 +126,7 @@ class PolygonMesh(object): >>> mesh.faces_for_edge[0] [0, 1] >>> [self.face[i] for i in mesh.faces_for_edge[1]] - [, ] + [[...], [...], ...] where 0 and 1 are indices into the face list """ @@ -216,3 +174,7 @@ class PolygonMesh(object): __str__ = __unicode__ __repr__ = __unicode__ + + def centroid(self, vert_ids): + verts = [self.vertices[vid] for vid in vert_ids] + return centroid(verts) diff --git a/surf/subd/cc.py b/surf/subd/cc.py index d1e765d..aec6f93 100644 --- a/surf/subd/cc.py +++ b/surf/subd/cc.py @@ -1,287 +1,91 @@ -from surf.geometry import Vertex, Edge, PolygonMesh +from collections import defaultdict + +from surf.geometry import PolygonMesh, centroid + +from pprint import pprint as pp # XXX -def sub_edges(self): - temp_p = PolygonMesh() - temp_p.edges = [Edge(), Edge()] - # temp_p.vertices = - sub_edges[0].vertices = [self.vertices[0], self.edge_vertex] - sub_edges[1].vertices = [self.edge_vertex, self.vertices[1]] - return self.__sub_edges +def refine(mesh): + new_vertices = list(mesh.vertices) + f_vert_offset = len(new_vertices) + + edge_vids_for = defaultdict(list) + face_vids_for = defaultdict(list) + + new_edges = list(mesh.edges) + new_faces = [] + + # For each face, add a face point + for cur_face_id, cur_face in enumerate(mesh.faces): + # Set each face point to be the centroid of all original points for the + # respective face. + + # here we have already created a list that is a copy of the original + # verts. we append the new face points to that same list, and simply + # keep track of offsets into the new vert list ... see e_vert_offset + # below + new_face_point = mesh.centroid(cur_face) + new_vertices.append(new_face_point) + + e_vert_offset = len(new_vertices) + + # For each edge, add an edge point. + for cur_edge_id, cur_edge in enumerate(mesh.edges): + # make mapping from edge -> new_face_vert for later + face_ids_for_edge = mesh.faces_for_edge[cur_edge_id] + for fid in face_ids_for_edge: + edge_vids_for[fid].append(cur_edge_id) + + tmp_verts = [] + + # Set each edge point to be the average of the two neighbouring, (very + # recently calculated) face points ... + tmp_verts.extend([new_vertices[f + f_vert_offset] for f in face_ids_for_edge]) + + # ... and its two original endpoints. + tmp_verts.extend([mesh.vertices[vid] for vid in cur_edge]) + + # centroid == average + new_vertices.append(centroid(tmp_verts)) + + # For each face point, add an edge for every edge of the face, connecting + # the face point to each edge point for the face. + for trunc_vid in xrange(len(new_vertices[f_vert_offset:e_vert_offset])): + overall_vid = f_vert_offset + trunc_vid + for edge_vid in edge_vids_for[trunc_vid]: + new_edges.append([edge_vid, overall_vid]) + + # pp(new_edges) + + # For each original point P + assert f_vert_offset == len(mesh.vertices) + for new_vid in xrange(f_vert_offset): + # take the average F of all n face points for faces touching P ... + F = centroid([mesh.vertices[vid] for vid in mesh.faces_for_vert[new_vid]]) + print ">>>", F + + # and take the average R of all n edge midpoints for edges touching P + # wiki is wrong ... it should be the edge points, not midpoints ... + edges = [mesh.edges[eid] for eid in mesh.edges_for_vert[new_vid]] + e_verts = [] + for edge in edges: + e_verts.extend([mesh.vertices[vid] for vid in edge]) + + R = centroid(e_verts) + # where each edge midpoint is the average of its two endpoint vertices. + # Move each original point to the point (or add it to new_verts) + v = (F + 2 * R + (len(edges) - 3) * new_vertices[new_vid]) / len(edges) + + return None -def centroid(face, poly): - ''' - ''' - # gather all face vertex coords - face_vertices = face.vertices - - xs = [vertex.x for vertex in face_vertices] - ys = [vertex.y for vertex in face_vertices] - zs = [vertex.z for vertex in face_vertices] - - # average each vertex component - x = sum(xs) / len(xs) - y = sum(ys) / len(ys) - z = sum(zs) / len(zs) - - return Vertex(poly, x, y, z) +if __name__ == '__main__': + import json + from surf.geometry import PolygonMesh + from surf.subd.cc import refine -def edge_divide(edge, poly): - ''' - Set each edge vertices to be the average of the two neighboring - face vertices and its two original end vertices. - ''' - edge_ids = poly.edge_ids_with_parent(edge.id) - - if edge_ids: - return edge_ids - else: - # otherwise split it - xs = [] - ys = [] - zs = [] - for face in edge.faces: - centroid_v = centroid(face, None) - xs.append(centroid_v.x) - ys.append(centroid_v.y) - zs.append(centroid_v.z) - - for vertex in edge.vertices: - xs.append(vertex.x) - ys.append(vertex.y) - zs.append(vertex.z) - - x = sum(xs) / len(xs) - y = sum(ys) / len(ys) - z = sum(zs) / len(zs) - - e0 = Edge(poly) - e1 = Edge(poly) - edge_vertex = Vertex(poly, x, y, z) - - edge_vertex.edge_ids = [e0.id, e1.id] - - e0.vertex_ids = [edge.vertices[0].id, edge_vertex.id] - e1.vertex_ids = [edge_vertex.id, edge.vertices[1].id] - - e0.edge_ids = edge.winged_edges_at_vertex(0) - e0.edge_ids.append(e1.id) - - e1.edge_ids = edge.winged_edges_at_vertex(1) - e1.edge_ids.append(e0.id) - - e0.parent_id = edge.id - e1.parent_id = edge.id - - # add all these to the new polygon - poly.edge_ids.append(e0.id) - poly.edge_ids.append(e1.id) - poly.vertices.append(edge_vertex.id) - - return e0.id, e1.id, edge_vertex.id - - -def sub_faces(self): - setup_sub_divisions() - return sub_faces() - - -def interior_edges(self): - setup_sub_divisions() - return self.__interior_edges - - -def setup_sub_divisions(polygon, face): - ''' - v0 ev0 v1 - *------e0-----* - | | | - | | | - ev3 e|11----f5----e|1 ev1 - | | | - | | | - *------e2-----* - v3 ev2 v2 - ''' - - # create empty sub_faces that will be filled with edge references - # below - # these need to at least exist so the interior edges have - # something to reference - - # sub_faces = [Face(polygon) for edge in face.edge_ids] - - # set up empty edge objects to be filled below - # interior_edges = [Edge(polygon) for edge in face.edge_ids] - - # # each interior edge connects the exterior edge vertex (mid-point) - # # to the faceVertex (centroid) - # for edge_id in range(len(face.edges)): - # prevIndex = (edge_id - 1) % len(face.edges) - # nextIndex = (edge_id + 1) % len(face.edges) - - # # end vertices are face centroid and currEdge edge_vertex - # interior_edges[edge_id].vertices = [ - # face.edges[edge_id], - # edge_vertex, self.centroid - # ] - - # # wing edges are the current edge's sub_edges (ordered same as - # # vertex order) and the prev and next interior edges - # self.__interior_edges[index].edges = [ - # self.edges[index].sub_edges[0], - # self.edges[index].sub_edges[1], - # self.__interior_edges[prevIndex], - # self.__interior_edges[nextIndex] - # ] - - # # edge faces are the new sub_faces (current and next faces), the - # # current will be define below - # # and the next will be defined on the next iteration (or - # # already defined on the last iteration) - # self.__interior_edges[index].faces = [ - # self.__sub_faces[index], - # self.__sub_faces[nextIndex] - # ] - - # # now reference the current edge back into the faces, - # # and the edge.sub_edges, and the edge.edge_vertex - - # # current subFace (same index as current interior edge) - # # set its edges to reference the same edges used to setup the - # # interior edge - # # order will be pretty important on these steps... - # self.__sub_faces[index].edges = [ - # self.edges[index].sub_edges[0], - # self.__interior_edges[index], - # self.__interior_edges[prevIndex], - # self.edges[prevIndex].sub_edges[1] - # ] - - # # just set one of the vertex edges, the other belongs to - # # another face and will get added when that face is run - # self.edges[index].edge_vertex.edges.append( - # self.__interior_edges[index]) - - # self.edges[index].sub_edges[0].faces.append( - # self.__sub_faces[index]) - # self.edges[index].sub_edges[0].faces.append( - # self.__sub_faces[index]) - pass - - -def subdivide_face(poly, face): - # ''' - # ''' - - # # find face centroid - # fc = face.centroid - - # # find edge vertices - # for edge in face.edges: - # x, y, z = edge_mid_vertex(edge) - pass - - -def refine(poly): - ''' - For each face, add a face vertex - Set each face vertex to be the centroid of all original vertices for - the respective face. - For each edge, add an edge vertex. - Set each edge vertex to be the average of the two neighbouring face - vertices and its two original endvertices. - For each face vertex, add an edge for every edge of the face, connecting - the face vertex to each edge vertex for the face. - For each original vertex P, take the average F of all n face vertices for - faces touching P, and take the average R of all n edge midvertices for - edges touching P, where each edge midvertex is the average of its two - endvertex vertices. Move each original vertex to the vertex - ''' - - # create a new storage container for the items - new_poly = PolygonMesh() - - # for now just test with the first face - start_face = poly.faces[0] - - # go through the face vertices and add them to the new polygon - for vertex in start_face.vertices: - # truly, this needs to be a 'copy' of the vertex, I'll fix that later - new_poly.vertices.append(vertex) - - # find the face centroid - # and add the face centroid to the new polygon - start_centroid = centroid(start_face, new_poly) - new_poly.vertices.append(start_centroid) - - # for each edge on the face, - for edge in start_face.edges: - # divide that edge into two new edges with an edge vertex - # set their parent object as the original edge - new_e0_id, new_e1_id, edge_v_id = edge_divide(edge, new_poly) - - # create a new edge connecting the centroid to the edge_vertex - centroid_to_edge = Edge(new_poly) - new_poly.edges.append(centroid_to_edge) - - # set the new edge's vertex references - centroid_to_edge.vertex_ids = [edge_v_id, start_centroid.id] - - # set the new edge's winged_edge references - # centroid_to_edge.edge_ids = poly.edges ==> get edge by id not yet - # implemented... edge_v_id.edges - - # set the edge vertex edge references - edge_v_id.edges.append(centroid_to_edge.id) - - # set the centroid's edge reference - start_centroid.edge_ids.append(centroid_to_edge.id) - - # now walk through the edges connected to the centroid - start_centroid.edges[0] - # need to get an adjacent edge, based on the the shared vertex of the - # original polygon... centroid to edge_vertex to shared point... - - # start_face.neighbors - - # f = sum(list( - # set(face_vertices)), Vertex()) / len(list(set(face_vertices))) - # r = sum(list( - # set(edge_mid_points)), Vertex()) - # / len(list(set(edge_mid_points))) - # p = vertex - # n = len(vertex.edges) - # v = (f + 2.0 * r + (n - 3.0) * p) / n - # newVertices.append(v) - - # for vertex, newVertex in zip(poly.vertices, newVertices): - # vertex.x = newVertex.x - # vertex.y = newVertex.y - # vertex.z = newVertex.z - # # so now what......... - # # (F + 2R + (n-3) P) / n - # # - # # F = average of all face vertices touching P - # # R = average of all edge vertices touching P - # # P original point - # # n = number of edges connecting to P - - # p.faces = faces - # p.vertices = vertices - # p.edges = edges - - # # plotting these in excel seems to show the correct values (at first - # # glace...) - - # # so now what......... - # # (F + 2R + (n-3) P) / n - # # - # # F = average of all face vertices touching P - # # R = average of all edge vertices touching P - # # P original point - # # n = face vertices or edge vertices (should be the same number) - # return PolygonMesh(vertices, edges, faces) + cube = json.load(open('blender/samples/cube.json', 'r')) + p = PolygonMesh(**cube) + q = refine(p) diff --git a/surf/test/__init__.py b/surf/test/__init__.py index e468ca8..7515f7a 100644 --- a/surf/test/__init__.py +++ b/surf/test/__init__.py @@ -1,6 +1,7 @@ from .vertex import TestVertex from .edge import TestEdge from .polymesh import TestPM +from .subd.cc import TestCC # I only use the convoluted Class.__name__ to pass pyflakes (it complains about @@ -10,4 +11,5 @@ __all__ = [ TestVertex.__name__, TestEdge.__name__, TestPM.__name__, + TestCC.__name__, ] diff --git a/surf/test/edge.py b/surf/test/edge.py index d4b5069..c4aecba 100644 --- a/surf/test/edge.py +++ b/surf/test/edge.py @@ -1,22 +1,24 @@ +import json +import os import unittest -from surf.geometry import Vertex, Edge +from surf.geometry import Vertex, PolygonMesh class TestEdge(unittest.TestCase): def setUp(self): - self.origin = Vertex(0, 0, 0) - self.v1 = Vertex(-1, -1, -1) - self.v2 = Vertex(1, 1, 1) - - self.v3 = Vertex(5, 4, 3) - self.v4 = Vertex(10, -2, 13) - self.v5 = Vertex(-4, 15.3, 100) + path, file_name = os.path.split(__file__) + self.samples_dir = os.path.join(path, os.pardir, os.pardir, + 'blender', 'samples') + self.cube_file_name = os.path.join(self.samples_dir, 'cube.json') + cube_json = json.load(open(self.cube_file_name, 'r')) + self.cube = PolygonMesh(**cube_json) def test_centroid(self): - e = Edge(self.v1, self.v3) - self.assertEqual(e.centroid, Vertex(2, 1.5, 1)) + e = self.cube.edges[0] + v = self.cube.centroid(e) + self.assertEqual(v, Vertex(-1, 0, 1)) if __name__ == '__main__': diff --git a/surf/test/polymesh.py b/surf/test/polymesh.py index 94ea247..8a319fb 100644 --- a/surf/test/polymesh.py +++ b/surf/test/polymesh.py @@ -17,9 +17,9 @@ class TestPM(unittest.TestCase): def test_cube_load(self): p = PolygonMesh(**self.cube) v = p.vertices[0] - self.assertAlmostEqual(v[0], -1.0) - self.assertAlmostEqual(v[1], -1.0) - self.assertAlmostEqual(v[2], -1.0) + self.assertAlmostEqual(v.x, -1.0) + self.assertAlmostEqual(v.y, -1.0) + self.assertAlmostEqual(v.z, -1.0) if __name__ == '__main__': diff --git a/surf/test/subd/__init__.py b/surf/test/subd/__init__.py new file mode 100644 index 0000000..574f764 --- /dev/null +++ b/surf/test/subd/__init__.py @@ -0,0 +1,5 @@ +from .cc import TestCC + +__all__ = [ + TestCC.__name__, +] diff --git a/surf/test/subd/cc.py b/surf/test/subd/cc.py new file mode 100644 index 0000000..1b51ecc --- /dev/null +++ b/surf/test/subd/cc.py @@ -0,0 +1,24 @@ +import json +import os +import unittest + +from surf.geometry import PolygonMesh +from surf.subd import cc + + +class TestCC(unittest.TestCase): + + def setUp(self): + path, file_name = os.path.split(__file__) + self.samples_dir = os.path.join(path, os.pardir, os.pardir, os.pardir, + 'blender', 'samples') + self.cube_file_name = os.path.join(self.samples_dir, 'cube.json') + self.cube = json.load(open(self.cube_file_name, 'r')) + + def test_refine(self): + p = PolygonMesh(**self.cube) + p2 = cc.refine(p) + print p2 + +if __name__ == '__main__': + unittest.main(verbosity=3)