In the middle of implementing Catmull-Clark

This commit is contained in:
Stephen M. McQuay 2012-05-09 00:32:34 -06:00
parent 8b540012c1
commit ba1ec89c27
8 changed files with 142 additions and 344 deletions

View File

@ -1,7 +1,6 @@
from .geometry import Vertex, Edge, PolygonMesh from .geometry import Vertex, PolygonMesh
__all__ = [ __all__ = [
Vertex.__name__, Vertex.__name__,
Edge.__name__,
PolygonMesh.__name__, PolygonMesh.__name__,
] ]

View File

@ -25,11 +25,10 @@ def cross(a, b):
k = a.x * b.y - a.y * b.x k = a.x * b.y - a.y * b.x
return Vertex(i, j, k) return Vertex(i, j, k)
def centroid(verts):
def _centroid(verts): xs = [v.x for v in verts]
xs = [vertex.x for vertex in verts] ys = [v.y for v in verts]
ys = [vertex.y for vertex in verts] zs = [v.z for v in verts]
zs = [vertex.z for vertex in verts]
# average each vertex component # average each vertex component
x = sum(xs) / len(xs) x = sum(xs) / len(xs)
@ -38,7 +37,6 @@ def _centroid(verts):
return Vertex(x, y, z) return Vertex(x, y, z)
class Vertex(object): class Vertex(object):
''' '''
A vertex is a position along with other information such as color, normal 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) return Vertex(-self.x, -self.y, -self.z)
def __unicode__(self): 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__ __str__ = __unicode__
__repr__ = __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): class PolygonMesh(object):
''' '''
A polygon object is a collection of the following lists: A polygon object is a collection of the following lists:
@ -146,7 +104,7 @@ class PolygonMesh(object):
''' '''
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.vertices = kwargs['vertices'] self.vertices = [Vertex(*v) for v in kwargs['vertices']]
self.edges = kwargs['edges'] self.edges = kwargs['edges']
self.faces = kwargs['faces'] self.faces = kwargs['faces']
@ -168,7 +126,7 @@ class PolygonMesh(object):
>>> mesh.faces_for_edge[0] >>> mesh.faces_for_edge[0]
[0, 1] [0, 1]
>>> [self.face[i] for i in mesh.faces_for_edge[1]] >>> [self.face[i] for i in mesh.faces_for_edge[1]]
[<Face 0>, <Face 1>] [[...], [...], ...]
where 0 and 1 are indices into the face list where 0 and 1 are indices into the face list
""" """
@ -216,3 +174,7 @@ class PolygonMesh(object):
__str__ = __unicode__ __str__ = __unicode__
__repr__ = __unicode__ __repr__ = __unicode__
def centroid(self, vert_ids):
verts = [self.vertices[vid] for vid in vert_ids]
return centroid(verts)

View File

