def paste(self, f): self.plane = Plane(f.plane) self.isSelected = f.isSelected self.setMaterial(f.material.clone()) self.solid = f.solid self.vertices = [] for i in range(len(f.vertices)): newVert = f.vertices[i].clone() newVert.face = self self.vertices.append(newVert) self.generate()
def makeSolid(self, generator, faces, material, temp=False, color=None): solid = Solid(generator.getNextID()) solid.setTemporary(temp) if color is not None: solid.setColor(color) for arr in faces: face = SolidFace(generator.getNextFaceID(), Plane.fromVertices(arr[0], arr[1], arr[2]), solid) face.setMaterial(material) for vert in arr: face.vertices.append(SolidVertex(vert, face)) solid.faces.append(face) face.alignTextureToFace() if temp: face.setPreviewState() face.generate() if not temp: solid.setToSolidOrigin() solid.generateFaces() solid.recalcBoundingBox() else: solid.reparentTo(base.render) return solid
def flip(self): self.vertices.reverse() self.plane = Plane.fromVertices(self.vertices[0].pos, self.vertices[1].pos, self.vertices[2].pos) if self.hasGeometry: self.regenerateGeometry()
def __init__(self, id=0, plane=Plane(0, 0, 1, 0), solid=None): MapWritable.__init__(self, base.document) self.id = id self.material = FaceMaterial() self.vertices = [] self.isSelected = False self.plane = plane self.solid = solid self.hasGeometry = False self.vdata = None # Different primitive representations of this face. self.geom3D = None self.geom3DLines = None self.geom2D = None # Index into the Solid's GeomNode of the Geoms we render for this face. self.index3D = -1 self.index3DLines = -1 self.index2D = -1 # RenderState for each Geom we render for this face. self.state3D = RenderState.makeEmpty() self.state3DLines = RenderState.make( AntialiasAttrib.make(AntialiasAttrib.MLine), ColorAttrib.makeFlat(Vec4(1, 1, 0, 1))) self.state2D = RenderState.makeEmpty() if solid: self.setColor(self.solid.color) # Not None if face is a displacement. self.dispInfo = None
def xform(self, mat): for vert in self.vertices: vert.xform(mat) self.plane = Plane.fromVertices(self.vertices[0].pos, self.vertices[1].pos, self.vertices[2].pos) self.calcTextureCoordinates(True) if self.hasGeometry: self.regenerateGeometry()
def copy(self, generator): f = SolidFace(generator.getNextID(), Plane(self.plane), self.solid) f.isSelected = self.isSelected f.setFaceMaterial(self.material.clone()) for i in range(len(self.vertices)): newVert = self.vertices[i].clone() newVert.face = f f.vertices.append(newVert) return f
def readKeyValues(self, kv): self.id = int(self.id) base.document.reserveFaceID(self.id) p0 = Point3() p1 = Point3() p2 = Point3() kv.parsePlanePoints(kv.getValue("plane"), p0, p1, p2) self.plane = Plane.fromVertices(p0, p1, p2) self.material.readKeyValues(kv) self.setMaterial(self.material.material)
def confirmClip(self): if self.point1 is None or self.point2 is None or self.point3 is None: self.reset() return clipPlane = Plane.fromVertices(self.point1, self.point2, self.point3) side = ClipSide(self.side) self.reset() solids = [] for obj in base.selectionMgr.selectedObjects: if obj.ObjectName == "solid": solids.append(obj) base.actionMgr.performAction( "Clip %i solid(s)" % len(solids), Clip(solids, clipPlane, side != ClipSide.Back, side != ClipSide.Front)) self.side = side
def createFromIntersectingPlanes(planes, generator, generateFaces=True, temp=False): solid = Solid(generator.getNextID()) solid.setTemporary(temp) for i in range(len(planes)): # Split the winding by all the other planes w = Winding.fromPlaneAndRadius(planes[i]) for j in range(len(planes)): if i != j: # Flip the plane, because we want to keep the back. w.splitInPlace(-planes[j]) # Round vertices a bit for sanity w.roundPoints() # The final winding is the face face = SolidFace(generator.getNextFaceID(), Plane(planes[i]), solid) for j in range(len(w.vertices)): face.vertices.append(SolidVertex(w.vertices[j], face)) solid.faces.append(face) if not temp: solid.setToSolidOrigin() # Ensure all faces point outwards origin = Point3(0) for face in solid.faces: # The solid origin should be on or behind the face plane. # If the solid origin is in front of the face plane, flip the face. if face.plane.distToPlane(origin) > 0: face.flip() if generateFaces: solid.alignTexturesToFaces() solid.generateFaces() if not temp: solid.recalcBoundingBox() return solid
def updateClipPlane(self): self.clearClipPlane() if self.point1 == self.point2 or self.point1 == self.point3 or self.point2 == self.point3: return plane = Plane.fromVertices(self.point1, self.point2, self.point3) tempGen = IDGenerator() for obj in base.selectionMgr.selectedObjects: if obj.ObjectName != "solid": continue ret, back, front = obj.split(plane, tempGen, True) if ret: front.np.reparentTo(self.doc.render) back.np.reparentTo(self.doc.render) self.tempSolids.append((obj, front, back)) # Hide the original solid obj.np.stash() self.updateClipSide(self.side)
def split(self, plane, generator, temp=False): back = front = None # Check that this solid actually spans the plane classifications = [] for face in self.faces: classify = face.classifyAgainstPlane(plane) if classify not in classifications: classifications.append(classify) if PlaneClassification.Spanning not in classifications: if PlaneClassification.Back in classifications: back = self elif PlaneClassification.Front in classifications: front = self return [False, back, front] backPlanes = [plane] flippedFront = Plane(plane) flippedFront.flip() frontPlanes = [flippedFront] for face in self.faces: classify = face.classifyAgainstPlane(plane) if classify != PlaneClassification.Back: frontPlanes.append(face.getWorldPlane()) if classify != PlaneClassification.Front: backPlanes.append(face.getWorldPlane()) back = Solid.createFromIntersectingPlanes(backPlanes, generator, False, temp) front = Solid.createFromIntersectingPlanes(frontPlanes, generator, False, temp) if not temp: # copyBase() will set the transform to what we're copying from, but we already # figured out a transform for the solids. Store the current transform so we can # set it back. bTrans = back.np.getTransform() fTrans = front.np.getTransform() self.copyBase(back, generator) self.copyBase(front, generator) back.np.setTransform(bTrans) front.np.setTransform(fTrans) unionOfFaces = front.faces + back.faces for face in unionOfFaces: face.material = self.faces[0].material.clone() face.setMaterial(face.material.material) face.alignTextureToFace() # Restore textures (match the planes up on each face) for orig in self.faces: for face in back.faces: classify = face.classifyAgainstPlane(orig.getWorldPlane()) if classify != PlaneClassification.OnPlane: continue face.material = orig.material.clone() face.setMaterial(face.material.material) face.alignTextureToFace() break for face in front.faces: classify = face.classifyAgainstPlane(orig.getWorldPlane()) if classify != PlaneClassification.OnPlane: continue face.material = orig.material.clone() face.setMaterial(face.material.material) face.alignTextureToFace() break back.generateFaces() front.generateFaces() if not temp: back.recalcBoundingBox() front.recalcBoundingBox() return [True, back, front]
def getWorldPlane(self): plane = Plane(self.plane) plane.xform(self.solid.np.getMat(NodePath())) return plane
class SolidFace(MapWritable): ObjectName = "side" def __init__(self, id=0, plane=Plane(0, 0, 1, 0), solid=None): MapWritable.__init__(self, base.document) self.id = id self.material = FaceMaterial() self.vertices = [] self.isSelected = False self.plane = plane self.solid = solid self.hasGeometry = False self.vdata = None # Different primitive representations of this face. self.geom3D = None self.geom3DLines = None self.geom2D = None # Index into the Solid's GeomNode of the Geoms we render for this face. self.index3D = -1 self.index3DLines = -1 self.index2D = -1 # RenderState for each Geom we render for this face. self.state3D = RenderState.makeEmpty() self.state3DLines = RenderState.make( AntialiasAttrib.make(AntialiasAttrib.MLine), ColorAttrib.makeFlat(Vec4(1, 1, 0, 1))) self.state2D = RenderState.makeEmpty() if solid: self.setColor(self.solid.color) # Not None if face is a displacement. self.dispInfo = None #self.generateNodes() def setPreviewState(self): self.state3D = self.state3D.setAttrib(TransparencyAttrib.make(True)) self.state3D = self.state3D.setAttrib( ColorScaleAttrib.make(Vec4(1, 1, 1, PreviewAlpha))) self.state2D = self.state2D.setAttrib( ColorAttrib.makeFlat(LEGlobals.PreviewBrush2DColor)) def isDisplacement(self): return self.dispInfo is not None def getBounds(self, other=None): return self.solid.getBounds(other) def setSolid(self, solid): self.solid = solid def getOrientation(self): plane = self.getWorldPlane() # The normal must have a nonzero length! if plane[0] == 0 and plane[1] == 0 and plane[2] == 0: return FaceOrientation.Invalid # # Find the axis that the surface normal has the greatest projection onto. # orientation = FaceOrientation.Invalid normal = plane.getNormal() maxDot = 0 for i in range(6): dot = normal.dot(FaceNormals[i]) if (dot >= maxDot): maxDot = dot orientation = FaceOrientation(i) return orientation def showClipVisRemove(self): self.geom3D.setDraw(False) self.state3DLines = self.state3DLines.setAttrib( ColorAttrib.makeFlat(Vec4(1, 0, 0, 1))) self.state2D = self.state2D.setAttrib( ColorAttrib.makeFlat(Vec4(1, 0, 0, 1))) self.solid.setFaceGeomState(self.geom3DLines, self.state3DLines) self.solid.setFaceGeomState(self.geom2D, self.state2D) def showClipVisKeep(self): self.geom3D.setDraw(True) self.state3DLines = self.state3DLines.setAttrib( ColorAttrib.makeFlat(Vec4(1, 1, 0, 1))) self.state2D = self.state2D.setAttrib( ColorAttrib.makeFlat(Vec4(1, 1, 1, 1))) self.solid.setFaceGeomState(self.geom3DLines, self.state3DLines) self.solid.setFaceGeomState(self.geom2D, self.state2D) def show3DLines(self): if self.geom3DLines: self.geom3DLines.setDraw(True) def hide3DLines(self): if self.geom3DLines: self.geom3DLines.setDraw(False) def copy(self, generator): f = SolidFace(generator.getNextID(), Plane(self.plane), self.solid) f.isSelected = self.isSelected f.setFaceMaterial(self.material.clone()) for i in range(len(self.vertices)): newVert = self.vertices[i].clone() newVert.face = f f.vertices.append(newVert) return f def clone(self): f = self.copy(IDGenerator()) f.id = self.id return f def paste(self, f): self.plane = Plane(f.plane) self.isSelected = f.isSelected self.setMaterial(f.material.clone()) self.solid = f.solid self.vertices = [] for i in range(len(f.vertices)): newVert = f.vertices[i].clone() newVert.face = self self.vertices.append(newVert) self.generate() def unclone(self, f): self.paste(f) self.id = f.id def xform(self, mat): for vert in self.vertices: vert.xform(mat) self.plane = Plane.fromVertices(self.vertices[0].pos, self.vertices[1].pos, self.vertices[2].pos) self.calcTextureCoordinates(True) if self.hasGeometry: self.regenerateGeometry() def getAbsOrigin(self): avg = Point3(0) for vert in self.vertices: avg += vert.getWorldPos() avg /= len(self.vertices) return avg def getWorldPlane(self): plane = Plane(self.plane) plane.xform(self.solid.np.getMat(NodePath())) return plane def getName(self): return "Solid face" def select(self): self.state3D = self.state3D.setAttrib( ColorScaleAttrib.make(Vec4(1, 0.75, 0.75, 1))) self.state2D = self.state2D.setAttrib( ColorAttrib.makeFlat(Vec4(1, 0, 0, 1))) self.state2D = self.state2D.setAttrib( CullBinAttrib.make("fixed", LEGlobals.SelectedSort)) self.state2D = self.state2D.setAttrib(DepthWriteAttrib.make(False)) self.state2D = self.state2D.setAttrib(DepthTestAttrib.make(False)) self.solid.setFaceGeomState(self.geom3D, self.state3D) self.solid.setFaceGeomState(self.geom2D, self.state2D) self.show3DLines() self.isSelected = True def deselect(self): self.state3D = self.state3D.removeAttrib(ColorScaleAttrib) self.state2D = self.state2D.setAttrib( ColorAttrib.makeFlat(self.solid.color)) self.state2D = self.state2D.removeAttrib(DepthWriteAttrib) self.state2D = self.state2D.removeAttrib(DepthTestAttrib) self.state2D = self.state2D.removeAttrib(CullBinAttrib) self.solid.setFaceGeomState(self.geom3D, self.state3D) self.solid.setFaceGeomState(self.geom2D, self.state2D) self.hide3DLines() self.isSelected = False def readKeyValues(self, kv): self.id = int(self.id) base.document.reserveFaceID(self.id) p0 = Point3() p1 = Point3() p2 = Point3() kv.parsePlanePoints(kv.getValue("plane"), p0, p1, p2) self.plane = Plane.fromVertices(p0, p1, p2) self.material.readKeyValues(kv) self.setMaterial(self.material.material) def writeKeyValues(self, kv): kv.setKeyValue("id", str(self.id)) # Write out the first three vertices to define the plane. vert0 = self.vertices[0].pos vert1 = self.vertices[1].pos vert2 = self.vertices[2].pos kv.setKeyValue( "plane", "(%f %f %f) (%f %f %f) (%f %f %f)" % (vert0.x, vert0.y, vert0.z, vert1.x, vert1.y, vert1.z, vert2.x, vert2.y, vert2.z)) # Write out material values self.material.writeKeyValues(kv) def generate(self): self.regenerateGeometry() def setMaterial(self, mat): self.material.material = mat if mat: self.state3D = self.state3D.setAttrib( BSPMaterialAttrib.make(mat.material)) if mat.material.hasKeyvalue("$translucent") and bool( int(mat.material.getKeyvalue("$translucent"))): self.state3D = self.state3D.setAttrib( TransparencyAttrib.make(TransparencyAttrib.MDual)) if self.geom3D: self.solid.setFaceGeomState(self.geom3D, self.state3D) #if mat.material.hasKeyvalue("$basetexture") and "tools" in mat.material.getKeyvalue("$basetexture"): # self.geom3D.setDraw(False) def setColor(self, color): self.state2D = self.state2D.setAttrib(ColorAttrib.makeFlat(color)) if self.geom2D: self.solid.setFaceGeomState(self.geom2D, self.state2D) def setFaceMaterial(self, faceMat): self.material = faceMat self.setMaterial(self.material.material) self.calcTextureCoordinates(True) self.send('faceMaterialChanged', [self]) def alignTextureToFace(self): self.material.alignTextureToFace(self) self.calcTextureCoordinates(True) def alignTextureToWorld(self): self.material.alignTextureToWorld(self) self.calcTextureCoordinates(True) def calcTextureCoordinates(self, minimizeShiftValues): if minimizeShiftValues: self.minimizeTextureShiftValues() for vert in self.vertices: vert.uv.set(0, 0) if self.material.material is None: return if self.material.material.size.x == 0 or self.material.material.size.y == 0: return if self.material.scale.x == 0 or self.material.scale.y == 0: return for vert in self.vertices: vertPos = vert.getWorldPos() # # projected s, t (u, v) texture coordinates # s = self.material.uAxis.dot( vertPos) / self.material.scale.x + self.material.shift.x t = self.material.vAxis.dot( vertPos) / self.material.scale.y + self.material.shift.y # # "normalize" the texture coordinates # vert.uv.x = s / self.material.material.size.x vert.uv.y = -t / self.material.material.size.y self.calcTangentSpaceAxes() if self.hasGeometry: self.modifyGeometryUVs() def calcTangentSpaceAxes(self): # # Create the axes from texture space axes # plane = self.getWorldPlane() normal = plane.getNormal() self.material.binormal = Vec3(self.material.vAxis).normalized() self.material.tangent = normal.cross( self.material.binormal).normalized() self.material.binormal = self.material.tangent.cross( normal).normalized() # # Adjust tangent for "backwards" mapping if need be # tmp = self.material.uAxis.cross(self.material.vAxis) if normal.dot(tmp) > 0.0: self.material.tangent = -self.material.tangent def modifyGeometryUVs(self): # Modifies the geometry vertex UVs in-place twriter = GeomVertexWriter(self.vdata, InternalName.getTexcoord()) tanwriter = GeomVertexWriter(self.vdata, InternalName.getTangent()) bwriter = GeomVertexWriter(self.vdata, InternalName.getBinormal()) for i in range(len(self.vertices)): twriter.setData2f(self.vertices[i].uv) tanwriter.setData3f(self.material.tangent) bwriter.setData3f(self.material.binormal) def minimizeTextureShiftValues(self): if self.material.material is None: return # Keep the shift values to a minimum self.material.shift.x %= self.material.material.size.x self.material.shift.y %= self.material.material.size.y if self.material.shift.x < -self.material.material.size.x / 2: self.material.shift.x += self.material.material.size.x if self.material.shift.y < -self.material.material.size.y / 2: self.material.shift.y += self.material.material.size.y def classifyAgainstPlane(self, plane): front = back = onplane = 0 count = len(self.vertices) for vert in self.vertices: test = plane.onPlane(vert.getWorldPos()) if test <= 0: back += 1 if test >= 0: front += 1 if test == 0: onplane += 1 if onplane == count: return PlaneClassification.OnPlane if front == count: return PlaneClassification.Front if back == count: return PlaneClassification.Back return PlaneClassification.Spanning def flip(self): self.vertices.reverse() self.plane = Plane.fromVertices(self.vertices[0].pos, self.vertices[1].pos, self.vertices[2].pos) if self.hasGeometry: self.regenerateGeometry() def regenerateGeometry(self): # # Generate vertex data # numVerts = len(self.vertices) vdata = GeomVertexData("SolidFace", getFaceFormat(), GeomEnums.UHStatic) vdata.uncleanSetNumRows(len(self.vertices)) vwriter = GeomVertexWriter(vdata, InternalName.getVertex()) twriter = GeomVertexWriter(vdata, InternalName.getTexcoord()) nwriter = GeomVertexWriter(vdata, InternalName.getNormal()) tanwriter = GeomVertexWriter(vdata, InternalName.getTangent()) bwriter = GeomVertexWriter(vdata, InternalName.getBinormal()) for i in range(len(self.vertices)): vert = self.vertices[i] vwriter.setData3f(vert.pos) twriter.setData2f(vert.uv) nwriter.setData3f(self.plane.getNormal()) tanwriter.setData3f(self.material.tangent) bwriter.setData3f(self.material.binormal) # # Generate indices # # Triangles in 3D view prim3D = GeomTriangles(GeomEnums.UHStatic) prim3D.reserveNumVertices((numVerts - 2) * 3) for i in range(1, numVerts - 1): prim3D.addVertices(i + 1, i, 0) prim3D.closePrimitive() # Line loop in 2D view.. using line strips prim2D = GeomLinestrips(GeomEnums.UHStatic) prim2D.reserveNumVertices(numVerts + 1) for i in range(numVerts): prim2D.addVertex(i) # Close off the line strip with the first vertex.. creating a line loop prim2D.addVertex(0) prim2D.closePrimitive() # # Generate mesh objects # geom3D = SolidFaceGeom(vdata) geom3D.setDrawMask(VIEWPORT_3D_MASK) geom3D.setPlaneCulled(True) geom3D.setPlane(self.plane) geom3D.addPrimitive(prim3D) self.index3D = self.solid.addFaceGeom(geom3D, self.state3D) geom3DLines = SolidFaceGeom(vdata) geom3DLines.addPrimitive(prim2D) geom3DLines.setDrawMask(VIEWPORT_3D_MASK) geom3DLines.setDraw(False) self.index3DLines = self.solid.addFaceGeom(geom3DLines, self.state3DLines) geom2D = SolidFaceGeom(vdata) geom2D.addPrimitive(prim2D) geom2D.setDrawMask(VIEWPORT_2D_MASK) self.index2D = self.solid.addFaceGeom(geom2D, self.state2D) self.geom3D = geom3D self.geom3DLines = geom3DLines self.geom2D = geom2D self.vdata = vdata self.hasGeometry = True def delete(self): for vert in self.vertices: vert.delete() self.vertices = None self.id = None self.material.cleanup() self.material = None self.color = None self.index2D = None self.index3D = None self.index3DLines = None self.geom3D = None self.geom3DLines = None self.geom2D = None self.state3D = None self.state3DLines = None self.state2D = None self.solid = None self.isSelected = None self.plane = None self.vdata = None self.hasGeometry = None