def makeQuad(renderVP, collisionVP, bl=None, tr=None, br=None, tl=None): """Return a tuple of EggPolygons (renderQuad,collisionQuad): renderable and collision geometry for a 4-vertex polygon. Keyword arguments: renderVP -- EggVertexPool to which vertices will be added for the renderable quad collisionVP -- EggVertexPool to which vertices will be added for the collidable quad bl -- bottom-left vertex of quad (Point3D). Default (-10,-10,0) tr -- top-right vertex of quad (Point3D). Default (10,10,0) br -- bottom-right vertex of quad (Point3D) tl -- top-left vertex of quad (Point3D) If either of br or tl is None then sensible defaults will be computed according to the values of bl and tr. """ if bl is None: bl = P.Point3D(-10, -10, 0) if tr is None: tr = P.Point3D(10, 10, 0) if br is None: l = tr.getX() - bl.getX() br = bl + P.Vec3D(l, 0, 0) if tl is None: w = tr.getY() - bl.getY() tl = bl + P.Vec3D(0, w, 0) renderQuad = P.EggPolygon() collisionQuad = P.EggPolygon() for corner in [bl, br, tr, tl]: vertex = P.EggVertex() vertex.setPos(corner) collisionVertex = P.EggVertex() collisionVertex.setPos(corner) renderQuad.addVertex(renderVP.addVertex(vertex)) collisionQuad.addVertex(collisionVP.addVertex(collisionVertex)) # A bug in Panda3D means that auto-triangulation of concave polygons # fails for some combinations of vertices (the polygon is not properly # transformed into triangles) creating gaps in a model where polygons are # missing. We can check for this failure by calling # EggPolygon.triangulateInto() and checking its return value. If False is # returned we reverse the vertex order of the polygon (so it now faces # downward instead of up) and reverse the backface flag so the downside # (which is now facing up) is rendered instead of the upside (which is now # facing down). The result looks more or less the same as if the polygon # had triangulated correctly in the first place (except the shading will be # different). Thanks to ynjh_jo for this workaround. egn = P.EggGroupNode( ) # triangulateInto() stores the resulting polygons in # an EggGroupNode, which we discard, we only care # about the True or False return value if not renderQuad.triangulateInto(egn, 0): # Triangulation failed, fix it. renderQuad.reverseVertexOrdering() renderQuad.setBfaceFlag(1) collisionQuad.reverseVertexOrdering() collisionQuad.setBfaceFlag(1) renderQuad.recomputePolygonNormal() # Use faceted not smoothed lighting return renderQuad, collisionQuad
def makeTerrainFromHeightMap(heightmap): """Return a tuple of NodePaths (renderNodePath,collisionNodePath) to renderable and collision geometry for a chainable terrain model built from the given heightmap. collisionNodePath is parented to renderNodePath. renderNodePath is not parented to anything by this function. Every 3x3 group of quads in the collision geometry is collected under a PandaNode for efficient collision detection. This could be improved on by building both the renderable and collision geometry as octrees. Keyword arguments: heightmap -- a 2D list of height values. """ size = len(heightmap) renderNodePath = P.NodePath('') renderEggData = P.EggData() renderVertexPool = P.EggVertexPool('') collisionNodePath = renderNodePath.attachNewNode('') # Supply the EggGroup & EggVertexPool for the first node. collisionVertexPool = P.EggVertexPool('') collisionEggGroup = P.EggGroup('') collisionEggGroup.addObjectType('barrier') # We group every (numQuadGrid x numQuadGrid) quads under 1 collision node. numQuadGrid = 3 # The modulo of (size-2)/numQuadGrid marks when the quads must be grouped # into 1 geom. edgeMod = (size - 2) % numQuadGrid for i in range(0, len(heightmap) - 1, numQuadGrid): # Limit nextIrange to avoid it from jump over the edge. nextIrange = min(numQuadGrid, len(heightmap) - 1 - i) for j in range(0, len(heightmap) - 1): for nextI in range(0, nextIrange): bl = P.Point3D(i + nextI, j, heightmap[i + nextI][j]) tr = P.Point3D(i + nextI + 1, j + 1, heightmap[i + nextI + 1][j + 1]) br = P.Point3D(i + nextI + 1, j, heightmap[i + nextI + 1][j]) tl = P.Point3D(i + nextI, j + 1, heightmap[i + nextI][j + 1]) # Construct polygons (quads) with makeQuads. renderQuad, collisionQuad = makeQuad(renderVertexPool, collisionVertexPool, bl, tr, br, tl) renderEggData.addChild(renderQuad) collisionEggGroup.addChild(collisionQuad) if j % numQuadGrid == edgeMod and nextI == nextIrange - 1: # Group the previous (numQuadGrid x numQuadGrid) quads under # collision node. collisionEggData = P.EggData() collisionEggData.addChild(collisionEggGroup) pandaNode = P.loadEggData(collisionEggData) nodePath = collisionNodePath.attachNewNode( pandaNode.getChild(0).getChild(0)) # Uncomment the next line to see the collision geom. #nodePath.show() # Supply the EggGroup & EggVertexPool for the next node. collisionEggGroup = P.EggGroup('') collisionEggGroup.addObjectType('barrier') collisionVertexPool = P.EggVertexPool('') pandaNode = P.loadEggData(renderEggData) renderNodePath.attachNewNode(pandaNode) return renderNodePath, collisionNodePath