Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CollisionHandlerPusher has trouble with convex corners <=90 degrees #879

Closed
janEntikan opened this issue Mar 6, 2020 · 7 comments
Closed

CollisionHandlerPusher has trouble with convex corners <=90 degrees #879

janEntikan opened this issue Mar 6, 2020 · 7 comments
Labels
bug
Milestone

Comments

@janEntikan
Copy link

@janEntikan janEntikan commented Mar 6, 2020

When a CollisionHandlerPusher handles sphere collisions the node will get stuck at convex corners.
It seems the sharper the angle of the corner is, the more this effect occurs.

To reproduce after running main.py from attached zip file:
-Walk into a wall using WASD keys and take note of how you can slide along with it.
-Walk into a corner of the green shape straight on.
-Rotate away from the corner, while moving forward and notice how you stay stuck in position.
-Do the same with the red shape, notice how you start vibrating into the shape.
-Optional: walk into the red shape's convex corner from the side. If it's thin enough you can move straight through.

What I expected/hoped would happen:
Slide away from the corner as you would with a flat wall.

It seems it is slightly less noticeable in the roaming-ralph example because there are not a lot of sharp corners, though there are a few tree-stumps that can give ralph the jitters.

pusher.zip

@janEntikan janEntikan changed the title CollisionHandlerPusher has trouble with convex corners >=90 degrees CollisionHandlerPusher has trouble with convex corners <=90 degrees Mar 6, 2020
@rdb

This comment has been minimized.

Copy link
Member

@rdb rdb commented Mar 7, 2020

This is the collision debug info when disabling the floor and walking into the convex corner of the green shape:

intersection detected from render/player/player-sphere-pusher into render/Scene/shape_a/shape_a
intersection detected from render/player/player-sphere-pusher into render/Scene/shape_a/shape_a
Shove on render/player/player-sphere-pusher from render/Scene/shape_a/shape_a: 0.906392 -0.422438 0 times 0.164837
Shove on render/player/player-sphere-pusher from render/Scene/shape_a/shape_a: -0.939625 -0.342207 0 times 0.159021
Considering dot product -0.707107
Net shove on render/player/player-sphere-pusher is: -1.31726e-05 -0.124052 0

Basically, it's colliding with both polygons, and both are shoving the sphere out of it, but since you're coming at them from the opposite angle, both shoves' X coordinates are cancelling each other out.

I'm not really sure how to improve this.

@rdb

This comment has been minimized.

Copy link
Member

@rdb rdb commented Mar 7, 2020

This image demonstrates perhaps more clearly what I think is going on. Both polygons are independent collision solids, and both notice you're "inside" them, so both apply a force to push you "out" (along their normal vectors), but these cancel each other out laterally. And when you're trying to move to the left, you're actually moving "deeper into" the one on the right, so it responds by shoving "back" harder.

One might suggest that they should be angling their shove vectors to push the sphere somewhat out the side of each polygon, rather than the front. However, this would have funny effects when colliding with a flat surface head-on (like a ground plane!) as the sphere might start to slide around. It might be worth experimenting with applying this only when the center of the sphere is below the plane of the polygon, which is the case for the right polygon shown in the image.

I think this problem might be a lot easier to solve if we had a collision solid that understood the connections between polygons; then it would be able to know that it's colliding with a corner.

@janEntikan

This comment has been minimized.

Copy link
Author

@janEntikan janEntikan commented Mar 7, 2020

This stuff is going over my head a bit but perhaps there's a way to prioritize the normals from the collisions that are closest to the center of the collision solid somehow? Meaning, in that diagram of yours, it would prioritize the blue face.

@rdb

This comment has been minimized.

Copy link
Member

@rdb rdb commented Mar 7, 2020

Well, there might actually be something else going on here.
I created a simple sample program to try and reproduce the convex corner issue in a more controlled environment, and am finding that I can't actually. If you use the mouse to position the solid and hold shift to enable the pusher, it always seems to push it out to either side, and never seems to prevent it from sliding along the wall.

Code with constructed polygons

from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
load_prc_file_data("", "notify-level-collide debug")

from direct.directbase import DirectStart

base.cam.set_y(-1)
base.cam.set_z(50)
base.cam.look_at((0, 0, 0))

lens = OrthographicLens()
lens.set_film_size(5 * base.getAspectRatio(), 5)
base.camNode.setLens(lens)

