tests: Add some callbacks/simulation tests for Bullet

This commit is contained in:
Sam Edwards
2019-08-12 18:54:47 -06:00
parent 6d34a4a644
commit fd62daca7b
4 changed files with 214 additions and 0 deletions

0
tests/bullet/__init__.py Normal file
View File

71
tests/bullet/conftest.py Normal file
View File

@@ -0,0 +1,71 @@
import pytest
@pytest.fixture
def world():
bullet = pytest.importorskip("panda3d.bullet")
world = bullet.BulletWorld()
return world
def simulate_until(world, cond, dt=1.0/60, limit=600):
for x in range(limit):
world.do_physics(dt)
if cond():
return True
return False
@pytest.fixture
def scene(world):
"""
This test scene contains a "ramp" which slopes down at a 45-degree angle at
locations where X<0, and is flat at X>0. A bowling ball is placed above the
ramp at X=-5, Z=7. A stack of 2 very light boxes is placed at X=5, Z=0.
The world is given gravity.
When running the Bullet simulation, the ball should fall, travel down the
ramp, and hit the lower box with enough force to displace it and cause the
upper box to topple to the ground.
"""
core = pytest.importorskip("panda3d.core")
bullet = pytest.importorskip("panda3d.bullet")
bodies = []
ramp = bullet.BulletRigidBodyNode('ramp')
ramp.add_shape(bullet.BulletPlaneShape(core.Vec4(0, 0, 1, 0)))
ramp.add_shape(bullet.BulletPlaneShape(core.Vec4(1, 0, 1, 0).normalized()))
bodies.append(ramp)
ball = bullet.BulletRigidBodyNode('ball')
ball.add_shape(bullet.BulletSphereShape(1))
ball.set_mass(100)
ball.set_transform(core.TransformState.make_pos(core.Point3(-5, 0, 7)))
bodies.append(ball)
lower_box = bullet.BulletRigidBodyNode('lower_box')
lower_box.add_shape(bullet.BulletBoxShape(core.Vec3(2.5)))
lower_box.set_mass(1)
lower_box.set_transform(core.TransformState.make_pos(core.Point3(5, 0, 2.5)))
bodies.append(lower_box)
upper_box = bullet.BulletRigidBodyNode('upper_box')
upper_box.add_shape(bullet.BulletBoxShape(core.Vec3(2.5)))
upper_box.set_mass(1)
upper_box.set_transform(core.TransformState.make_pos(core.Point3(5, 0, 7.5)))
bodies.append(upper_box)
world.set_gravity(core.Vec3(0, 0, -9.8))
scene = core.NodePath('scene')
for body in bodies:
scene.attach_new_node(body)
world.attach(body)
yield scene
scene.remove_node()
for body in bodies:
world.remove(body)

View File

@@ -0,0 +1,79 @@
import pytest
from .conftest import simulate_until
# Skip these tests if we can't import bullet.
bullet = pytest.importorskip("panda3d.bullet")
from panda3d import core
bullet_filter_algorithm = core.ConfigVariableString('bullet-filter-algorithm')
def test_tick(world):
fired = []
def callback(cd):
fired.append(isinstance(cd, bullet.BulletTickCallbackData))
world.set_tick_callback(callback, False)
assert fired == []
world.do_physics(0.1)
assert fired == [True]
world.clear_tick_callback()
world.do_physics(0.1)
assert fired == [True]
@pytest.mark.skipif(bullet_filter_algorithm != 'callback', reason='bullet-filter-algorithm not set to callback')
def test_filter(world, scene):
# This is very similar to the basic physics test, but we're using
# a filter callback to prevent collisions between the lower box and ball.
# This should have the effect of the ball rolling to X>+10 without the
# upper box falling at all.
def callback(cd):
assert isinstance(cd, bullet.BulletFilterCallbackData)
if {cd.node_0.name, cd.node_1.name} == {'ball', 'lower_box'}:
# ball<->lower_box collisions are excluded
cd.collide = False
else:
# Everything else can collide
cd.collide = True
world.set_filter_callback(callback)
ball = scene.find('**/ball')
assert simulate_until(world, lambda: ball.get_x() > 10)
# The upper box shouldn't fall
upper_box = scene.find('**/upper_box')
assert not simulate_until(world, lambda: upper_box.get_z() < 5)
def test_contact(world, scene):
# This just runs the basic physics test, but detects the toppling of the
# upper box by a contact between upper_box<->ramp
contacts = []
def callback(cd):
assert isinstance(cd, bullet.BulletContactCallbackData)
if {cd.node0.name, cd.node1.name} == {'upper_box', 'ramp'}:
if not contacts:
contacts.append(True)
world.set_contact_added_callback(callback)
ball = scene.find('**/ball')
ramp = scene.find('**/ramp')
ball.node().notify_collisions(True)
ramp.node().notify_collisions(True)
assert simulate_until(world, lambda: ball.get_x() > 0)
# Now we wait for the upper box to topple
assert simulate_until(world, lambda: bool(contacts))

View File

@@ -0,0 +1,64 @@
import pytest
from .conftest import simulate_until
# Skip these tests if we can't import bullet.
bullet = pytest.importorskip("panda3d.bullet")
from panda3d import core
def test_basics(world, scene):
# N.B. see `scene` fixture's docstring in conftest.py to understand what's
# being simulated here
# Step forward until the ball crosses the threshold
ball = scene.find('**/ball')
assert simulate_until(world, lambda: ball.get_x() >= 0)
# Continue simulating until upper box falls
upper_box = scene.find('**/upper_box')
assert upper_box.get_z() > 5.0
assert simulate_until(world, lambda: upper_box.get_z() < 5.0)
def test_restitution(world, scene):
ball = scene.find('**/ball')
scene.find('**/ramp').node().restitution = 1.0
for with_bounce in (False, True):
# Reset ball
ball.node().set_angular_velocity(core.Vec3(0))
ball.node().set_linear_velocity(core.Vec3(0))
ball.set_pos(-2, 0, 100)
ball.node().restitution = 1.0 * with_bounce
# Simulate until ball rolls/bounces across Y axis
assert simulate_until(world, lambda: ball.get_x() >= 0)
if with_bounce:
# The ball bounced across, so it should be off the ground a bit
assert ball.get_z() > 1.2
else:
# The ball rolled, so it should be on the ground
assert ball.get_z() < 1.2
def test_friction(world, scene):
ball = scene.find('**/ball')
for with_friction in (False, True):
# Reset ball, give it a huge negative (CCW) spin about the X axis so
# it'll roll in +Y direction if there's any friction
ball.node().set_angular_velocity(core.Vec3(-1000,0,0))
ball.node().set_linear_velocity(core.Vec3(0))
ball.set_pos(-2, 0, 5)
ball.node().friction = 1.0 * with_friction
# Simulate until ball crosses Y axis
assert simulate_until(world, lambda: ball.get_x() >= 0)
if with_friction:
# The ball had friction, so should've gone off in the +Y direction
assert ball.get_y() > 1
else:
# No friction means the Y axis should be unaffected
assert abs(ball.get_y()) < 0.1