PandaSteer 2

Return to Code Snippets

PandaSteer 2

Postby chombee » Tue Feb 27, 2007 10:58 am

Edit 2009-05-16: I've uploaded the all latest PandaSteer code (including contributions) to my github account: http://github.com/seanh/PandaSteer/ Thanks to Astelix for packaging the code.

Screenshot

Movie

I've been updating my previous steering behaviors demo to steer animated, 3D characters over uneven terrain. These steering behaviors can be used to create point-and-click keyboard or mouse controlled player avatars, or mobile non-player characters. They allow a character to move around an environment in an intentional way -- going to some location, following or pursuing or evading another character, and avoiding obstacles and collisions with other characters.

There's still work to be done on this, particularly some efficiency considerations, but they say release early, release often, so here it is. So far I have seek and flee, pursue and evade, arrive, follow, wander, and collision avoidance translated from the old version. Still need to do obstacle avoidance. Update: the code is much more efficient now (thanks to ynjh_jo and drwr) and obstacle avoidance steering is finished

PandaSteer is plugin-oriented, so you can easily write your own steering demos and steering behaviors with PandaSteer, without having to modify PandaSteer itself.

Thanks to ThomasEgi, pro-rsoft and others for their help on the IRC channel, and ynjh_jo, drwr and others on the forums. And thanks to tiptoe, ynjh_jo and cyan for their work in this thread which I've studied and found very useful.

As well as steering, this demo also has:

* The diamond-square algorithm for heightfield generation, implemented in Python.

* Using Panda's Egg interface to construct a terrain model

* Lighting and fog

* Using Panda's collision system

* Starting and stopping different Actor animations and varying the animation playrate depending on what the character is doing

* Keyboard controls, including controlling the camera

* and probably other stuff I'm forgetting
Last edited by chombee on Sat May 16, 2009 10:20 am, edited 6 times in total.
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby ynjh_jo » Tue Feb 27, 2007 2:49 pm

# FIXME: Add a loader class for loading character models, so that models can be duplicated instead of reloaded if they are already loaded.

Don't actors use loadModelCopy as default ?
def loadModel(self, modelPath, partName="modelRoot", lodName="lodRoot", copy=1)

loadModel(self, string, string="modelRoot", string="lodRoot", bool = 0) Actor model loader. Takes a model name (ie file path), a part name(defaults to "modelRoot") and an lod name(defaults to "lodRoot"). If copy is set to 0, do a loadModel instead of a loadModelCopy.

:shock: Have you visualize the collisions ? "base.cTrav.showCollisions(render)"
It would help seeing these flaws :
1. only the last defined Character has collision detection. Increase the terrain height and you'll see. (->FIXED)
2. collision ray's origin too low, the origin too easily sinks under the terrain (->FIXED, now using segment to avoid collision detection against many more triangles underneath the character)
3. collision (ray) against the actor's geometry (->FIXED)
4. TOTAL[memory leakage (5MB in each complete plugins cycle) + performance loss upon plugin switch], garbage-collecting doesn't work, because you only use detachNode on Character instances, not removeNode. To use removeNode, first remove :
- the colliders and floorhandlers
- characters' step task
- the actor
To remove them, first, the references must be kept. (->FIXED)

new steer.py
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby chombee » Wed Feb 28, 2007 8:05 am

Wow, great work ynjh_jo!

I've merged your fixes into my branch, they were spot on, every one of them :)

Your fixes have revealed another serious problem though: using a CollisionSegment (or CollisionRay) and CollisionHandlerFloor for each Character seems to be very inefficient.

On my machine (2.4GHz, 1GB RAM, Nvidia Quadro4 900XGL, using proprietary Nvidia driver on Ubuntu 6.10) the "wandering pedestrians" demo, which has 10 Characters, now runs at 5fps. If I disable the CollisionFloorHandlers it runs at a nice 30.5fps. Even the "arrive" demo with one character runs at about 30fps with the CollisionHandlerFloor, about 70fps without.

Ultimately I'd like to have 10-12 Characters, each using different models, and on a much larger terrain with some static models for obstacles too, and it'll need to run at at least 15fps, preferably 30fps. So the current situation is not encouraging.

I tried having a single global CollisionHandlerFloor shared by all Character's, with each Character adding its own CollisionSegment. But this was no improvement.

Surely colliding a single line segment against some simple terrain should be pretty cheap? There must be a more efficient way to do this?

Perhaps I can write my own query function which returns the Z-position of the terrain at a given (X,Y)-position, and simply use this instead of Panda's collision system.

But I'd appreciate any help with this issue.

