Researching Project Feasibility...

I’m looking into whether P3D can handle the requirements for a small project I’m thinking of. Basically it’s a ‘map editor’ for modding a game.

To do this I’d need to load a large (.obj) mesh (say around 40k tris) at run-time and manipulate it’s vertexes to allow the user to alter it’s shape (so I need to be able to tell exactly what face/vertex is being interacted with by the user/mouse).

Is this possible with a reasonable level of effort? (Performance wise do I need C++ or does Python cut it?)

So far looking around I’m impressed with the website and documentation here so hopefully this can be done!

In short yes. This section of the manual will probably help. You can do it in python or c++m or with geometry shader.

Nice, thanks, that’s exactly the lower-level info I need. I also must say, I’m very impressed with the community here.

I think my main concern is performance using Python since I’m sure my requirements go beyond Flash 3d (non accel) capabilities, so I’m trying to avoid hitting another performance brick wall.

Python is slower than C++, but in most cases it is fast enough. If you do find it to be too slow you can also switch.

Also remember that graphics cards can’t handle more than a couple hundred geometry objects at a time. It can do millions of polygons, but throw in 500 separate triangles it will slow even the most high end to a crawl.

Also panda only uses .egg for meshes (and it’s binary version, .bam). If you need the .obj format you will need to convert to and from.

First, I think that Panda3D is suited for your project.

However, re-writing a single mesh with 40k vertices each frame (using e. g. GeomVertexRewriter) is likely something no engine can handle out of the box if you want to use Python and not C++.

I think you will have to break your single mesh down into smaller pieces to get acceptable response (framerate). You can do so after loading the mesh and join the pieces again into a single mesh before saving.

I’ll be exporting to a completely non-standard format so I’ve no problem with also reading manually (.obj without coding it myself would be a nice to have; I’ve found some example code already.)

Breaking the mesh up is something I’ve considered might need to be done, but I’m trying to avoid it if I can. Actually I’ll have an average of about 20-25k vertices, 40-45k faces (not 40k vert), but that probably doesn’t help much.

Right now I’m working on learning Python, it’s been awhile since I’ve used C++ but I used to know it very well, so that’s an option if needed.

Perhaps my lack of vast experience in 3D is showing here, but this terminology has me confused. Do you mean 500 separate meshes? Not sure what you mean by 500 separate triangles vs millions of polys.
I have (ex):
19881 vertices, 39200 faces

I’ve always roughly considered “face” == “triangle” == “poly” (although this could be quads), is this inaccurate?

Thanks!

Separate meshes.

manipulating single vertices should be no problem performance wise.even on big meshes.

Ok - so here’s a tougher one. Am I going to be able to detect the closest vertex to a mouse click event, or does the engine limit you to detecting clicks ‘somewhere’ on a GeomNode in general? At first glance there doesn’t seem to be any fine-grained event feedback to help with this.

while i dont think you can directly get the vertices from within python. there definetly is a collision-visualisation routine in panda wich highlightsall triangles and edges which are taken into collision account (also highlighting the triangles it actually collides with). this code definetly contains what you need in one way or another.

You are able to get the picked coordiates when you click on an object. All what is needed is to look up what vertex is close to it. pickedPoint in getCollision() should interest you.

class Picker(DirectObject.DirectObject):
    def __init__(self, show=False):
        self.accept('makePickable', self.makePickable)
        #create traverser
        #base.cTrav = CollisionTraverser()
        self.cTrav = CollisionTraverser()
        #create collision ray
        self.createRay(self,base.camera,name="mouseRay",show=show)

    def getMouseCell(self):
        """Returns terrain cell coordinates (x,y) at mouse pointer"""
        #get mouse coords
        if base.mouseWatcherNode.hasMouse()==False: return
        mpos=base.mouseWatcherNode.getMouse()
        #locate ray from camera lens to mouse coords
        self.ray.setFromLens(base.camNode, mpos.getX(),mpos.getY())
        #get collision: picked obj and point
        pickedObj,pickedPoint=self.getCollision(self.queue)
        #call appropiate mouse function (left or right)
        if pickedObj==None: return
        cell=(int(math.floor(pickedPoint[0])),int(math.floor(pickedPoint[1])))
        return cell
    
    def getCenterCoords(self, nodePath = render):
        '''Returns terrain cell coordinates (x,y) that is at center of camera'''
        self.ray.setFromLens(base.camNode, 0, 0)
        #get collision: picked obj and point
        pickedObj,pickedPoint=self.getCollision(self.queue, nodePath)
        return pickedPoint
    
    def getCoords(self):
        #get mouse coords
        if base.mouseWatcherNode.hasMouse()==False: return
        mpos=base.mouseWatcherNode.getMouse()
        #locate ray from camera lens to mouse coords
        self.ray.setFromLens(base.camNode, mpos.getX(),mpos.getY())
        #get collision: picked obj and point
        pickedObj,pickedPoint=self.getCollision(self.queue)
        return pickedPoint
    
    
    def getCollision(self, queue, nodePath = render):
        """Returns the picked nodepath and the picked 3d point.
This function not inteded to be called directly, use a get*() function instead.
"""
        #do the traverse
        #base.cTrav.traverse(render)
        self.cTrav.traverse(nodePath)
        #process collision entries in queue
        if queue.getNumEntries() > 0:
            queue.sortEntries()
            for i in range(queue.getNumEntries()):
                collisionEntry=queue.getEntry(i)
                pickedObj=collisionEntry.getIntoNodePath()
                #iterate up in model hierarchy to found a pickable tag
                parent=pickedObj.getParent()
                for n in range(1):
                    if parent.getTag('pickable')!="" or parent==render: break
                    parent=parent.getParent()
                #return appropiate picked object
                if parent.getTag('pickable')!="":
                    pickedObj=parent
                    pickedPoint = collisionEntry.getSurfacePoint(pickedObj)
                    return pickedObj,pickedPoint
        return None,None

    def makePickable(self,newObj,tag='true'):
        """sets nodepath pickable state"""
        newObj.setTag('pickable',tag)
        #print "Pickable: ",newObj,"as",tag
    
    """creates a ray for detecting collisions"""
    def createRay(self,obj,ent,name,show=False,x=0,y=0,z=0,dx=0,dy=0,dz=-1):
        #create queue
        obj.queue=CollisionHandlerQueue()
        #create ray
        obj.rayNP=ent.attachNewNode(CollisionNode(name))
        obj.ray=CollisionRay(x,y,z,dx,dy,dz)
        obj.rayNP.node().addSolid(obj.ray)
        obj.rayNP.node().setFromCollideMask(GeomNode.getDefaultCollideMask())
        #base.cTrav.addCollider(obj.rayNP, obj.queue)
        self.cTrav.addCollider(obj.rayNP, obj.queue)
        if show: obj.rayNP.show()

Wow, that’s great. I have the ‘nonstandard’ model format loading and displaying correctly, my main challenge will be user interactivity it seems, but this is a good start. Thanks!