Modifying (Built-In-)Collision Geometry?

In short, is it feasible to alter polygonal collision geometry, as used by the built-in collision system in a CollisionPolygon object, on the fly (and without greatly impacting performance)?

(The idea being to have collision geometry that changes over time, thus changing the region in which things collide with it.)

I do believe that such can be done in one way or another with visible geometry–this is, after all, how Actors are animated.

However, I don’t know enough about the collision system to know whether it’s feasible there, or whether it would be significantly detrimental to performance…

I understand it like this:

There are the pre built collision shapes like spheres. And there is “custom” and that’s anything you give a mesh to and then the mesh will determine collisions as you would expect.

So. Yes, you can just whip up a new collision body, and switch them out or remove them, etc…

Both prebuilt and custom collision shapes are mostly disconnected from the visible geometry, except for the special case where you create the collision body from the visible mesh. But that’s basically the initialization step and what you do afterwards will work the same. So, splitting a main object into two parts and then deleting the old collision shape and creating new ones from the split parts should work as one would expect.

Thank you for the answer. :slight_smile:

As to hot-swapping collision-shapes, I think that I considered it–and as you say, if one is just performing a once-off change, doing so might well work.

But for geometry that changes over time… surely that would be an inefficient approach…? Creating and destroying colllision-shapes, potentially every frame, rather than just updating the vertices…?

The custom collision shapes work exactly like regular geometry. So if you can update regular geometry, you can do it with collision shapes too. I haven’t looked into it. But if you have something that already does it for graphics, the process should work exactly the same for the custom collision mesh.

The collision system merely loops over the geometry you give it and finds collisions as defined.

The only difference is that you set a collision mask defining how it should collide and to add the shape to the list of things that is being looped over.

I was thinking of some destructible thing where you have a “break” moment where you calculate new parts and it’s just one step, but changing it over time should be possible too.

No idea on the efficiency.

The thing is, I’m not sure that that’s true: I’m not sure that there isn’t some collision-specific processing of the geometry being done.

[edit]
In fact, this suspicion of mine is bolstered by the manual:

In the section “Collision System Misuse”, the manual warns against colliding against visible geometry, because:

Even collision geometry constructed from CollisionPolygon objects is significantly more efficient than colliding against the renderable geometry!
And further that:
Various exporters are able to automatically generate optimized collision geometry during the export process.

Which suggests that there is some collision-specific optimisation potentially in effect…
[/edit]

That’s part of my concern: I don’t want something that’s going to prove to be a performance issue. (And don’t want to spend the time implementing such a system only to find that it is such a performance issue. ^^; )

I think CollisionPolygon means quad. If you use mesh, then this is also a CollisionPolygon, but a trangulated one. To use the quad effectively, it must be coplanar. With Quads, collision calculations are performed twice as fast as triangles.

Here is a small example of how to create a quad.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath, LVecBase3f, CollisionNode, CollisionPolygon

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        collision_node = CollisionNode("quad")
        quad = CollisionPolygon(LVecBase3f(-1, -1, 0), LVecBase3f(1, -1, 0), LVecBase3f(1, 1, 0), LVecBase3f(-1, 1, 0))
        # Option for geometry.
        #quad = CollisionPolygon(LVecBase3f(-1, -1, 0), LVecBase3f(1, -1, 0), LVecBase3f(1, 1, 0))
        collision_node.add_solid(quad)
        node_path = NodePath(collision_node)
        node_path.show()
        node_path.reparent_to(render)

app = MyApp()
app.run()

In the case of geometry, the constructor uses three points.

Looking at the API, we can conclude that at the moment there is no way to change the existing CollisionPolygon.

I’m not sure of what you’re saying here–as you yourself point out, there is a three-point constructor, indicating that a triangulated polygon-mesh is feasible…

Perhaps, but are you sure that there’s no other optimisation going on?

(Especially as visible geometry can also be constructed of quads.)

Ah, that is a pity! :/

Ah well, I suppose that I’ll have to look for less-accurate approximations for what I have in mind, then…

Well, thank you for your answers! :slight_smile:

CollisionPolygon - It is the polygon that is considered fast, note that its points must lie in the plane for correct calculations.

A triangle is a triangle. In theory it should be named: CollisionTriangle. This is slow. Since the required calculations for the intersection are doubled.

As for creating a CollisionPolygon from the visible geometry, you did not take into account the coplanarity, it is difficult to achieve in this case.

Triangles are polygons too. :stuck_out_tongue:

(From the dictionary, defining polygon, emphasis mine: “a figure, especially a closed plane figure, having three or more, usually straight, sides.”)

Hmm… Perhaps that is all that the manual means–but I’d like official confirmation, ideally.

However, this is not always unambiguous, let it be both yes and no.

https://www.twinkl.com/teaching-wiki/polygon

I can also add that I have not seen a polygon with two corners.

I’ll confess that I only skimmed that article, but it seems to support my assertion that triangles are considered to be polygons.

Hah, warp space-time enough and it might be possible! XD

While you are waiting for an answer, as an entertainment you can compare polygon(4 corners) against polygon(3 corners). As a result, I think you will come to the conclusion.

CollisionSphereTriangle.bam (133.6 KB) CollisionSpherePolygon.bam (78.4 KB)

I think the collision shape that is on the right will be faster.

That was an interesting experiment, indeed! (A quick-and-hacky one, mind, and I would like to swear to its validity, but still.)

You are in fact correct!

Based on a simple expedient of traversing a simple ray-cast many times over per frame, the left-hand shape produced a frame-interval of about 100ms, while the right-hand one produced a frame-interval of about 180ms!