More improvements to come from me too! Perhaps when I recover from the cold I'm coming down with...
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby drwr » Wed Feb 28, 2007 8:57 am

In my experience, whenever I've had any problems with performance of collisions, it's generally because my collision solids are attempting to collide with far more than I thought they were.

How simple is your floor terrain? Dozens of polygons? Or thousands? Thousands of polygons look nice for rendering, but you really only want to collide with dozens. If you haven't done it already, you may need to have a reduced-complexity version of your floor for the purposes of collisions.

Also, the showCollisions() step is useful for lighting up the polygons that it's testing against. Make sure that it's only a handful.

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby chombee » Wed Feb 28, 2007 9:23 am

Hmm.. I had wondered if I would need to generate my terrain to be more efficient. But it is a pretty small and simple terrain in this demo, dozens or maybe hundreds of polys, and each CollisionSegment is only colliding with a handful.

Screenshot showing wireframe and collisions: http://www.panda3dprojects.com/chombee/ ... isions.jpg
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby drwr » Wed Feb 28, 2007 10:22 am

dozens or maybe hundreds of polys

Really? It looks like a 32 x 32 grid in the screenshot. That's 1,024 quads--2,048 triangles. And since it's all one mesh, the collision traverser will have to test each of those 2,048 trianglse against each CollisionRay.

At the very least, you should subdivide the mesh into several smaller pieces, so the collision traverser can rule out large parts of it based on the bounding volume.

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby ThomasEgi » Wed Feb 28, 2007 12:13 pm

how about adding a internal octree-like structure for collidable meshes if a certain number of vertices is reached? subdivide them in groups and subdivide them again if a sub-group is still above the limit. (just as idea for some future panda versions).
could help with the collission detection of detailed meshes.
might be intresting for culling/clipping of large objects like environments etc. so you wouldnt have to splitt it ourself while creating the stuff.
my idea would've been something like
model = loader.loadOctreeModel("path/to/model",optional vertex count for splitting groups)
User avatar
ThomasEgi
 
Posts: 2149
Joined: Fri Jul 28, 2006 10:43 am
Location: Germany,Koblenz

Postby ynjh_jo » Wed Feb 28, 2007 1:33 pm

This is the result (my edited code above) on my 2.8GHz HyperThreading :
- 1 arrive char : 70 fps
- 10 wandering chars : 8 fps

Then I created 2 terrains, 1 for rendering, 1 for collision. The one for collision consists of a bunch af geomnodes, 1 geomnode built of simply 1 rectangle.

Result :
- 1 arrive char : 165 fps
- 10 wandering chars : 28 fps

in makeTerrain (scene.py):
Code: Select all
    # Now create a terrain model from the heightmap.
    tnp = render.attachNewNode("Terrain")
    data4Rendering = EggData()
    vp = EggVertexPool('Terrain vertex pool')
    # for collision geometries
    tCollNP = tnp.attachNewNode("Terrain4Collision")
    for i in range(0,len(midpoints)-1):
        for j in range(0,len(midpoints)-1):
            # vertexpool for 1 collision geomnode
            vCollpool = EggVertexPool('TerrainCollision vpool')
            bl = Point3D(i,j,midpoints[i][j])
            tr = Point3D(i+1,j+1,midpoints[i+1][j+1])
            br = Point3D(i+1,j,midpoints[i+1][j])
            tl = Point3D(i,j+1,midpoints[i][j+1])
            poly, polyColl = makeRectangle(vp,vCollpool,bl,tr,br,tl)
            data4Rendering.addChild(poly)
            # data for collision terrain, 1 rectangle per geomnode
            data4Collision = EggData()
            data4Collision.addChild(polyColl)
            rectNode = loadEggData(data4Collision)
            tCollNP.attachNewNode(rectNode)
    pandaNode = loadEggData(data4Rendering)
    tnp.attachNewNode(pandaNode)
    return tnp, tCollNP


makeRectangle (scene.py):
Code: Select all
def makeRectangle(vp,vCollpool,bl=None,tr=None,br=None,tl=None):
    if bl is None: bl = Point3D(-10,-10,0)
    if tr is None: tr = Point3D(10,10,0)
    if br is None:
        l = tr.getX() - bl.getX()
        br = bl + Vec3D(l,0,0)
    if tl is None:
        w = tr.getY() - bl.getY()
        tl = bl + Vec3D(0,w,0)
    poly = EggPolygon()
    polyColl = EggPolygon()
    for corner in [bl,br,tr,tl]:
        v = EggVertex()
        v.setPos(corner)
        vC = EggVertex()
        vC.setPos(corner)
        poly.addVertex(vp.addVertex(v))
        polyColl.addVertex(vCollpool.addVertex(vC))
    poly.recomputePolygonNormal() # Use faceted not smoothed lighting
    return poly, polyColl