poly1 = CollisionPolygon((0, 0, 1), (0, 0, -1), (1, 3, -1), (1, 3, 1))
poly2 = CollisionPolygon((0, 0, -1), (0, 0, 1), (-1, 3, 1), (-1, 3, -1))

sphere = CollisionSphere((0, 0, 0), 0.5)
sphere.tangible = False

into_node = CollisionNode("into")
into_node.add_solid(poly1)
into_node.add_solid(poly2)
into_node.set_into_collide_mask(1)
into_np = render.attach_new_node(into_node)
into_np.show()

from_np_parent = render.attach_new_node("from-parent")

from_node = CollisionNode("from")
from_node.add_solid(sphere)
from_node.set_from_collide_mask(1)
from_np = from_np_parent.attach_new_node(from_node)
from_np.show()

handler = CollisionHandlerPusher()
handler.add_collider(from_np, from_np_parent)

base.cTrav = CollisionTraverser()
base.cTrav.show_collisions(base.render)
base.cTrav.add_collider(from_np, handler)

def update(task):
    if base.mouseWatcherNode.has_mouse():
        pos = base.mouseWatcherNode.get_mouse()
        from_np_parent.set_pos(pos[0] * 4, pos[1] * 3, 0)
    return task.cont

base.taskMgr.add(update)

def enable_pushing():
    sphere.tangible = True

def disable_pushing():
    sphere.tangible = False

text = OnscreenText('hold shift to enable pushing', parent=base.a2dBottomCenter, fg=(1, 1, 1, 1), pos=(0, 0.05))

base.accept('shift', enable_pushing)
base.accept('shift-up', disable_pushing)


base.run()

But now loading in your model, it fails:

Code with your model

from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
load_prc_file_data("", "notify-level-collide debug")

from direct.directbase import DirectStart

base.cam.set_z(50)
base.cam.set_hpr(0, -91, 0)

lens = OrthographicLens()
lens.set_film_size(25 * base.getAspectRatio(), 25)
base.camNode.setLens(lens)

sphere = CollisionSphere((0, 0, 1), 0.8)
sphere.tangible = False

into_np = loader.load_model("corners.bam")
into_np.find("**/floor").stash()
into_np.set_collide_mask(1)
into_np.reparent_to(render)

from_np_parent = render.attach_new_node("from-parent")

from_node = CollisionNode("from")
from_node.add_solid(sphere)
from_node.set_from_collide_mask(1)
from_np = from_np_parent.attach_new_node(from_node)
from_np.show()

handler = CollisionHandlerPusher()
handler.add_collider(from_np, from_np_parent)

base.cTrav = CollisionTraverser()
base.cTrav.show_collisions(base.render)
base.cTrav.add_collider(from_np, handler)

def update(task):
    if base.mouseWatcherNode.has_mouse():
        pos = base.mouseWatcherNode.get_mouse()
        from_np_parent.set_pos(pos[0] * 20, pos[1] * 15, 0)
    return task.cont

base.taskMgr.add(update)

def enable_pushing():
    sphere.tangible = True

def disable_pushing():
    sphere.tangible = False

text = OnscreenText('hold shift to enable pushing', parent=base.a2dBottomCenter, fg=(1, 1, 1, 1), pos=(0, 0.05))

base.accept('shift', enable_pushing)
base.accept('shift-up', disable_pushing)

base.run()

I need to find out what's different from your model and my test set-up.

@rdb

This comment has been minimized.

Copy link
Member

@rdb rdb commented Mar 7, 2020

Exact same polygons, different response. The only difference is that your polygons were generated from visible geometry, rather than pregenerated CollisionPolygons. Use 1 and 2 to switch between your scene and my polygons:

from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
load_prc_file_data("", "notify-level-collide debug")

from direct.directbase import DirectStart

base.cam.set_pos(3, 0, 10)
base.cam.set_hpr(140, -45, 0)
base.camLens.set_fov(90)

poly1 = CollisionPolygon(
    (-2.92718, -6.19478, 4.71415),
    (-2.92718, -6.19478, -0.140995),
    (-9.30449, -8.7393, -0.140995),
)
poly2 = CollisionPolygon(
    (-4.84357, -10.6557, 4.71415),
    (-2.92718, -6.19478, -0.140995),
    (-2.92718, -6.19478, 4.71415),
)
sphere = CollisionSphere((0, 0, 3), 0.8)
sphere.tangible = False

