From 540fa8b70283f57012dd79a171b80bbf2e5cca7a Mon Sep 17 00:00:00 2001 From: William Blattman Date: Sat, 17 Mar 2012 16:19:07 -0700 Subject: [PATCH 1/3] some inital attempts --- geometry.py | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100755 geometry.py diff --git a/geometry.py b/geometry.py new file mode 100755 index 0000000..bbeaa95 --- /dev/null +++ b/geometry.py @@ -0,0 +1,293 @@ +# import scipy + +''' +http://en.wikipedia.org/wiki/Polygon_mesh + +Polygon meshes may be represented in a variety of ways, using different methods to store the vertex, edge and face data. These include: + Face-vertex meshes: A simple list of vertices, and a set of polygons that vertex to the vertices it uses. + Winged-edge meshes, in which each edge vertices to two vertices, two faces, and the four (clockwise and counterclockwise) edges that touch it. Winged-edge meshes allow constant time traversal of the surface, but with higher storage requirements. + Half-edge meshes: Similar to winged-edge meshes except that only half the edge traversal information is used. + Quad-edge meshes, which store edges, half-edges, and vertices without any reference to polygons. The polygons are implicit in the representation, and may be found by traversing the structure. Memory requirements are similar to half-edge meshes. + Corner-tables, which store vertices in a predefined table, such that traversing the table implicitly defines polygons. This is in essence the "triangle fan" used in hardware graphics rendering. The representation is more compact, and more efficient to retrieve polygons, but operations to change polygons are slow. Furthermore, corner-tables do not represent meshes completely. Multiple corner-tables (triangle fans) are needed to represent most meshes. + Vertex-vertex meshes: A "VV" mesh represents only vertices, which vertex to other vertices. Both the edge and face information is implicit in the representation. However, the simplicity of the representation allows for many efficient operations to be performed on meshes. + + + Face-vertex meshes represent an object as a set of faces and a set of vertices. This is the most widely used mesh representation, being the input typically accepted by modern graphics hardware. + v4 v5 + *-----e8-----* + | | + | | + e|4 f4 e|5 + | | + v4 v|0 v|1 v5 + *-----e4-----*-----e0-----*-----e5-----* + | | | | + | | | | + e11| f3 e|3 f0 e|1 f1 e|9 + | | | | + | | | | + *-----e7-----*-----e2-----*-----e6-----* + v7 v|3 v|2 v6 + | | + e|7 f2 e|6 + | | + v|7 v|6 + *-----e10----* + | | + | | + e|11 f5 e|9 + | | + | | + *-----e8-----* + v4 v5 + v0 - <0,1,0> + v1 - <1,1,0> + v2 - <1,0,0> + v3 - <0,0,0> + v4 - <0,1,1> + v5 - <1,1,1> + v6 - <1,0,1> + v7 - <0,0,1> + + face list + f0 - e0, e1, e2, e3 + f1 - e1, e5, e9, e6 + f2 - e2, e6, e10, e7 + f3 - e4, e3, e7, e11 + f4 - e8, e5, e0, e4 + f5 - e10, e9, e8, e11 + + winged edges ordered by face, then by vertex reference + edge list + e0 - v0, v1; f0, f4; e3, e1, e4, e5 + e1 - v1, v2; f0, f1; e0, e2, e5, e6 + e2 - v3, v2; f0, f2; e3, e7, e1, e6 + e3 - v0, v3; f3, f0; e4, e7, e0, e2 + e4 - v4, v0; f3, f4; e11, e3, e0 e8 + e5 - v5, v1; f4, f1; e8, e0, e9, e1 + e6 - v2, v6; f1, f2; e1, e9, e2, e10 + e7 - v7, v3; f3, f2; e11, e3, e10, e2 + e8 - v4, v5, f4, f5; e4, e5, e11, e9 + e9 - v5, v6; f1, f5; e5, e6, e8, e10 + e10 - v7, v6; f2, f5; e7, e6, e11, e9 + e11 - v4, v7; f3, f5; e4, e7, e8, 10 + + vertex list + v0 - e0, e3, e4 + v1 - e0, e5, e1 + v2 - e1, e6, e2 + v3 - e2, e7, e3 + v4 - e4, e11, e8 + v5 - e5, e9, e8 + v6 - e2, e9, e10 + v7 - e7, e10, e11 + +''' + +class Vertex(object): + ''' + A vertex is a position along with other information such as color, normal vector and texture coordinates. + ''' + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + self.edges = [] + + def __repr__(self): + return "<%.2f, %.2f, %.2f>, faces: %s" % (self.x, self.y, self.z, [edge.id for edge in self.edges]) + +class Edge(object): + ''' + ''' + def __init__(self): + self.id = 0 + self.vertices = [] + self.faces = [] + self.edges = [] + + def createEdgeVertex(self): + ''' + Set each edge vertices to be the average of the two neighboring + face vertices and its two original end vertices. + ''' + + # two neighboring face vertices: + neighborVertices = [face.centroid for face in self.faces] + + vertices = list(self.vertices) + vertices.extend(neighborVertices) + return self.__averageVertices(vertices) + + def __averageVertices(self, vertices): + xs = [vertex.x for vertex in vertices] + ys = [vertex.y for vertex in vertices] + zs = [vertex.z for vertex in vertices] + x = sum(xs)/len(xs) + y = sum(ys)/len(ys) + z = sum(zs)/len(zs) + return Vertex(x, y, z) + +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. + ''' + def __init__(self): + self.edges = [] + + def __getCentroid(self): + # gather all vertex coords + faceVertices = self.__getFaceVertices() + xs = [vertex.x for vertex in faceVertices] + ys = [vertex.y for vertex in faceVertices] + zs = [vertex.z for vertex in faceVertices] + + # average each vertex component + x = sum(xs) / len(xs) + y = sum(ys) / len(ys) + z = sum(zs) / len(zs) + + return Vertex(x, y, z) + centroid = property(fget=__getCentroid) + + def __getFaceVertices(self): + return list(set([vertex for edge in self.edges for vertex in edge.vertices])) + +class Polygon(object): + ''' + Face splitting should happend on the polygon level(?). It doesn't make sense to split just one face since + it needs to average vertices with all adjoinging faces + ''' + + def __init__(self, vertices, edges, faces): + self.vertices = vertices + self.edges = edges + self.faces = faces + + def catmullClarkRefine(self): + ''' + 1. For each face, add a face vertex + Set each face vertex to be the centroid of all original vertices for the respective face. + 2. 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. + 3. For each face vertex, add an edge for every edge of the face, connecting the face vertex to each edge vertex for the face. + 4. 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 + ''' + + # 1 + faceVertex = self.faces[0].centroid + + # 2 + edgeVertices = [edge.createEdgeVertex() for edge in self.faces[0].edges] + + # 3 + + # 4 + # (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) + +v = [] +v.append(Vertex(0.0, 1.0, 0.0)) +v.append(Vertex(1.0, 1.0, 0.0)) +v.append(Vertex(1.0, 0.0, 0.0)) +v.append(Vertex(0.0, 0.0, 0.0)) +v.append(Vertex(0.0, 1.0, 1.0)) +v.append(Vertex(1.0, 1.0, 1.0)) +v.append(Vertex(1.0, 0.0, 1.0)) +v.append(Vertex(0.0, 0.0, 1.0)) + +e = [] +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) +e.append(Edge()) + +f = [] +f.append(Face()) +f.append(Face()) +f.append(Face()) +f.append(Face()) +f.append(Face()) +f.append(Face()) + +# vertex list +v[0].edges = [e[0], e[3], e[4]] +v[1].edges = [e[0], e[5], e[1]] +v[2].edges = [e[1], e[6], e[2]] +v[3].edges = [e[2], e[7], e[3]] +v[4].edges = [e[4], e[11], e[8]] +v[5].edges = [e[5], e[9], e[8]] +v[6].edges = [e[2], e[9], e[10]] +v[7].edges = [e[7], e[10], e[11]] + +# face list +f[0].edges = [e[0], e[1], e[2], e[3]] +f[1].edges = [e[1], e[5], e[9], e[6]] +f[2].edges = [e[2], e[6], e[10], e[7]] +f[3].edges = [e[4], e[3], e[7], e[11]] +f[4].edges = [e[8], e[5], e[0], e[4]] +f[5].edges = [e[10], e[9], e[8], e[11]] + +#winged edges ordered by face, then by vertex reference +e[0].vertices, e[0].faces, e[0].edges = [v[0], v[1]], [f[0], f[4]], [e[3], e[1], e[4], e[5]] +e[1].vertices, e[1].faces, e[1].edges = [v[1], v[2]], [f[0], f[1]], [e[0], e[2], e[5], e[6]] +e[2].vertices, e[2].faces, e[2].edges = [v[3], v[2]], [f[0], f[2]], [e[3], e[7], e[1], e[6]] +e[3].vertices, e[3].faces, e[3].edges = [v[0], v[3]], [f[3], f[0]], [e[4], e[7], e[0], e[2]] +e[4].vertices, e[4].faces, e[4].edges = [v[4], v[0]], [f[3], f[4]], [e[11], e[3], e[0], e[8]] +e[5].vertices, e[5].faces, e[5].edges = [v[5], v[1]], [f[4], f[1]], [e[8], e[0], e[9], e[1]] +e[6].vertices, e[6].faces, e[6].edges = [v[2], v[6]], [f[1], f[2]], [e[1], e[9], e[2], e[10]] +e[7].vertices, e[7].faces, e[7].edges = [v[7], v[3]], [f[3], f[2]], [e[11], e[3], e[10], e[2]] +e[8].vertices, e[8].faces, e[8].edges = [v[4], v[5]], [f[4], f[5]], [e[4], e[5], e[11], e[9]] +e[9].vertices, e[9].faces, e[9].edges = [v[5], v[6]], [f[1], f[5]], [e[5], e[6], e[8], e[10]] +e[10].vertices, e[10].faces, e[10].edges = [v[7], v[6]], [f[2], f[5]], [e[7], e[6], e[11], e[9]] +e[11].vertices, e[11].faces, e[11].edges = [v[4], v[7]], [f[3], f[5]], [e[4], e[7], e[8], e[10]] + +# just to prove to myself that the objects are the same, this is what years of pass by value have done to me... + +# print id(v[0].x) +# print id(e[0].vertices[0].x) +# print id(f[0].edges[0].vertices[0].x) + +# v[0].x = 9 + +# print id(v[0].x) +# print id(e[0].vertices[0].x) +# print id(f[0].edges[0].vertices[0].x) + +# print v[0].x +# print e[0].vertices[0].x +# print f[0].edges[0].vertices[0].x + +polygon = Polygon(v, e, f) +polygon.catmullClarkRefine() + + + +# from numpy import * +# import pylab +# import mpl_toolkits.mplot3d.axes3d as p3 + +# fig = pylab.figure() +# ax = p3.Axes3D(fig) +# ax.plot_wireframe(xs, ys, zs) +# ax.set_xlabel('X') +# ax.set_ylabel('Y') +# ax.set_zlabel('Z') +# pylab.show() + +# v4 = Vertex(4, 0, 0, 1) +# v5 = Vertex(5, 1, 0, 1) +# v6 = Vertex(6, 0, 1, 1) +# v7 = Vertex(7, 1, 1, 1) From f9179f972cf5d62c164041f3147046f213637151 Mon Sep 17 00:00:00 2001 From: William Blattman Date: Sat, 17 Mar 2012 23:14:27 -0700 Subject: [PATCH 2/3] some more changes to the geometry tests, and some notes on what todo --- howTheHeck.pdf | Bin 0 -> 38407 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 howTheHeck.pdf diff --git a/howTheHeck.pdf b/howTheHeck.pdf new file mode 100755 index 0000000000000000000000000000000000000000..c87a4f1b658aa148d08a8f9de8ab8419ffdd542d GIT binary patch literal 38407 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

nZ`|FoKV!u%(4sp9Ua=CMiV*Fw=f)y(t` z2KN&6x>s{1$)SzJY@R08cNg)dhCxMs`HwaRso$i2d1>^GCZyWkMy^`!UK}sIn{7>U zD6Br1{}P{wjCt3BrPJE8<_HG{!RR^gzwE#D@1P@E#LQa6ob1dx#Kf$?upeA3zytuG z1q`ym_45C6#QvB4FUvp9vvB{*la);i7%KY3isz%Kmq3S5`W}> z{{Gn}e_3AY_j3I5{y!f7nflK&FLhx4SNpuAfc5%EOZ?{*|CxSisekzWBlVx>{*nGC z{l8k_uXj+b#tf>>fO-F4+v~5c^VfEG0Pa`ewLhqsm@=tac)moE;bzhRHYPC}F|hM6 zso2>$0}o!}w)`W8%ga`8z_gea9}6ps2sgVhvoNOwH}JC%6P93S6Xjq7F8y!{vxss? z2>jnYUc!L?ulozL0se~a{XZM?u}_JsPAg+WuX(kg>0)%N%OIdwcOo4!%BfXr)o2Mb zSL1aei8V!!G}}MG*y`LXcX#ZLyE$ZWv+fSvYdsp)nDg*%j;EqWJ{A&oK%6lJB%B0v zHwhgS3u)RAx5EtwM5PJUl6?s;uL?LM3Rubx_(ra&uk6dd@CMQ*psQ8LfmA7QVuo5h zp^o|*uIdbG|If{q&1LT4#j_SZjZ_Z2N^B^2-w$5$@psE#!s(qQk&W}I9LM0>$Dw`3 zksp<#0?Z{D&%eY#U>KrAFk_1{WBg=Bq;QW}rxwSN&?lELVC&JR?=j$`Rf-Nz@$XLw zE=dV|lM;ezYq^K>{t0Vq{&er||H(bL*Sr(GJ`Yjx41wvC&obSy zLH&$(Y!1adg6B7~V3ZLe$XUtV(Zm!H4%ji7nSpQre6eu8j96B7;+IqZe7yvSeR&bv z{@Vt4*-id$HkOxYHh;6Ru>xb*{>{em(!2j=<7NjA(Eqjp*w|l{0pMVHbuEDFWpMxP zT5cBLF!*m93xEwckpE`me2EJ1HybzCD?e6F;Pm%5KQ?BzSLJd7UgyUN-~>)a|6K+r za25Nt4LHsI-H-hxDEQy5W#@YB$HfYq0{+{Ni<#|Jowa2o6>d9Kinaij55f2O9_u zVBdIsEeHc{!1P4$<|LzA2=m)~~nghW6nghW6 znge*t2qM4N96+@I;rE&Y!2Fs6zzl)|0D=Pmf&-|Hui5|rf&&180|0^psL8MM1Hl1& z40-Jbf&&;F@s%Gi%;9Sr2o7Kjz1MyqIDq$Iul+!9fVi&%fY5UQ2t5Y|Q+Ztm@IK;I zxvU^KfN@M-`+?v9-f_P61Hl1&6G0EC_cKOhnMf&+w} z13>6G0EC_cce{UG1_(U|fYS3 te=Q#X7Z3iwRE`N4AIz2buR1Zktd0SLV-d5m0vqk`wdMkDbM? Date: Sat, 17 Mar 2012 23:18:32 -0700 Subject: [PATCH 3/3] some more changes to the geometry tests, and some notes on what todo --- geometry.py | 54 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/geometry.py b/geometry.py index bbeaa95..0bfb70a 100755 --- a/geometry.py +++ b/geometry.py @@ -95,18 +95,23 @@ class Vertex(object): self.edges = [] def __repr__(self): - return "<%.2f, %.2f, %.2f>, faces: %s" % (self.x, self.y, self.z, [edge.id for edge in self.edges]) + return "<%.2f, %.2f, %.2f>" % (self.x, self.y, self.z) class Edge(object): ''' ''' def __init__(self): - self.id = 0 self.vertices = [] self.faces = [] self.edges = [] - def createEdgeVertex(self): + def neighborFace(self, neighborFace): + if neighborFace == self.faces[0]: + return self.faces[1] + else: + return self.faces[0] + + def edgeVertex(self): ''' Set each edge vertices to be the average of the two neighboring face vertices and its two original end vertices. @@ -134,7 +139,7 @@ class Face(object): ''' def __init__(self): self.edges = [] - + def __getCentroid(self): # gather all vertex coords faceVertices = self.__getFaceVertices() @@ -151,8 +156,9 @@ class Face(object): centroid = property(fget=__getCentroid) def __getFaceVertices(self): - return list(set([vertex for edge in self.edges for vertex in edge.vertices])) - + return list([vertex for edge in self.edges for vertex in edge.vertices]) + vertices = property(fget=__getFaceVertices) + class Polygon(object): ''' Face splitting should happend on the polygon level(?). It doesn't make sense to split just one face since @@ -164,31 +170,41 @@ class Polygon(object): self.edges = edges self.faces = faces + def __getFaceNeighbors(self, testFace): + for face in self.faces: + testFace.isNeighbor(face) + def catmullClarkRefine(self): ''' - 1. For each face, add a face vertex + For each face, add a face vertex Set each face vertex to be the centroid of all original vertices for the respective face. - 2. For each edge, add an edge vertex. + 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. - 3. For each face vertex, add an edge for every edge of the face, connecting the face vertex to each edge vertex for the face. - 4. 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 + 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 ''' - # 1 - faceVertex = self.faces[0].centroid + # this calculates and returns the averaged vertex point + print self.faces[0].edges[0].edgeVertex() - # 2 - edgeVertices = [edge.createEdgeVertex() for edge in self.faces[0].edges] + # this returns a vertex at the face centroid + print self.faces[0].centroid - # 3 + # this will get to the first neighborFace... + # each face knows its faceVertex, and all of its edgeVertices + neighborFace = self.faces[0].edges[0].neighborFace(self.faces[0]) + + print neighborFace.centroid + + # so now what......... - # 4 # (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) + v = [] v.append(Vertex(0.0, 1.0, 0.0)) @@ -243,9 +259,9 @@ f[5].edges = [e[10], e[9], e[8], e[11]] #winged edges ordered by face, then by vertex reference e[0].vertices, e[0].faces, e[0].edges = [v[0], v[1]], [f[0], f[4]], [e[3], e[1], e[4], e[5]] e[1].vertices, e[1].faces, e[1].edges = [v[1], v[2]], [f[0], f[1]], [e[0], e[2], e[5], e[6]] -e[2].vertices, e[2].faces, e[2].edges = [v[3], v[2]], [f[0], f[2]], [e[3], e[7], e[1], e[6]] -e[3].vertices, e[3].faces, e[3].edges = [v[0], v[3]], [f[3], f[0]], [e[4], e[7], e[0], e[2]] -e[4].vertices, e[4].faces, e[4].edges = [v[4], v[0]], [f[3], f[4]], [e[11], e[3], e[0], e[8]] +e[2].vertices, e[2].faces, e[2].edges = [v[2], v[3]], [f[0], f[2]], [e[3], e[7], e[1], e[6]] +e[3].vertices, e[3].faces, e[3].edges = [v[3], v[0]], [f[3], f[0]], [e[4], e[7], e[0], e[2]] +e[4].vertices, e[4].faces, e[4].edges = [v[0], v[4]], [f[3], f[4]], [e[11], e[3], e[0], e[8]] e[5].vertices, e[5].faces, e[5].edges = [v[5], v[1]], [f[4], f[1]], [e[8], e[0], e[9], e[1]] e[6].vertices, e[6].faces, e[6].edges = [v[2], v[6]], [f[1], f[2]], [e[1], e[9], e[2], e[10]] e[7].vertices, e[7].faces, e[7].edges = [v[7], v[3]], [f[3], f[2]], [e[11], e[3], e[10], e[2]]