@ -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): def refine(mesh):
temp_p = PolygonMesh() new_vertices = list(mesh.vertices)
temp_p.edges = [Edge(), Edge()] f_vert_offset = len(new_vertices)
# 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
edge_vids_for = defaultdict(list)
face_vids_for = defaultdict(list)
def centroid(face, poly): new_edges = list(mesh.edges)
''' new_faces = []
'''
# gather all face vertex coords # For each face, add a face point
face_vertices = face.vertices 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.
xs = [vertex.x for vertex in face_vertices] # here we have already created a list that is a copy of the original
ys = [vertex.y for vertex in face_vertices] # verts. we append the new face points to that same list, and simply
zs = [vertex.z for vertex in face_vertices] # keep track of offsets into the new vert list ... see e_vert_offset
# 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)
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 # below
# these need to at least exist so the interior edges have new_face_point = mesh.centroid(cur_face)
# something to reference new_vertices.append(new_face_point)
# sub_faces = [Face(polygon) for edge in face.edge_ids] e_vert_offset = len(new_vertices)
# set up empty edge objects to be filled below # For each edge, add an edge point.
# interior_edges = [Edge(polygon) for edge in face.edge_ids] 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)
# # each interior edge connects the exterior edge vertex (mid-point) tmp_verts = []
# # 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 # Set each edge point to be the average of the two neighbouring, (very
# interior_edges[edge_id].vertices = [ # recently calculated) face points ...
# face.edges[edge_id], tmp_verts.extend([new_vertices[f + f_vert_offset] for f in face_ids_for_edge])
# edge_vertex, self.centroid
# ]
# # wing edges are the current edge's sub_edges (ordered same as # ... and its two original endpoints.
# # vertex order) and the prev and next interior edges tmp_verts.extend([mesh.vertices[vid] for vid in cur_edge])
# 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 # centroid == average
# # current will be define below new_vertices.append(centroid(tmp_verts))
# # 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, # For each face point, add an edge for every edge of the face, connecting
# # and the edge.sub_edges, and the edge.edge_vertex # 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])
# # current subFace (same index as current interior edge) # pp(new_edges)
# # 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 # For each original point P
# # another face and will get added when that face is run assert f_vert_offset == len(mesh.vertices)
# self.edges[index].edge_vertex.edges.append( for new_vid in xrange(f_vert_offset):
# self.__interior_edges[index]) # 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
# self.edges[index].sub_edges[0].faces.append( # and take the average R of all n edge midpoints for edges touching P
# self.__sub_faces[index]) # wiki is wrong ... it should be the edge points, not midpoints ...
# self.edges[index].sub_edges[0].faces.append( edges = [mesh.edges[eid] for eid in mesh.edges_for_vert[new_vid]]
# self.__sub_faces[index]) e_verts = []
pass 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 subdivide_face(poly, face):
# '''
# '''
# # find face centroid if __name__ == '__main__':
# fc = face.centroid import json
from surf.geometry import PolygonMesh
# # find edge vertices from surf.subd.cc import refine
# for edge in face.edges:
# x, y, z = edge_mid_vertex(edge)
pass
def refine(poly): cube = json.load(open('blender/samples/cube.json', 'r'))
''' p = PolygonMesh(**cube)
For each face, add a face vertex q = refine(p)
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)

View File

@ -1,6 +1,7 @@
from .vertex import TestVertex from .vertex import TestVertex
from .edge import TestEdge from .edge import TestEdge
from .polymesh import TestPM from .polymesh import TestPM
from .subd.cc import TestCC
# I only use the convoluted Class.__name__ to pass pyflakes (it complains about # I only use the convoluted Class.__name__ to pass pyflakes (it complains about
@ -10,4 +11,5 @@ __all__ = [
TestVertex.__name__, TestVertex.__name__,
TestEdge.__name__, TestEdge.__name__,
TestPM.__name__, TestPM.__name__,
TestCC.__name__,
] ]

View File

@ -1,22 +1,24 @@
import json
import os
import unittest import unittest
from surf.geometry import Vertex, Edge from surf.geometry import Vertex, PolygonMesh
class TestEdge(unittest.TestCase): class TestEdge(unittest.TestCase):
def setUp(self): def setUp(self):
self.origin = Vertex(0, 0, 0) path, file_name = os.path.split(__file__)
self.v1 = Vertex(-1, -1, -1) self.samples_dir = os.path.join(path, os.pardir, os.pardir,
self.v2 = Vertex(1, 1, 1) 'blender', 'samples')
self.cube_file_name = os.path.join(self.samples_dir, 'cube.json')
self.v3 = Vertex(5, 4, 3) cube_json = json.load(open(self.cube_file_name, 'r'))
self.v4 = Vertex(10, -2, 13) self.cube = PolygonMesh(**cube_json)
self.v5 = Vertex(-4, 15.3, 100)
def test_centroid(self): def test_centroid(self):
e = Edge(self.v1, self.v3) e = self.cube.edges[0]
self.assertEqual(e.centroid, Vertex(2, 1.5, 1)) v = self.cube.centroid(e)
self.assertEqual(v, Vertex(-1, 0, 1))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -17,9 +17,9 @@ class TestPM(unittest.TestCase):
def test_cube_load(self): def test_cube_load(self):
p = PolygonMesh(**self.cube) p = PolygonMesh(**self.cube)
v = p.vertices[0] v = p.vertices[0]
self.assertAlmostEqual(v[0], -1.0) self.assertAlmostEqual(v.x, -1.0)
self.assertAlmostEqual(v[1], -1.0) self.assertAlmostEqual(v.y, -1.0)
self.assertAlmostEqual(v[2], -1.0) self.assertAlmostEqual(v.z, -1.0)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1,5 @@
from .cc import TestCC
__all__ = [
TestCC.__name__,
]

24
surf/test/subd/cc.py Normal file
View File

@ -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)