SteerTest (steer.py) :
Code: Select all
        # Make some terrain and colour, scale and position it.
        tnp, terrain4Coll = makeTerrain(h=8)
        tnp.setColor(0.6,0.8,0.5,1)
        tnp.setPos(tnp,-50,-50,0)
        tnp.setScale(3)
        terrain4Coll.setCollideMask(floorMASK)
        terrain4Coll.hide()

:D It's BLAAZZZZIN' now !
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby drwr » Wed Feb 28, 2007 2:42 pm

Note that, as long as you are creating geometry specifically for collisions, you might as well create CollisionNodes instead of GeomNodes (which are optimized for collisions instead of rendering, and so are faster for the collision test).

Easiest way to do this, since you are already using the egg library, is to put the CollisionPolygons under a GroupNode, and add the "barrier" object type to that group, like this:
Code: Select all
data4Collision = EggData()
gn = EggGroup('group')
data4Collision.addChild(gn)
gn.addObjectType('barrier')
gn.addChild(polyColl)
rectNode = loadEggData(data4Collision)

Note that optimal number of quads per group is probably a bit more than one. :)

David
drwr
 
Posts: 11425
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby chombee » Wed Feb 28, 2007 3:31 pm

Well, I was planning to rewrite the terrain model generation function to group the nodes in an octree, but it looks like you are well ahead of me! Those framerates sound fantastic ynjh_jo, and thanks for the hints drwr :)

I think I get the gist of what you have done with the terrain generation, but I won't be sure I understand it until I can run the code and visualize your collision terrain. I'll try it out next time I'm at my (fast) computer.

I have been working on improvements of my own, making the demo interactive through mouse-clicks to control the characters, and updating the obstacle avoidance behaviour from the last version. It's simple enough with the existing code in vehicle.py to get some obstacles into the scene and get the characters avoiding them, but although I think the steering is fine the collision detection routines I wrote for it are not too robust :) I feel it needs to be updated to work with Panda's collision detection system instead, which I'm starting to get the hamg of. I should have a significant update before too long. In the meantime, feel free to jump ahead of me :)

Thanks again for getting involved! You've really improved this code. When it's finished I think this code could be a really great thing to have available on the forums. As well as the instructive steering behaviours demo and code for reuse, I could put together an example game using it. I'm thinking of a scene with some terrain, trees, buildings, streetlamps, etc., and about 12 characters with different models wandering around (or maybe it could be some sort of weird clone town of Ralph people). The player controls one character by pointing and clicking and the camera follows, and the player can walk up to any of the non-player characters and initiate a 'conversation', which would be drawn in speech bubbles using DirectGUI. The player could give the NPCs instructions like 'Follow character X', 'Chase character X', 'Run away from character X', 'Go get X and bring it/him/her to me', etc. So it wouldn't really be much of a game, but it would be a pretty good base on which to build an adventure-style game.
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby ynjh_jo » Thu Mar 01, 2007 2:34 am

I splitted the collision terrain into small pieces, currently 3x3 grid of quads, and it's adjustable.
screenshot showing the rendered & randomly colored 3x3 quads collision nodes.
Code: Select all
    # Now create a terrain model from the heightmap.
    tnp = render.attachNewNode("Terrain")
    data4Rendering = EggData()
    vp = EggVertexPool('Terrain vertex pool')
    # nodepath to hold collision nodes
    tCollNP = tnp.attachNewNode("Terrain4Collision")
    # supply the eggGroup & eggVPool for the first node
    vCollpool = EggVertexPool('TerrainCollision vpool')
    collGroup = EggGroup('collQuads')
    collGroup.addObjectType('barrier')
    # we use (numQuadGrid x numQuadGrid) quads for 1 collision node
    numQuadGrid=3
    # the modulo of (size-2)/numQuadGrid, to mark when the quads must be built into 1 geom
    edgeMod=(size-2)%numQuadGrid
    for i in range(0,len(midpoints)-1, numQuadGrid):
        # limit nextIrange to avoid it from jump over the edge
        nextIrange=min(numQuadGrid,len(midpoints)-1-i)
        for j in range(0,len(midpoints)-1):
            for nextI in range(0,nextIrange):
                bl = Point3D(i+nextI,j,midpoints[i+nextI][j])
                tr = Point3D(i+nextI+1,j+1,midpoints[i+nextI+1][j+1])
                br = Point3D(i+nextI+1,j,midpoints[i+nextI+1][j])
                tl = Point3D(i+nextI,j+1,midpoints[i+nextI][j+1])
                poly, polyColl = makeRectangle(vp,vCollpool,bl,tr,br,tl)
                data4Rendering.addChild(poly)
                # add a quad for collision terrain
                collGroup.addChild(polyColl)
                # build the collision node geom
                if j%numQuadGrid==edgeMod and nextI==nextIrange-1:
                   data4Collision = EggData()
                   data4Collision.addChild(collGroup)
                   rectNode = loadEggData(data4Collision)
                   cNP=tCollNP.attachNewNode(rectNode.getChild(0).getChild(0))
                   # uncomment the next line to see the collision geom
                   #cNP.show()
                   # supply the eggGroup & eggVPool for the next node
                   collGroup = EggGroup('collQuads')
                   collGroup.addObjectType('barrier')
                   vCollpool = EggVertexPool('TerrainCollision vpool')
    pandaNode = loadEggData(data4Rendering)
    tnp.attachNewNode(pandaNode)
    return tnp, tCollNP

Thanks for the pointer, David, now it can hit 39 fps ! :D
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby chombee » Thu Mar 01, 2007 8:10 am

ynjh_jo: just tried out both of your efficiency improvements. Although I'm not sure I understand your code exactly, it's a major enhancement! I'm getting a smooth 30FPS with 10 characters on screen at once now, and 65-75FPS in all the other demos (with 1-3 characters). Fantastic work!

Let me see if I understand your enhancements correctly.

So for your first improvement, you create two terrain models, one for rendering and one for colliding, instead of just using the renderable terrain for collisions. The collision terrain is constructed slightly differently from the rendering terrain, because the render terrain is all one big model, it's all in one EggData. Whereas with the new collision terrain, you made a separate model, a separate EggData and EggVertexPool, for every four-vertex block of the terrain. And this is the reason for the speed improvement? Do I have it right?

Then Panda would only need to test the bounding box of each of those collision models, and can discard most of them without actually having to test against the polygons, right?

And for your second improvement, if I understand correctly, you have collected every 3x3 group of collision models together under an EggGroup? This allows Panda to discard entire EggGroups at once by testing against the bounding box of the whole group, so is even faster. Do I have it right?

I think that if even more efficiency was needed for a larger terrain, these EggGroups could be further collected into groups of EggGroups, forming a hierarchical structure like an octree. But that doesn't seem necessary at the moment, as the framerates I'm already getting with your improvements are more than enough.

Thanks again for the great work!
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby ynjh_jo » Thu Mar 01, 2007 8:46 am

Yes, I believe Panda's collision system works on geomnode level, checking the bounding vol first before testing any intersection with the polys.
uhm..., sorry, I edited the last code above, check it again if you'd like to compare it.
Maker 4 terrain nodepath objects ....... giving a big terrain with variable hilly and flat areas (shame about the cracks at the joins though).

I just noticed that the number of cracks increases as the hilliness. Then I realized it's not your code's fault, it's the work of Panda's auto triangulation against concave polygons. Some of the quads must not be triangulated correctly.
So, I just created those tris manually, and no more cracks now. Weird, this old flaw is still here.....
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby chombee » Thu Mar 01, 2007 9:19 am

Sorry, which code did you edit? I don't see it.
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby ynjh_jo » Thu Mar 01, 2007 10:30 am

It's the 3x3 splitting. Lucky me, it wasn't exposed then... :lol: :lol:
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby chombee » Thu Mar 01, 2007 10:45 am

Right, uploaded a new version of the whole thing: Download.

The download link in my first post has been updated too.

This new release includes all of ynjh_jo's improvements up to this point, plus:

* Now supports mouse-clicking. Click event is passed to plugin. Some of the plugins allow you to control a character by clicking, others ignore it. Picking code is taken from Tiptoe, Cyan and ynjh_jo elsewhere on this forum.

* For some steering behaviours the target that the character is steering towards is drawn (as an orange jack-o-lantern). This is achieved by merging class Marker from some code that Tiptoe, Cyan and ynjh_jo worked on elsewhere in this forum

* Plugins are now loaded in order of filename, so you can control the plugin order by changing their filenames

* Added pandaSteerPlugin.py with 'abstract' class PandaSteerPlugin that all plugins should inherit from (just structural cleanup really)

* Other minor changes and bugfixes

* Correction of some outdated comments

I'm still working on porting the obstacle avoidance code.

Many thanks to everyone that's given tips or enhancements or commented on IRC, or whose code I've made use of. Wouldn't be here without the Panda3D community! :)
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby ynjh_jo » Thu Mar 01, 2007 12:15 pm

That's nice, but....you haven't fixed the terrain cracks !
Code: Select all
                poly1,poly2, polyColl1,polyColl2 = makeTriangles(vp,vCollpool,bl,tr,br,tl)
                data4Rendering.addChild(poly1)
                data4Rendering.addChild(poly2)
                # add a quad for collision terrain
                collGroup.addChild(polyColl1)
                collGroup.addChild(polyColl2)


I changed makeRectangle to makeTriangles, added 1 more loop of course :
Code: Select all
    poly1 = EggPolygon()
    poly2 = EggPolygon()
    polyColl1 = EggPolygon()
    polyColl2 = EggPolygon()
    for corner in [bl,br,tr]:
        v = EggVertex()
        v.setPos(corner)
        vC = EggVertex()
        vC.setPos(corner)
        poly1.addVertex(vp.addVertex(v))
        polyColl1.addVertex(vCollpool.addVertex(vC))
    for corner in [bl,tr,tl]:
        v = EggVertex()
        v.setPos(corner)
        vC = EggVertex()
        vC.setPos(corner)
        poly2.addVertex(vp.addVertex(v))
        polyColl2.addVertex(vCollpool.addVertex(vC))
    poly1.recomputePolygonNormal() # Use faceted not smoothed lighting
    poly2.recomputePolygonNormal()
    return poly1,poly2, polyColl1,polyColl2


.... and remove "terrain4Coll.hide()" (in steer.py), or else the collision terrain would be never showed up (it overrides the show() in scene.py).
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby chombee » Fri Mar 02, 2007 3:41 pm

Ok, updated again: download.

Added obstacle avoidance steering behaviour and a couple more plugins to demonstrate it. The code is pretty rough at the moment but obstacle avoidance works really well with Panda's collision system, it's very fast and robust, I'm pretty happy with it.

I used a Panda CollisionTube, projected ahead of each character, to detect upcoming collisions with obstacles, represented as Panda CollisionSpheres. Since the manual says CollisionTube is good only as an into object I used it as such, and used the static CollisionSpheres as from objects, even though conceptually, the CollisionTube is the thing that's moving so would seem the logical choice as the from object. When a collision occurs between a character's CollisionTube and a CollisionSphere, a steering force is applied to steer away from the obstacle.

Some work needs to be done:

* If the target a character is trying to arrive at (for example) is inside an obstacle the character will run round and round the obstacle

* Worse, if the target is just in front of an obstacle, the character may arrive at the target and stop, but then the CollisionTube may be colliding with the obstacle, which will cause the character to run around needlessly trying to avoid the obstacle

The best way to fix this I think would be to vary the length of the character's CollisionTube frame-by-frame according to the character's speed, so that when the character comes to rest at a target location the CollisionTube will shrink to a sphere around the character and not collide with any obstacles beyond the target. I had trouble implementing this though and have given up for now.

The collision avoidance (that is, avoiding other moving characters, not static obstacles) behaviour could be improved a bit. It does not use pandas collision system to detect upcoming collisions but my own code, and collisions seem to occur fairly often with a lot of characters in a small space. Is it possible to collide two CollisionTubes against eachother? If so, the characters CollisionTubes could be used to detect character-character collisions also.

The 'follow' steering behaviour needs improvement. The character needs to follow a good distance behind the target so as not to collide with the target, needs to avoid blocking the path of the target, and needs to avoid bumping into other followers. Ultimately it should be more like the demo here: http://www.red3d.com/cwr/steer/LeaderFollow.html

yjnh_jo: thanks a lot for your fix for the cracks in the terrain. It's definitely going to be crucial for me in the future. I haven't had time to merge the fix into my own download yet, but will do so as soon as I can.

Thanks, and enjoy! :)
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Great code

Postby mavasher » Sat Mar 03, 2007 12:44 pm

I've downloaded your steer code. Just want to say great work. I have a couple of questions/suggestions.

-The obstacle class currently only allows spheres. it seems like boxes or rectangles would be more useful for many applications.

-There are many applications for the current 2D steer class. Have you thought about making a separate 3D steer class with the same behaviors?

keep up the good work.
User avatar
mavasher
 
Posts: 295
Joined: Thu Feb 08, 2007 10:32 am
Location: Texas

Postby chombee » Sat Mar 03, 2007 1:31 pm

Another update. The Panda3DProjects site where I put all my downloads seems to be down right now, so this download is on my own server: download.

Changes:

* I fixed a bug in the collision avoidance code. The characters thought they had radius 1, but it should be more like 3. They're much better at not bumping into each other now, Collisions are pretty rare even in the very crowded demo.

* I merged ynjh_jo's latest fix, preventing cracks from appearing in the terrain. If you run scene.py you'll get a test scene that generates 4 terrain models with different hilliness values and chains them together into one terrain. There's some very incomplete DirectGUI stuff thrown in for playing with the fog and lighting. There are no cracks in the models now. Thanks again ynjh_jo!

(P.S. to get the steering demo, run steer.py).

I feel obliged to point out again that this steering code is heavily derived from the C++ steering code of OpenSteer. A fair number of my functions, particularly with collision avoidance steering, are just translated from opensteer to python/panda and then tuned.

PandaSteer seems to be working pretty well now. The code still needs much cleaning up inside, and I'm sure there are bugs I've yet to find. There is the issue of the Character's CollisionTube varying in length frame by frame depending on the Character's speed, that should be possible with CollisionTube.setPointB but I haven't got it right yet. That would be a good fix.

Enjoy!

ynjh_jo: the terrain looks different now with your fix. It's made of pairs of triangles instead of rectangles, and so is shaded differently, which slightly spoils the blocky graphical style I was going for. Is it possible to build the terrain out of rectangles instead of triangles, and not get cracks? Or does your fix rely on changing to triangles instead of rectangles?
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby chombee » Sat Mar 03, 2007 1:40 pm

mavasher: thanks for trying out the code and getting back to me :)

In theory the Character's should be able to steer to avoid any 'from' object in the Panda collision system, which could be a sphere, inverse sphere, or plane, and you could use six planes together to make a cube or rectangle. It is just a case of writing new obstacle classes on the model of the existing sphere obstacle class. All that matters to the Character is that it gets a CollisionEvent with a position and surface normal for the collision from the Panda collision system, so you shouldn't need to edit Character to get it to steer round obstacles of different shapes. In theory.

Personally I think spheres will work fine. You can roughly make any shape you want by chaining many spheres together.

For my purposes I only need 2D steering over uneven terrain. If you wanted 3D steering for flying vehicles and the like, much of the code would translate very easily, just replace Vec2 with Vec3, but some of the functions in vehicle.py and steerVec.py contain linear algebra that may get a lot more difficult in 3D compared to 2D. I'm probably not going to do 3D myself as I don't need it.
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby ynjh_jo » Sat Mar 03, 2007 3:47 pm

Is it possible to build the terrain out of rectangles instead of triangles, and not get cracks?

Alright, go back to your makeRectangle, it can be fixed without creating the triangles directly.
First, we have to check the triangulation process, if it failed, simply reverse it's vertex order and disable backface culling for that quad only. Thus we still get (double-sided) convex quad, not concave one :lol: .
Insert these lines after creating the quad :
Code: Select all
    egn=EggGroupNode()
    if not poly.triangulateInto(egn,0):
       print 'triangulation failed...',
       poly.reverseVertexOrdering()
       poly.setBfaceFlag(1)
       polyColl.reverseVertexOrdering()
       polyColl.setBfaceFlag(1)
       print ' , but fixed :)'
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Sun Mar 04, 2007 11:15 am

I've been playing around with animations switch. I think synchronizing the next anim's start frame to the current one's frame must help to avoid sudden weird body parts movement, especially the legs. No blending here, so it's still rough.
And I tried playing the walk anim when switching to stand pose, instead of simply posing the actor. The start frame uses the walk anim's current frame, but "mirrored" relative to the middle of the anim's number of frames. It's to get the start frame for the shortest anim length to the stand pose frame (i.e. frame 6 for ralph, as you've used since the beginning), while maintaining body parts' position as close as possible to the current walk frame.

To get the most acceptable result, I switched it to stand pose after the velocity length went below .05. Less than that, the actor will slide for a while (due to small playrate) before playing the anim.
Code: Select all
    def storeLastPose(self):
        currAnim=self.actor.getCurrentAnim()
        numFrames=self.actor.getNumFrames(currAnim)
        animFrame=self.actor.getCurrentFrame(currAnim)
        self.lastPose=float(animFrame)/float(numFrames)
        self.actor.stop(currAnim)
        #print currAnim, self.lastPose

    def loopFromPose(self,animName):
        self.actor.pose(animName, frame=self.lastPose*self.actor.getNumFrames(animName))
        self.actor.loop(animName, restart=0)

    # FSM State handlers. Called when transitioning to a new state.
    def enterRun(self):  self.loopFromPose("run")
    def exitRun(self):   self.storeLastPose()
    def enterWalk(self): self.loopFromPose("walk")
    def exitWalk(self):  self.storeLastPose()
    def enterStand(self):
        standPoseFrame=6    # frame 6 (the most acceptable stand pose)
        numFrames=self.actor.getNumFrames("walk")
        lastFrame=self.lastPose*numFrames
        # "mirror" the frame to bring it closer to the most acceptable stand pose
        if lastFrame>.5*(numFrames-1):
           lastFrame=numFrames-1-lastFrame
        #----------------------------------------------
        frameDiff=standPoseFrame-lastFrame
        # if already at stand pose, don't do anything
        if frameDiff==0:
           return
        # forward animation playback
        if frameDiff>=0:
           fromFrame=lastFrame
           toFrame=standPoseFrame
        else:
        # backward animation playback
           fromFrame=standPoseFrame
           toFrame=lastFrame
        #----------------------------------------------
        playDir=2*frameDiff/numFrames
        self.actor.setPlayRate(playDir,"walk")
        self.actor.play("walk", fromFrame=fromFrame, toFrame=toFrame)
        #print 'switch to stand pose'
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Mon Mar 05, 2007 7:36 am