plane = Plane((0, 0, 1), (0, 0, 3))

into0_np = loader.load_model("corners.bam")
into0_np.find("**/floor").stash()
into0_np.flatten_strong()
into0_np.set_collide_mask(1)
into0_np.reparent_to(render)
#into0_np.stash()

into1_node = CollisionNode("into")
into1_node.add_solid(poly1)
into1_node.add_solid(poly2)
into1_node.set_into_collide_mask(1)
into1_np = render.attach_new_node(into1_node)
into1_np.show()
into1_np.stash()

from_np_parent = render.attach_new_node("from-parent")

from_node = CollisionNode("from")
from_node.add_solid(sphere)
from_node.set_from_collide_mask(1)
from_np = from_np_parent.attach_new_node(from_node)
from_np.show()

handler = CollisionHandlerPusher()
handler.add_collider(from_np, from_np_parent)

base.cTrav = CollisionTraverser()
base.cTrav.show_collisions(base.render)
base.cTrav.add_collider(from_np, handler)

def update(task):
    if base.mouseWatcherNode.has_mouse():
        mpos = base.mouseWatcherNode.get_mouse()
        pos3d = Point3()
        nearPoint = Point3()
        farPoint = Point3()
        base.camLens.extrude(mpos, nearPoint, farPoint)
        if plane.intersects_line(pos3d,
            render.get_relative_point(base.cam, nearPoint),
            render.get_relative_point(base.cam, farPoint)):
            pos3d.set_z(0)
            from_np_parent.set_pos(render, pos3d)
    return task.cont

base.taskMgr.add(update)

def enable_pushing():
    sphere.tangible = True

def disable_pushing():
    sphere.tangible = False

def switch_scene0():
    into0_np.unstash()
    into1_np.stash()

def switch_scene1():
    into0_np.stash()
    into1_np.unstash()

text = OnscreenText('press 1 and 2 to switch scenes\nhold shift to enable pushing', parent=base.a2dBottomCenter, fg=(1, 1, 1, 1), pos=(0, 0.15))

base.accept('shift', enable_pushing)
base.accept('shift-up', disable_pushing)

base.accept('1', switch_scene0)
base.accept('2', switch_scene1)

base.run()
@janEntikan

This comment has been minimized.

Copy link
Author

@janEntikan janEntikan commented Mar 7, 2020

How quaint. Another difference, besides being generated from visual geometry, is that they are converted by panda3d-gltf/blend2bam. I tried setting the Collide : Polyset descend tag before converting but that didn't do anything either. (is that reserved for eggs?)

@rdb

This comment has been minimized.

Copy link
Member

@rdb rdb commented Mar 7, 2020

So, I printed out the debug information, and the difference is that with your model it contains this:

Shove on render/from-parent/from from render/Scene/shape_b.001: -0.370587 0.928798 0 times 1.24451
Shove on render/from-parent/from from render/Scene/shape_b.001: 0.918804 -0.394715 0 times 1.1561
Considering dot product -0.707107
Net shove on render/from-parent/from is: 0.601034 0.699563 0

But with my polygons it contains this:

Shove on render/from-parent/from from render/into: -0.370586 0.928798 0 times 1.24451
Shove on render/from-parent/from from render/into: 0.918804 -0.394714 0 times 1.1561
Considering dot product -0.707106
Discarding shove from convex corner.
Net shove on render/from-parent/from is: 1.06223 -0.456329 0

Apparently there is something to detect a "convex corner" already, which automatically picks one of the polygons to shove from, as you suggested. However, this code is only getting activated when the type is exactly CollisionPolygon, not CollisionGeom (which is a subclass of CollisionPolygon, created for visible geometry), so basically the convex corner handling code is disabled for collisions with visible geometry.

This is turning out to be a very easy fix. The other problem in your code, by the way, is that the sphere has a radius that is too large and so it is bouncing around between the various shapes, but that can be easily fixed by reducing the radius.

I don't think blend2bam looks at the Collide property.

@rdb rdb added the bug label Mar 7, 2020
@rdb rdb added this to the 1.10.6 milestone Mar 7, 2020
@rdb rdb closed this in e8d8f20 Mar 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.