class Geometry: def __init__(self, name, format = GeomVertexFormat.getV3()): self.views = [] self.vertexBuffer = GeomVertexData(name + "-vdata", format, GeomEnums.UHDynamic) self.np = NodePath(name) self.np.node().setBounds(OmniBoundingVolume()) self.np.node().setFinal(1) # taha was here def generateIndices(self, firstVertex = 0, numVerts = None): if numVerts is None: numVerts = self.vertexBuffer.getNumRows() clear = True else: clear = False for view in self.views: view.generateIndices(firstVertex, numVerts, clear) def generateGeometry(self): self.generateVertices() self.generateIndices() def generateVertices(self): pass def cleanup(self): for view in self.views: view.cleanup() self.views = None self.np.removeNode() self.np = None self.vertexBuffer = None def addView(self, view): self.views.append(view) return view def removeView(self, view): if isinstance(view, int): viewObj = self.views[view] else: viewObj = view viewObj.cleanup() self.views.remove(viewObj)
def makeGrid(rng = 1000, spacing = 10): #FIXME make this scale based on zoom??? ctup = (.3,.3,.3,1) xs = range(-rng,rng+1,spacing) ys = xs fmt = GeomVertexFormat.getV3c4() #3 component vertex, w/ 4 comp color #fmt = GeomVertexFormat.getV3() #3 component vertex, w/ 4 comp color vertexData = GeomVertexData('points', fmt, Geom.UHStatic) verts = GeomVertexWriter(vertexData, 'vertex') color = GeomVertexWriter(vertexData, 'color') for i,d in enumerate(xs): switch1 = (-1) ** i * rng switch2 = (-1) ** i * -rng #print(d,switch1,0) verts.addData3f(d, switch1, 0) verts.addData3f(d, switch2, 0) color.addData4f(*ctup) color.addData4f(*ctup) for i,d in enumerate(ys): switch1 = (-1) ** i * rng switch2 = (-1) ** i * -rng verts.addData3f(switch1, d, 0) verts.addData3f(switch2, d, 0) color.addData4f(*ctup) color.addData4f(*ctup) gridLines = GeomLinestrips(Geom.UHStatic) gridLines.addConsecutiveVertices(0, vertexData.getNumRows()) gridLines.closePrimitive() grid = Geom(vertexData) grid.addPrimitive(gridLines) return grid
def makeGrid(rng=1000, spacing=10): #FIXME make this scale based on zoom??? ctup = (.3, .3, .3, 1) xs = range(-rng, rng + 1, spacing) ys = xs fmt = GeomVertexFormat.getV3c4() #3 component vertex, w/ 4 comp color #fmt = GeomVertexFormat.getV3() #3 component vertex, w/ 4 comp color vertexData = GeomVertexData('points', fmt, Geom.UHStatic) verts = GeomVertexWriter(vertexData, 'vertex') color = GeomVertexWriter(vertexData, 'color') for i, d in enumerate(xs): switch1 = (-1)**i * rng switch2 = (-1)**i * -rng #print(d,switch1,0) verts.addData3f(d, switch1, 0) verts.addData3f(d, switch2, 0) color.addData4f(*ctup) color.addData4f(*ctup) for i, d in enumerate(ys): switch1 = (-1)**i * rng switch2 = (-1)**i * -rng verts.addData3f(switch1, d, 0) verts.addData3f(switch2, d, 0) color.addData4f(*ctup) color.addData4f(*ctup) gridLines = GeomLinestrips(Geom.UHStatic) gridLines.addConsecutiveVertices(0, vertexData.getNumRows()) gridLines.closePrimitive() grid = Geom(vertexData) grid.addPrimitive(gridLines) return grid
def getNodeFromController(controller, controlled_prim): if type(controlled_prim) is collada.controller.BoundSkinPrimitive: ch = Character('simplechar') bundle = ch.getBundle(0) skeleton = PartGroup(bundle, '<skeleton>') character_joints = {} for (name, joint_matrix) in controller.joint_matrices.iteritems(): joint_matrix.shape = (-1) character_joints[name] = CharacterJoint(ch, bundle, skeleton, name, Mat4(*joint_matrix)) tbtable = TransformBlendTable() for influence in controller.index: blend = TransformBlend() for (joint_index, weight_index) in influence: char_joint = character_joints[controller.getJoint(joint_index)] weight = controller.getWeight(weight_index)[0] blend.addTransform(JointVertexTransform(char_joint), weight) tbtable.addBlend(blend) array = GeomVertexArrayFormat() array.addColumn(InternalName.make('vertex'), 3, Geom.NTFloat32, Geom.CPoint) array.addColumn(InternalName.make('normal'), 3, Geom.NTFloat32, Geom.CPoint) array.addColumn(InternalName.make('texcoord'), 2, Geom.NTFloat32, Geom.CTexcoord) blendarr = GeomVertexArrayFormat() blendarr.addColumn(InternalName.make('transform_blend'), 1, Geom.NTUint16, Geom.CIndex) format = GeomVertexFormat() format.addArray(array) format.addArray(blendarr) aspec = GeomVertexAnimationSpec() aspec.setPanda() format.setAnimation(aspec) format = GeomVertexFormat.registerFormat(format) dataname = controller.id + '-' + controlled_prim.primitive.material.id vdata = GeomVertexData(dataname, format, Geom.UHStatic) vertex = GeomVertexWriter(vdata, 'vertex') normal = GeomVertexWriter(vdata, 'normal') texcoord = GeomVertexWriter(vdata, 'texcoord') transform = GeomVertexWriter(vdata, 'transform_blend') numtris = 0 if type(controlled_prim.primitive) is collada.polylist.BoundPolylist: for poly in controlled_prim.primitive.polygons(): for tri in poly.triangles(): for tri_pt in range(3): vertex.addData3f(tri.vertices[tri_pt][0], tri.vertices[tri_pt][1], tri.vertices[tri_pt][2]) normal.addData3f(tri.normals[tri_pt][0], tri.normals[tri_pt][1], tri.normals[tri_pt][2]) if len(controlled_prim.primitive._texcoordset) > 0: texcoord.addData2f(tri.texcoords[0][tri_pt][0], tri.texcoords[0][tri_pt][1]) transform.addData1i(tri.indices[tri_pt]) numtris += 1 elif type(controlled_prim.primitive ) is collada.triangleset.BoundTriangleSet: for tri in controlled_prim.primitive.triangles(): for tri_pt in range(3): vertex.addData3f(tri.vertices[tri_pt][0], tri.vertices[tri_pt][1], tri.vertices[tri_pt][2]) normal.addData3f(tri.normals[tri_pt][0], tri.normals[tri_pt][1], tri.normals[tri_pt][2]) if len(controlled_prim.primitive._texcoordset) > 0: texcoord.addData2f(tri.texcoords[0][tri_pt][0], tri.texcoords[0][tri_pt][1]) transform.addData1i(tri.indices[tri_pt]) numtris += 1 tbtable.setRows(SparseArray.lowerOn(vdata.getNumRows())) gprim = GeomTriangles(Geom.UHStatic) for i in range(numtris): gprim.addVertices(i * 3, i * 3 + 1, i * 3 + 2) gprim.closePrimitive() pgeom = Geom(vdata) pgeom.addPrimitive(gprim) render_state = getStateFromMaterial(controlled_prim.primitive.material) control_node = GeomNode("ctrlnode") control_node.addGeom(pgeom, render_state) ch.addChild(control_node) bundle = AnimBundle('simplechar', 5.0, 2) skeleton = AnimGroup(bundle, '<skeleton>') root = AnimChannelMatrixXfmTable(skeleton, 'root') #hjoint = AnimChannelMatrixXfmTable(root, 'joint1') #table = [10, 11, 12, 13, 14, 15, 14, 13, 12, 11] #data = PTAFloat.emptyArray(len(table)) #for i in range(len(table)): # data.setElement(i, table[i]) #hjoint.setTable(ord('i'), CPTAFloat(data)) #vjoint = AnimChannelMatrixXfmTable(hjoint, 'joint2') #table = [10, 9, 8, 7, 6, 5, 6, 7, 8, 9] #data = PTAFloat.emptyArray(len(table)) #for i in range(len(table)): # data.setElement(i, table[i]) #vjoint.setTable(ord('j'), CPTAFloat(data)) wiggle = AnimBundleNode('wiggle', bundle) np = NodePath(ch) anim = NodePath(wiggle) a = Actor(np, {'simplechar': anim}) a.loop('simplechar') return a #a.setPos(0, 0, 0) else: raise Exception("Error: unsupported controller type")
def getNodeFromController(controller, controlled_prim): if type(controlled_prim) is collada.controller.BoundSkinPrimitive: ch = Character('simplechar') bundle = ch.getBundle(0) skeleton = PartGroup(bundle, '<skeleton>') character_joints = {} for (name, joint_matrix) in controller.joint_matrices.iteritems(): joint_matrix.shape = (-1) character_joints[name] = CharacterJoint(ch, bundle, skeleton, name, Mat4(*joint_matrix)) tbtable = TransformBlendTable() for influence in controller.index: blend = TransformBlend() for (joint_index, weight_index) in influence: char_joint = character_joints[controller.getJoint(joint_index)] weight = controller.getWeight(weight_index)[0] blend.addTransform(JointVertexTransform(char_joint), weight) tbtable.addBlend(blend) array = GeomVertexArrayFormat() array.addColumn(InternalName.make('vertex'), 3, Geom.NTFloat32, Geom.CPoint) array.addColumn(InternalName.make('normal'), 3, Geom.NTFloat32, Geom.CPoint) array.addColumn(InternalName.make('texcoord'), 2, Geom.NTFloat32, Geom.CTexcoord) blendarr = GeomVertexArrayFormat() blendarr.addColumn(InternalName.make('transform_blend'), 1, Geom.NTUint16, Geom.CIndex) format = GeomVertexFormat() format.addArray(array) format.addArray(blendarr) aspec = GeomVertexAnimationSpec() aspec.setPanda() format.setAnimation(aspec) format = GeomVertexFormat.registerFormat(format) dataname = controller.id + '-' + controlled_prim.primitive.material.id vdata = GeomVertexData(dataname, format, Geom.UHStatic) vertex = GeomVertexWriter(vdata, 'vertex') normal = GeomVertexWriter(vdata, 'normal') texcoord = GeomVertexWriter(vdata, 'texcoord') transform = GeomVertexWriter(vdata, 'transform_blend') numtris = 0 if type(controlled_prim.primitive) is collada.polylist.BoundPolylist: for poly in controlled_prim.primitive.polygons(): for tri in poly.triangles(): for tri_pt in range(3): vertex.addData3f(tri.vertices[tri_pt][0], tri.vertices[tri_pt][1], tri.vertices[tri_pt][2]) normal.addData3f(tri.normals[tri_pt][0], tri.normals[tri_pt][1], tri.normals[tri_pt][2]) if len(controlled_prim.primitive._texcoordset) > 0: texcoord.addData2f(tri.texcoords[0][tri_pt][0], tri.texcoords[0][tri_pt][1]) transform.addData1i(tri.indices[tri_pt]) numtris+=1 elif type(controlled_prim.primitive) is collada.triangleset.BoundTriangleSet: for tri in controlled_prim.primitive.triangles(): for tri_pt in range(3): vertex.addData3f(tri.vertices[tri_pt][0], tri.vertices[tri_pt][1], tri.vertices[tri_pt][2]) normal.addData3f(tri.normals[tri_pt][0], tri.normals[tri_pt][1], tri.normals[tri_pt][2]) if len(controlled_prim.primitive._texcoordset) > 0: texcoord.addData2f(tri.texcoords[0][tri_pt][0], tri.texcoords[0][tri_pt][1]) transform.addData1i(tri.indices[tri_pt]) numtris+=1 tbtable.setRows(SparseArray.lowerOn(vdata.getNumRows())) gprim = GeomTriangles(Geom.UHStatic) for i in range(numtris): gprim.addVertices(i*3, i*3+1, i*3+2) gprim.closePrimitive() pgeom = Geom(vdata) pgeom.addPrimitive(gprim) render_state = getStateFromMaterial(controlled_prim.primitive.material) control_node = GeomNode("ctrlnode") control_node.addGeom(pgeom, render_state) ch.addChild(control_node) bundle = AnimBundle('simplechar', 5.0, 2) skeleton = AnimGroup(bundle, '<skeleton>') root = AnimChannelMatrixXfmTable(skeleton, 'root') #hjoint = AnimChannelMatrixXfmTable(root, 'joint1') #table = [10, 11, 12, 13, 14, 15, 14, 13, 12, 11] #data = PTAFloat.emptyArray(len(table)) #for i in range(len(table)): # data.setElement(i, table[i]) #hjoint.setTable(ord('i'), CPTAFloat(data)) #vjoint = AnimChannelMatrixXfmTable(hjoint, 'joint2') #table = [10, 9, 8, 7, 6, 5, 6, 7, 8, 9] #data = PTAFloat.emptyArray(len(table)) #for i in range(len(table)): # data.setElement(i, table[i]) #vjoint.setTable(ord('j'), CPTAFloat(data)) wiggle = AnimBundleNode('wiggle', bundle) np = NodePath(ch) anim = NodePath(wiggle) a = Actor(np, {'simplechar' : anim}) a.loop('simplechar') return a #a.setPos(0, 0, 0) else: raise Exception("Error: unsupported controller type")
class Track3d(object): ''' Generate the 3d Mesh out of the StreetData and the 2dTrack ''' def __init__(self, res, x, y, z=200, player_count=1, street_data=""): ''' ''' self._notify = DirectNotify().newCategory("TrackGen3D") self._notify.info("New Track3D-Object created: %s" % (self)) # street_data = (Vec2(4.0,4.0), Vec2(10.0,10.0), Vec2(10.0,0.0), Vec2(4.0,0.0), Vec2(0.0,-1.0)) # street_data = StreetData(Vec2(15.0,1.0), Vec2(15.0,-5.0), Vec2(0.0,-5.0), mirrored=True) #, Vec2(15.0,0.0) self.street_data = StreetData() # self.street_data.readFile("data/road/road01.xml") # self.street_data.readFile("data/road/tube.xml") if street_data == "": datas = ["road01", "tube"] street_data = datas[random.randint(0, len(datas) - 1)] self.street_data.readFile("data/road/" + street_data + ".xml") self.streetTextrange = 0.0 self.track = Track(x, y, z) self.track.generateTestTrack(player_count) # self.track.generateTrack(player_count) self.track_points = self.track.getInterpolatedPoints(res) self.varthickness = [] # Generate the Vector for thickness of the road self.generateNormals() # for i in range(len(self.track_points)-1): # if i == 0: # self.varthickness.append(self.calcTheVector(self.track_points[i],self.track_points[i],self.track_points[i+1])) #First # continue # self.varthickness.append(self.calcTheVector(self.track_points[i-1],self.track_points[i],self.track_points[i+1])) # self.varthickness.append(self.calcTheVector(self.track_points[len(self.track_points)-2],self.track_points[len(self.track_points)-1],self.track_points[len(self.track_points)-1])) #Last ## # Normalizing the Vector # for i in self.varthickness: # i.normalize() ## # print self.varthickness[-1] # print self.varthickness[0] # print self.varthickness[1] # print self.varthickness[2] # Spin the last 100 Points a litte bit to Vec3(-1,0,0) for i in range(-100, 1): # print self.varthickness[i] * (-i / 100), self.varthickness[i] , ((i* -1) / 100.0), i # print ((i* -1) / 100.0), self.varthickness[i], self.varthickness[i] * ((i* -1) / 100.0) self.varthickness[i] = self.varthickness[i] * (((i + 1) * -1) / 100.0) + Vec3(-1, 0, 0) self.normals[i] = self.normals[i] * (((i + 1) * -1) / 100.0) + Vec3(0, 0, 1) self.varthickness[i].normalize() self.normals[i].normalize() # print self.varthickness[i] # print self.varthickness[-1] # print self.varthickness[0] # print self.varthickness[1] # print self.varthickness[2] # print self.varthickness # for i in range(len(self.varthickness)): # if self.varthickness[i-1].almostEqual(self.varthickness[i], 0.3): # pass # else: # print "varthickness", self.varthickness[i-1], self.varthickness[i] # ------------------------------------------------------------------------------------- def resetWriters(self): ''' ''' self.vdata = GeomVertexData('street', GeomVertexFormat.getV3n3c4t2(), Geom.UHStatic) self.vertex = GeomVertexWriter(self.vdata, 'vertex') self.normal = GeomVertexWriter(self.vdata, 'normal') self.color = GeomVertexWriter(self.vdata, 'color') self.texcoord = GeomVertexWriter(self.vdata, 'texcoord') self.prim = GeomTriangles(Geom.UHStatic) # ------------------------------------------------------------------------------------- def calcTheVector(self, pre, now, past): vector1 = (pre[0] - now[0], pre[1] - now[1]) vector2 = (now[0] - past[0], now[1] - past[1]) high = pre[2] - past[2] return Vec3(((vector1[1] + vector2[1]) / 2.0), ((vector1[0] + vector2[0]) / 2.0), high) # ------------------------------------------------------------------------------------- def getVarthickness(self): return self.varthickness # ------------------------------------------------------------------------------------- def setTrackPoints(self, track_points): ''' ''' self.track_points = track_points def getTrackPoints(self): ''' ''' return self.track_points trackpoints = property(fget=getTrackPoints, fset=setTrackPoints) # ------------------------------------------------------------------------------------- def generateNormals(self): ''' ''' self.varthickness = [] self.normals = [] last_normal = Vec3(0, 0, 1) # last_vec = Vec3(0, 1, 0) for i in range(len(self.track_points)): if i == 0: vec = self.track_points[0] - self.track_points[1] elif i + 1 == len(self.track_points): vec = self.track_points[i - 1] - self.track_points[0] else: vec = self.track_points[i - 1] - self.track_points[i + 1] # calculate here the direction out of the street vector and the last normal last_normal.normalize() vec.normalize() mat = Mat3() mat.setRotateMat(-90, last_normal) # turn the direction around the last_normal turned_vec = mat.xform(vec) turned_vec.normalize() last_normal = turned_vec.cross(vec) # calculate the new normal turned_vec.normalize() self.varthickness.append(turned_vec) self.normals.append(last_normal) # ------------------------------------------------------------------------------------- def createVertices(self, track_points=None, street_data=None): ''' ''' if track_points is None: track_points = self.track_points if street_data is None: street_data = self.street_data self.resetWriters() texcoordinates = [] # street_data_length = len(street_data) texcoordinates = street_data.getTexCoordinates() tracklength = self.track.getLength() tex_step = 0.006 * (0.0002 * tracklength) for i in range(len(track_points)): turned_vec = self.varthickness[i] last_normal = self.normals[i] j = 0 for shapedot in street_data: # this is like a layer in 3d [Ebenengleichung] # vec = vec + vec*scalar + vec*scalar # this is used to transform the 2d-Streetshape to 3d point = track_points[i] + (turned_vec * shapedot[0]) + (last_normal * shapedot[1]) self.vertex.addData3f(point[0], point[1], point[2]) self.normal.addData3f(0, 0, 1) # KA how to calc self.streetTextrange += tex_step self.texcoord.addData2f(texcoordinates[j], self.streetTextrange) j += 1 # ------------------------------------------------------------------------------------- def connectVertices(self, street_data): # param j = len(street_Data) j = len(street_data) for i in range(self.vdata.getNumRows() - (j)): # -j?????? oder +-1 if (i + 1) % j != 0: self.prim.addVertex(i) self.prim.addVertex(i + 1) self.prim.addVertex(i + j + 1) self.prim.closePrimitive() self.prim.addVertex(i) self.prim.addVertex(i + j + 1) self.prim.addVertex(i + j) self.prim.closePrimitive() else: # close mesh's bottom side self.prim.addVertex(i + 1 - j) self.prim.addVertex(i + 1) self.prim.addVertex(i) self.prim.closePrimitive() self.prim.addVertex(i) self.prim.addVertex(i + 1) self.prim.addVertex(i + j) self.prim.closePrimitive() # close start and end k = self.vdata.getNumRows() - j for i in range(j): if (i + 1) % j != 0: self.prim.addVertex(i) self.prim.addVertex(i + k + 1) self.prim.addVertex(i + 1) self.prim.closePrimitive() self.prim.addVertex(i) self.prim.addVertex(i + k) self.prim.addVertex(i + k + 1) self.prim.closePrimitive() else: # close mesh's bottom side self.prim.addVertex(i) self.prim.addVertex(i + k - j + 1) self.prim.addVertex(i - j + 1) self.prim.closePrimitive() self.prim.addVertex(i) self.prim.addVertex(i + k) self.prim.addVertex(i + k - j + 1) self.prim.closePrimitive() # ------------------------------------------------------------------------------------- def createRoadMesh(self): ''' ''' # Creating the Vertex self.createVertices() # Connect the Vertex self.connectVertices(self.street_data) geom = Geom(self.vdata) geom.addPrimitive(self.prim) node = GeomNode('street') node.addGeom(geom) # nodePath = self.render.attachNewNode(node) return node # ------------------------------------------------------------------------------------- def createUninterpolatedRoadMesh(self): ''' ''' # Creating the Vertex self.createVertices(self.track.getPoints(), self.street_data) # Connect the Vertex self.connectVertices(self.street_data) geom = Geom(self.vdata) geom.addPrimitive(self.prim) node = GeomNode('street') node.addGeom(geom) # nodePath = self.render.attachNewNode(node) return node # ------------------------------------------------------------------------------------- def createBorderLeftMesh(self): ''' ''' # Creating the Vertex self.createVertices(self.track_points, self.street_data.border_l) # Connect the Vertex self.connectVertices(self.street_data.border_l) geom = Geom(self.vdata) geom.addPrimitive(self.prim) node = GeomNode('border_l') node.addGeom(geom) # nodePath = self.render.attachNewNode(node) return node # ------------------------------------------------------------------------------------- def createBorderRightMesh(self): ''' ''' # Creating the Vertex self.createVertices(self.track_points, self.street_data.border_r) # Connect the Vertex self.connectVertices(self.street_data.border_r) geom = Geom(self.vdata) geom.addPrimitive(self.prim) node = GeomNode('border_r') node.addGeom(geom) # nodePath = self.render.attachNewNode(node) return node # ------------------------------------------------------------------------------------- def createBorderLeftCollisionMesh(self): ''' ''' # Creating the Vertex self.createVertices(self.track_points, self.street_data.border_l_coll) # Connect the Vertex self.connectVertices(self.street_data.border_l_coll) geom = Geom(self.vdata) geom.addPrimitive(self.prim) node = GeomNode('border_l_coll') node.addGeom(geom) # nodePath = self.render.attachNewNode(node) return node # ------------------------------------------------------------------------------------- def createBorderRightCollisionMesh(self): ''' ''' # Creating the Vertex self.createVertices(self.track_points, self.street_data.border_r_coll) # Connect the Vertex self.connectVertices(self.street_data.border_r_coll) geom = Geom(self.vdata) geom.addPrimitive(self.prim) node = GeomNode('border_r_coll') node.addGeom(geom) # nodePath = self.render.attachNewNode(node) return node
class ConstrainedDelaunayTriangulator(object): """Creates a Constrained Delaunay Triangulation""" @staticmethod def drawTriangleIndices(adjLst, name='indsgroup'): from direct.gui.OnscreenText import OnscreenText indNP = render.attachNewNode(name) for i in range(0, len(adjLst)): center = getCenterOfPoints3D(adjLst[i].getPoints()) dummy = indNP.attachNewNode(str(i)) txt = OnscreenText(text=str(i), pos=center, scale=1) txt.reparentTo(dummy) dummy.setP(dummy.getP() - 90) return indNP @staticmethod def findContainingTriangle(point, startTriangle, fullList): triangles = collections.deque([]) triangles.append(startTriangle) while triangles: tri = triangles.popleft() if tri.containsPoint(point): return tri triangles.extend([fullList[i] for i in tri.getNeighbors(includeEmpties=False)]) raise ValueError("Point added that's outside of the bounded space {0}".format(point)) def __init__(self, vertexName='ConstrainedDelaunayTriangles', vertexFormat=GeomVertexFormat.getV3(), usage = Geom.UHDynamic, onVertexCreationCallback = None, universalZ = 0.0): self._vertexData = GeomVertexData(vertexName, vertexFormat, Geom.UHDynamic) self._geomTriangles = GeomTriangles(usage) self._geomTrianglesHoles = GeomTriangles(usage) self._vertexRewriter = GeomVertexRewriter(self._vertexData, 'vertex') # user cannot have control of a writer if onVertexCreationCallback is None: onVertexCreationCallback = lambda x, y, z: None # something to call without checking existence later self._vertexCallback = onVertexCreationCallback self._universalZ = universalZ self.__holes = [[]] self.__polygon = [] inf = float('inf') negInf = float('-inf') self.bounds = { 'minX': inf, 'maxX': negInf, 'minY': inf, 'maxY': negInf, } self.lastStaticVertexIndex = -1 def addHoleVertex(self, index): """Adds the next consecutive vertex of the current hole.""" # we might not have holes but we should always have triangles after triangulation self.__holes[-1].append(index) def addPolygonVertex(self, index): """Adds the next consecutive vertex of the polygon.""" self.__polygon.append(index) def _addVertex(self, x, y, z, bounded=True): # BLOG track bounds to create the encapsulating triangle rather than lexicographic ordering # BLOG could have used a heap while adding verts, then popped as we processed each vertex if x < self.bounds['minX'] and bounded: self.bounds['minX'] = x if x > self.bounds['maxX'] and bounded: self.bounds['maxX'] = x if y < self.bounds['minY'] and bounded: self.bounds['minY'] = y if y > self.bounds['maxY'] and bounded: self.bounds['maxY'] = y if not self._vertexRewriter.isAtEnd(): self._vertexRewriter.setRow(self._vertexData.getNumRows()) n = self._vertexRewriter.getWriteRow() self._vertexRewriter.addData3f(x, y, self._universalZ) self._vertexCallback(x, y, self._universalZ) return n def addVertex(self, pointOrX, y=None, z=None, bounded=True): """Adds a new vertex to the vertex pool.""" if hasattr(pointOrX, 'y'): # if the first argument is a point expand and call the backing function return self._addVertex(pointOrX.x, pointOrX.y, pointOrX.z, bounded=bounded) return self._addVertex(pointOrX, y, z, bounded=bounded) def addVertexToPolygon(self, pointOrX, y, z, bounded=True): """Adds a vertex to the pool and then adds its index to the polygon vertex index list.""" n = self.addVertex(pointOrX, y, z, bounded=bounded) self.addPolygonVertex(n) return n def addVertexToHole(self, pointOrX, y, z): """Adds a vertex to the pool and then adds its index to the polygon vertex index list.""" n = self.addVertex(pointOrX, y, z) self.addHoleVertex(n) return n def beginHole(self): """Finishes the previous hole, if any, and prepares to add a new hole.""" if self.__holes[-1]: # if the last hole (list of vertices) is empty use it as the next hole self.__holes.append([]) # if it's not empty append a new hole def clear(self): """Removes all vertices and polygon specifications from the Triangulator, and prepares it to start over.""" raise NotImplementedError("""ConstrainedDelaunayTriangulator.clear() is not implemented.""") def clearPolygon(self): """Removes the current polygon definition (and its set of holes), but does not clear the vertex pool.""" raise NotImplementedError("""ConstrainedDelaunayTriangulator.clearPolygon() is not implemented.""") def getAdjacencyList(self): """Returns a list of triangles, each referencing its own neighbors by their list index.""" return self.__polygon def getGeomNode(self, name='ConstrainedDelaunayTriangles'): """returns a GeomNode, with the provided name, sufficient to put in the scene and draw.""" # BLOG My TODO legend # DOC 1) (adding to scene) create a Geom and add primitives of like base-type i.e. triangles and triangle strips geom = Geom(self._vertexData) geom.addPrimitive(self._geomTriangles) # DOC 2) create a GeomNode to hold the Geom(s) and add the Geom(s) gn = GeomNode(name) gn.addGeom(geom) # DOC 3) attach the node to the scene (in the calling code) # gnNodePath = render.attachNewNode(gn) return gn def getNumTriangles(self): """Returns the number of triangles generated by the previous call to triangulate().""" if not self.isTriangulated(): raise ValueError("Vertices must be added and triangulate() must be called before calling getNumTriangles()") return len(self.__polygon) def getNumVertices(self): """Returns the number of vertices in the pool.""" return self._vertexData.getNumRows() def getVertexReader(self): """Returns a reader for the vertex column.""" return GeomVertexReader(self._vertexData, 'vertex') def getTriangleV0(self, n): """Returns vertex 0 of the nth triangle generated by the previous call to triangulate().""" try: return self.__polygon[n].pointIndex0() except AttributeError: # BLOG switching errors to clarify the cause raise LookupError("Must call triangulate() before querying for a triangle's vertices.") def getTriangleV1(self, n): """Returns vertex 1 of the nth triangle generated by the previous call to triangulate().""" try: return self.__polygon[n].pointIndex1() except AttributeError: # BLOG switching errors to clarify the cause raise LookupError("Must call triangulate() before querying for a triangle's vertices.") def getTriangleV2(self, n): """Returns vertex 2 of the nth triangle generated by the previous call to triangulate().""" try: return self.__polygon[n].pointIndex2() except AttributeError: # BLOG switching errors to clarify the cause raise LookupError("Must call triangulate() before querying for a triangle's vertices.") def getTriangleList(self): import copy return copy.copy(self.__polygon) def getVertex(self, n): """Returns the nth vertex.""" self._vertexRewriter.setRow(n) return self._vertexRewriter.getData3f() def getGeomVertexData(self): return self._vertexData def getGeomTriangles(self): assert self.isTriangulated() return self._geomTriangles def getVertices(self): """Returns a list of vertices.""" verts = [] for i in range(0, self._vertexData.getNumRows()): self._vertexRewriter.setRow(i) verts.append(self._vertexRewriter.getData3f()) return verts def isLeftWinding(self): """Returns true if the polygon vertices are listed in counterclockwise order, or false if they appear to be listed in clockwise order.""" assert self.isTriangulated() return self.__polygon[0].isLeftWinding() def isTriangulated(self): """Guesses whether the polygon has been triangulated.""" return len(self.__polygon) > 0 and isinstance(self.__polygon[0], ConstrainedDelaunayAdjacencyTriangle) def triangulate(self, makeDelaunay=True): """Does the work of triangulating the specified polygon.""" global notify if self.isTriangulated(): raise ValueError("triangulate() must only be called once.") notify.warning("bounds: minX, minY, maxX, maxY {0} {1} {2} {3}".format(self.bounds['minX'], self.bounds['minY'], self.bounds['maxX'], self.bounds['maxY'])) h = abs(self.bounds['maxY'] - self.bounds['minY']) w = abs(self.bounds['maxX'] - self.bounds['minX']) topLeft = Point3(self.bounds['minX'] - 10*w, self.bounds['maxY'] + 10*h, self._universalZ) bottomRight = Point3(self.bounds['minX'] - 10*w, self.bounds['minY'] - 10*h, self._universalZ) farRight = Point3(self.bounds['maxX'] + 10*w, (self.bounds['maxY'] + self.bounds['minY'])/2, self._universalZ) # Other than the tree below, any vertices added after this are CDT as opposed to DT self.lastStaticVertexIndex = self.getNumVertices() - 1 # these three will be removed later, thus will not be artificial restraints v0 = self.addVertex(topLeft, bounded=False) v1 = self.addVertex(bottomRight, bounded=False) v2 = self.addVertex(farRight, bounded=False) bounds = ConstrainedDelaunayAdjacencyTriangle(v0, v1, v2, self._vertexData, self._geomTriangles, self._vertexRewriter) triangulated = [bounds] while True: try: pt = self.__polygon.pop() except IndexError: break self._vertexRewriter.setRow(pt) point = self._vertexRewriter.getData3f() notify.warning("\n######################## len {} TRIANGULATE {} ###########################\n".format(len(triangulated), point)) notify.warning("\n########################## SO FAR\n{}\n############################\n".format([tr.index for tr in triangulated])) # find the triangle the point lays within found = ConstrainedDelaunayTriangulator.findContainingTriangle(point, bounds, triangulated) if found is not None: # BLOG heapq.merge() is useless as it returns an iterable which can't be indexed, but heaps require lists # BLOG Hence, it's probably faster to heap.push than to iterate (in C) a merge then iterate to recreate a list # BLOG if I use a heap # triangulate the point into the triangle, collecting any new triangles newTriangles = found.triangulatePoint(pt, triangulated) triangulated.extend(newTriangles) if makeDelaunay: for tri in newTriangles: tri.legalize(tri.point0, triangulated) # point, triangulated) tri.legalize(tri.point1, triangulated) tri.legalize(tri.point2, triangulated) else: raise ValueError("Point given that's outside of original space.") notify.warning("triangulated: length: {} type: {}".format(len(triangulated), type(triangulated))) self.__polygon = triangulated