Aow..., after you added collision sphere around each char and added sphere obstacles, why haven't you done clean removal of them ? Simply removing/detaching them doesn't do any removal, because the traverser is still using them *ALL*, so there are still references to each of them.
To view the report, try to print out the number of currently active colliders :
Code: Select all
        print 'ACTIVE COLLIDERS :',base.cTrav.getNumColliders()

at the very bottom of restart method.
Then rapidly restart the plugin or switch to the next one.
And you'll see it's increasing 'till thousands (10 chars or +obstacles plugin), and keep watching your memory meter to see your free mem crawling down.

All you need to do is removing them from the traverser :
- in characters removal loop :
Code: Select all
            base.cTrav.removeCollider(character.spherecnp)

- in obstacles removal loop :
Code: Select all
            base.cTrav.removeCollider(obstacle.cnp)


If no leakage, there should be only [ numChars*2 (sphere&segment) + numObstacles + 1 (mouse ray) ] active colliders.
Running memory meter all the time is very helpful and makes me feel better. :D
http://ynjh.tk
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby chombee » Mon Mar 05, 2007 10:48 am

Thanks a lot ynjh_jo, more fantastic work!

Okay, another update: download. (I've moved the download back onto panda3dprojects.com, which seems to be up and running again).

Merged the last three improvements from ynjh_jo, and a couple from me:

* Make the terrain out of rectangles again, not triangles, triangulation of polygons fixed in makeRectangle. Thanks ynjh_jo.

* Improvement to the animation of Ralph from ynjh_jo (see ynjh_jo's explanation above).

* Fix for memory leak from ynjh_jo: Destroy CollisionSphere and CollisionTube of Character and CollisionSphere of Obstacle when destroying Characters and Obstacles. This code really belongs in the Character and Obstacle classes, not in the class that is using Character and Obstacle.

I've made some notes about refactoring the code and have a plan ready, there are many changes I want to make when I have time that will make the code much nicer on the inside, but nothing really major.

* Scaled the size of markers used to show characters' targets, big enough to see but not so big as to get in the way as before
* Fix wrapping of characters when they leave the terrain (it's more accurate now)
* I think I recently shrunk the radius of Character's CollisionTube from 3 to 2.5 (making it fit Ralph more tightly). I don't know why but this seemed to introduce a bug where, in the obstacle avoidance demos, characters would get stuck running round and round the largest obstacle, or even get inside the obstacle. So I changed the radius of the tube back to 3 again and this doesn't happen.

I have to concentrate on some non-Panda3D work now, maybe for a month or two, so major updates might not be coming for a while, though I can still do minor fixes if people post them or point out bugs. I'll definitelty continue to work on pandasteer full time soon.
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby chombee » Thu Mar 08, 2007 11:17 am

Minor update, a few more fixes and improvements from the amazing ynjh_jo and me: download (Update: removed imports of no-longer-existent obstacle.py)

Some minor refactoring and tidying up from me (chombee) and fixes from ynjh_jo. The major refactoring jobs are still to be done.

Me:

* Split out makeHeightMap (diamond-square algorithm) from makeTerrain
* Set self.reticle = None at the start of self.step() in Vehicle, for each
frame, reticle is only drawn if steering behaviour used that frame sets
reticle. Prevents reticle from being drawn in the wrong place some frames.
* Got rid of fromCol function in steer.py (not needed)
* Added destroy functions to SphereObstacle and Character
* Removed toggleSolids function in steer.py (not needed, in time
toggleCollisions should become a general toggleAnnotation function).
* General tidying of code and comments

ynjh_jo:

* Character should switch to the walk animation at speed .05 or below, not .005
* Don't need to remove character.tube from the collision traverser in
character.destroy() (we never added it in the first place).
* Fixed comment describing what's going on with the triangulation workaround in makeQuad()

Note that there's a detailed changelog in changelog.txt in the download.
Last edited by chombee on Thu Mar 08, 2007 11:41 am, edited 1 time in total.
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby rdb » Thu Mar 08, 2007 11:31 am

Man, I can just say - awesome, breathtaking.

Keep up the good work! :)
rdb
 
Posts: 9418
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Postby mavasher » Mon Mar 12, 2007 3:03 pm

Chombee,

I've been working on a 3D version of your code. Things are going well, your code was written such that it's been pretty easy.

I noticed in your latest version you're referencing the Panda collision system instead of your own collision detection.

Using your older collision system as a guide I rewrote the code so that five probes were produced (forward,right,left, up, down) for collision detection instead of the three that Vehicle.py had.

My point in telling you all this is that I see that you're using collision tubes now. It seems like with more vehicles you're going to have some serious performance issues. I wonder if a five-probe-CollisionRay system would work better for performance. I have code for generating four probes each 45 degrees out from the forward vector like a pyramid with the position as the apex.

Also, a somewhat unrelated question. It looks like in the code that you're assigning all the collision objects to the collision Traverser- It looks like the SphereObstacles are "from" objects instead of "into" objects. Is there a reason for this?
User avatar
mavasher
 
Posts: 295
Joined: Thu Feb 08, 2007 10:32 am
Location: Texas

Postby chombee » Mon Mar 12, 2007 3:32 pm

I've been working on a 3D version of your code. Things are going well, your code was written such that it's been pretty easy.


Great! I can't wait to see it :) My code is due for a refactoring, it will be much more clearly written when I have time to finish it up! Maybe you can implement some of the steering behaviours that I haven't yet, such as path following, proper leader following and flocking (separation, alignment and cohesion).

My point in telling you all this is that I see that you're using collision tubes now. It seems like with more vehicles you're going to have some serious performance issues. I wonder if a five-probe-CollisionRay system would work better for performance.


Possibly. I'll worry about that if and when I get performance issues. There's no problem with 10 characters and half a dozen or so obstacles, as in the last demo in the current version, so that's a good sign. I'm using Panda's collision system to collide spheres into tubes, and spheres are the fastest type of collision solid, so I'm hopeful that it will be quite fast. And my experience with the collision ray used to keep my characters feet on the ground and the help I got with the efficiency of that from drwr and ynjh_jo (in this thread) tells me that the key efficiency concern with collisions is to make sure that each collision solid is only being tested against the collision solids it really needs to be tested against, and not a single one more. That and arranging the collision solids in an efficient way in the scene graph, like an octree.

But switching from tubes to rays might be an option for greater efficiency, yes, although I want to stick with tubes if at all possible because they are just perfect for the job. Another possibility would be to use planes, each tube would now be a cuboid represented by six collision planes, that might be fast.

Also, a somewhat unrelated question. It looks like in the code that you're assigning all the collision objects to the collision Traverser- It looks like the SphereObstacles are "from" objects instead of "into" objects. Is there a reason for this?


Yeah, Panda doesn't support using tubes as into objects, only as from, but spheres can be either from or into. So I know it's a little counter-intuitive, but the tubes are the from objects because they have to be.

Any other questions about the code please feel free to ask!
chombee
 
Posts: 244
Joined: Fri Jan 13, 2006 10:09 am

Postby mavasher » Mon Mar 12, 2007 5:59 pm

Come to think about it maybe it would just make the system more complex processing-wise to have to test 5 simple objects (rays) instead of 1 slightly more complex object (a tube).

As far as new behaviors I could probably help. I'm not a programmer really. I'm a medical student and I've just been playing around with panda. I can probably help more with the math than the actual programming.

Path Following: I'm not sure how you would want to implement this. I'd probably pass a list of waypoints to the behavior. Then, pass a variable for whether or not to loop the waypoints or finish at the last one. Once the vehicle got within a certain distance that waypoint could be declared "cleared" and the target could move to the next one. This could be fairly easy. Use "seek" and "obstacle avoid" and cycle through waypoints as the targets.

Proper leader following: You've got most of this coded already. The separation is the only thing that really needs to be added. Separation would probably involve having a larger collision sphere and looking for collisions then applying a force normal to the collision surface.
User avatar
mavasher
 
Posts: 295
Joined: Thu Feb 08, 2007 10:32 am
Location: Texas

Next

Return to Code Snippets

Who is online

Users browsing this forum: No registered users and 0 guests