If you want to try the experiment for yourself, here’s the code that I used:

from panda3d.core import loadPrcFile, loadPrcFileData
loadPrcFileData("", "show-frame-rate-meter #t")
loadPrcFileData("", "frame-rate-meter-milliseconds #t")

from direct.showbase.ShowBase import ShowBase

from panda3d.core import CollisionTraverser, CollisionHandlerQueue, CollisionRay, CollisionNode

from panda3d import __version__ as pandaVersion
print (pandaVersion)

import sys
print (sys.version)


class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)


        model = loader.loadModel("CollisionSpherePolygon.bam")
        #model = loader.loadModel("CollisionSphereTriangle.bam")
        model.reparentTo(render)

        ray = CollisionRay(0, 0, 0, 0, 1, 0)
        node = CollisionNode("mew")
        node.addSolid(ray)
        np = render.attachNewNode(node)

        self.queue = CollisionHandlerQueue()

        self.traverser = CollisionTraverser()
        self.traverser.addCollider(np, self.queue)

        self.accept("1", self.mew1)

        self.firstTime = True

    def mew1(self):
        taskMgr.add(self.update, "update")

    def update(self, task):
        for i in range(1000):
            self.traverser.traverse(render)
            if i == 0 and self.firstTime:
                self.firstTime = False
                print ("Entries found:", self.queue.getNumEntries())
        return task.cont


app = Game()
app.run()

To perform the test, comment-and-uncomment the relevant calls to “loadModel” (if required), then run the program and press “1” and observe the frame-timer at the top-right.

In fact, I found the answers back when I was developing my exporter for blender. A simple logic operates here, exactly the same as when rendering polygons.

However, I need to find out what causes the opposite effect.

1 Like

I modified it a little bit, because in games objects are in motion. You can repeat the experiment, but for me, as expected, the right shows 150, and the left ~ 280

from panda3d.core import loadPrcFile, loadPrcFileData
loadPrcFileData("", "show-frame-rate-meter #t")
loadPrcFileData("", "frame-rate-meter-milliseconds #t")

from direct.showbase.ShowBase import ShowBase

from panda3d.core import CollisionTraverser, CollisionHandlerQueue, CollisionRay, CollisionNode

from panda3d import __version__ as pandaVersion
print (pandaVersion)

import sys
print (sys.version)


class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        #self.model = loader.loadModel("CollisionSpherePolygon.bam")
        self.model = loader.loadModel("CollisionSphereTriangle.bam")
        #self.model.show()
        self.model.reparentTo(render)

        ray = CollisionRay(0, -5, 0, 0, 1, 0)
        node = CollisionNode("mew")
        node.addSolid(ray)
        np = render.attachNewNode(node)
        #np.show()

        self.queue = CollisionHandlerQueue()

        self.traverser = CollisionTraverser()
        self.traverser.addCollider(np, self.queue)

        self.accept("1", self.mew1)

        self.firstTime = True

    def mew1(self):
        taskMgr.add(self.update, "update")

    def update(self, task):
        self.model.setHpr(task.time, task.time, task.time)
        for i in range(1000):
            self.traverser.traverse(render)
            if i == 0 and self.firstTime:
                self.firstTime = False
                print ("Entries found:", self.queue.getNumEntries())
        return task.cont


app = Game()
app.run()
1 Like

In the section “Collision System Misuse”, the manual warns against colliding against visible geometry.

Because the visible geometry is usually optimized to be pretty with lots of verts and you don’t need that for collisions.

E.g. a ray colliding with any of the 500 faces of a mesh sphere or a ray as a line being close to a sphere defined by a point and a radius, are two very different calculations.

But if you are doing “minecraft”/“runescape” graphics, where the visible geometry is very minimal and identical to the collision geometry, there will be no difference.

The warning in the manual is there because if just straight up turn collisions “on” for your 2M faces character model you just got from an asset store will it will ruin your performance for no reason.

This wiki has some example pictures https://wiki.splashdamage.com/index.php/Collision_Meshes

There are also additional considerations, rdb said that internally the engine wants “buckets” for things that can collide. If the buckets don’t collide, neither will the objects inside them.

That reduces the effort of calculation from

number**2
to
2*(number/2)**2.

E.g. from
16**2=256
to
2*8**2=128.
And the efficiency goes up the better you can “bucket” your objects. Rdb said he put in some option for the collision system to try and “bucket smartly” automatically, but you have to turn that on explicitly.

Hmm… It may be that, indeed…

However, a quick test suggests otherwise:

In Blender I made a simple sphere, and exported it twice: once as visible geometry, and once as collision geometry. I then modified the experiment that I posted above to make use of those two models, and repeated my timing-test.

You can see the results below:


When using visible geometry, the frame-interval is much higher than when using collision geometry.

(And taking a quick look at the egg-file for the visible-geometry sphere, it does look as though it is still composed primarily of quads; it doesn’t seem to have been triangulated, at least before it enters the engine.)

Oh yes, I’m well aware of that! My concern is less related to how I arrange my scene than to how to handle a single given object of unusual requirements…

You may need to interpret the results of the experiment differently. 113.8ms is the fastest. And 323.9 ms is slow. I don’t understand why you say otherwise.

I’m not saying otherwise–I’m saying exactly what you are.

My point is against the claim just above my post that the reason that the manual warns against colliding with visible geometry is simply a matter of higher polygon counts being expected in such.

But if that were the case, then I would expect to see similar performance from collision and visible geometry, as long as they have the same polygon count (and there aren’t complicating factors, like triangulation). Which doesn’t seem to be the case.

Specifically, here are the claims that I’m addressing: