Exemplo n.º 1
0
    def test_setRigidBody(self):
        """
        Set and retrieve object attributes like position, velocity,
        acceleration, and rotation.
        """
        # Instantiate a Leonard.
        leo = getLeonard()

        # Test constants.
        body_new = {
            'imass': 2,
            'scale': 3,
            'cshapes': {'csempty': getCSEmpty()},
            'position': (1, 2, 5),
            'velocityLin': (8, 9, 10.5),
            'velocityRot': (9, 10, 11.5),
            'rotation': (11, 12.5, 13, 13.5)
        }

        # Create a test body.
        id_1 = 0
        body = getRigidBody(cshapes={'csempty': getCSEmpty()})

        # Add the object to the DB with ID=0.
        assert leoAPI.addCmdSpawn([(id_1, body)]).ok
        leo.processCommandsAndSync()

        # Modify the state vector for body with id_1.
        assert leoAPI.addCmdModifyBodyState(id_1, body_new).ok
        leo.processCommandsAndSync()

        # Query the body again and verify the changes are in effect.
        ret = leo.allBodies[id_1]
        assert ret.imass == body_new['imass']
        assert ret.scale == body_new['scale']
        assert np.array_equal(ret.position, body_new['position'])
        assert np.array_equal(ret.velocityLin, body_new['velocityLin'])
        assert np.array_equal(ret.velocityRot, body_new['velocityRot'])
        assert np.array_equal(ret.rotation, body_new['rotation'])

        # Query the AABB, update the collision shapes, and verify that the new
        # AABBs are in effect.
        assert leo.allAABBs[id_1] == {}

        # Modify the body state by adding a collision shape.
        body_new = {'cshapes': {'cssphere': getCSSphere(radius=1)}}
        assert body_new is not None
        assert leoAPI.addCmdModifyBodyState(id_1, body_new).ok
        leo.processCommandsAndSync()
        assert leo.allAABBs[id_1] == {'cssphere': [0, 0, 0, 1, 1, 1]}

        # Modify the body state by adding a collision shape.
        cs_a = getCSSphere(radius=1, pos=(1, 2, 3))
        cs_b = getCSSphere(radius=2, pos=(4, 5, 6))
        cshapes = {'1': cs_a, '2': getCSEmpty(), '3': cs_b}
        body_new = {'cshapes': cshapes}
        assert leoAPI.addCmdModifyBodyState(id_1, body_new).ok
        leo.processCommandsAndSync()
        correct = {'1': [1, 2, 3, 1, 1, 1], '3': [4, 5, 6, 2, 2, 2]}
        assert leo.allAABBs[id_1] == correct
Exemplo n.º 2
0
    def test_computeAABBS_StaticPlane(self):
        """
        Static planes are permissible collision shapes for a body if
        that is indeed the only collision shape for that body.
        Conversely, it is not allowed for a body to have multiple
        collision shapes if one of them is a StaticPlane.
        """
        computeAABBs = azrael.leo_api.computeAABBs

        # One or more spheres are permissible.
        cs = {'cssphere': getCSSphere()}
        assert computeAABBs(cs).ok

        # A single plane is permissible.
        cs = {'csplane': getCSPlane()}
        assert computeAABBs(cs).ok

        # A plane in conjunction with any other object is not allowed...
        cs = {'csplane': getCSPlane(), 'cssphere': getCSSphere()}
        assert not computeAABBs(cs).ok

        # not even with another plane.
        cs = {'csplane': getCSPlane(), 'cssphere': getCSSphere()}
        assert not computeAABBs(cs).ok

        # The position and rotation of a plane is defined via the normal
        # vector and its offset. The position/rotation fields in the
        # CollShapeMeta structure are thus redundant and *must* be set to
        # defaults to avoid unintended side effects.
        cs = {'csplane': getCSPlane(pos=(0, 1, 2))}
        assert not computeAABBs(cs).ok

        cs = {'csplane': getCSPlane(rot=(1, 0, 0, 0))}
        assert not computeAABBs(cs).ok
Exemplo n.º 3
0
    def test_computeAABBS_StaticPlane(self):
        """
        Static planes are permissible collision shapes for a body if
        that is indeed the only collision shape for that body.
        Conversely, it is not allowed for a body to have multiple
        collision shapes if one of them is a StaticPlane.
        """
        computeAABBs = azrael.leo_api.computeAABBs

        # One or more spheres are permissible.
        cs = {'cssphere': getCSSphere()}
        assert computeAABBs(cs).ok

        # A single plane is permissible.
        cs = {'csplane': getCSPlane()}
        assert computeAABBs(cs).ok

        # A plane in conjunction with any other object is not allowed...
        cs = {'csplane': getCSPlane(), 'cssphere': getCSSphere()}
        assert not computeAABBs(cs).ok

        # not even with another plane.
        cs = {'csplane': getCSPlane(), 'cssphere': getCSSphere()}
        assert not computeAABBs(cs).ok

        # The position and rotation of a plane is defined via the normal
        # vector and its offset. The position/rotation fields in the
        # CollShapeMeta structure are thus redundant and *must* be set to
        # defaults to avoid unintended side effects.
        cs = {'csplane': getCSPlane(pos=(0, 1, 2))}
        assert not computeAABBs(cs).ok

        cs = {'csplane': getCSPlane(rot=(1, 0, 0, 0))}
        assert not computeAABBs(cs).ok
Exemplo n.º 4
0
    def test_modify_size(self):
        """
        Change the size of the collision shape. This is more intricate than
        changing the mass (see previous test) because this time the entire
        collision shape must be swapped out underneath.

        To test this we create two spheres that do not touch, which means
        nothing must happen during a physics update. Then we enlarge one sphere
        so that it touchs the other. This time Bullet must pick up on the
        interpenetration and modify the sphere's position somehow (we do not
        really care how).
        """
        # Constants and parameters for this test.
        objID_a, objID_b = 10, 20
        pos_a = [0, 0, 0]
        pos_b = [3, 0, 0]

        # Create two identical spheres, one left, one right (x-axis).
        radius = 2
        cs_a = {'csfoo': getCSSphere(radius=radius)}
        cs_b = {'csbar': getCSSphere(radius=radius)}
        obj_a = getRigidBody(position=pos_a, cshapes=cs_a)
        obj_b = getRigidBody(position=pos_b, cshapes=cs_b)
        del cs_a, cs_b, pos_a, pos_b

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send objects to Bullet and progress the simulation. The sole point of
        # progressing the simulation is to make sure Bullet actually accesses
        # the objects; we do not actually care if/how the objects moved.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Request the object back and inspect the collision shapes.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        assert list(ret_a.data.cshapes.keys()) == ['csfoo']
        assert list(ret_b.data.cshapes.keys()) == ['csbar']
        tmp_cs = sim.rigidBodies[objID_a].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == radius
        tmp_cs = sim.rigidBodies[objID_b].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == radius

        # Enlarge the second object so that the spheres do not overlap.
        obj_b = obj_b._replace(scale=2.5)
        sim.setRigidBodyData(objID_b, obj_b)
        ret = sim.getRigidBodyData(objID_b)
        assert ret.ok
        tmp_cs = sim.rigidBodies[objID_b].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == 2.5 * radius

        # Then step the simulation again to ensure Bullet accesses each object
        # and nothing bad happens (eg a segfault).
        sim.compute([objID_a, objID_b], 1.0, 60)
Exemplo n.º 5
0
    def test_modify_size(self):
        """
        Change the size of the collision shape. This is more intricate than
        changing the mass in test_modify_mass. The reason is that, internally,
        the entire collision shape must be replaced.

        For this test we create two non-touching spheres. Nothing must
        therefore happen during a physics update. Then we enlarge one sphere
        to ensure they interpenetrate. This time Bullet must pick up on the
        interpenetration and modify the sphere's position somehow (we do not
        really care how).
        """
        # Constants and parameters for this test.
        objID_a, objID_b = '10', '20'
        pos_a = [0, 0, 0]
        pos_b = [3, 0, 0]

        # Create two identical spheres. Place one left and one right (x-axis).
        radius = 2
        cs_a = {'csfoo': getCSSphere(radius=radius)}
        cs_b = {'csbar': getCSSphere(radius=radius)}
        obj_a = getRigidBody(position=pos_a, cshapes=cs_a)
        obj_b = getRigidBody(position=pos_b, cshapes=cs_b)
        del cs_a, cs_b, pos_a, pos_b

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send objects to Bullet and progress the simulation. Nothing must
        # happen because the bodies do not touch and no forces are active.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Request the object back and inspect the radius of the spherical
        # collision shapes.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        tmp_cs = sim.rigidBodies[objID_a].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == radius
        tmp_cs = sim.rigidBodies[objID_b].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == radius

        # Enlarge the second object until it overlaps with the first.
        obj_b = obj_b._replace(scale=2.5)
        sim.setRigidBodyData(objID_b, obj_b)
        ret = sim.getRigidBodyData(objID_b)
        assert ret.ok
        tmp_cs = sim.rigidBodies[objID_b].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == 2.5 * radius

        # Step the simulation. This ensures that Bullet accesses each
        # object without segfaulting despite swapping out C-pointers
        # underneath the hood.
        sim.compute([objID_a, objID_b], 1.0, 60)
Exemplo n.º 6
0
    def test_modify_size(self):
        """
        Change the size of the collision shape. This is more intricate than
        changing the mass in test_modify_mass. The reason is that, internally,
        the entire collision shape must be replaced.

        For this test we create two non-touching spheres. Nothing must
        therefore happen during a physics update. Then we enlarge one sphere
        to ensure they interpenetrate. This time Bullet must pick up on the
        interpenetration and modify the sphere's position somehow (we do not
        really care how).
        """
        # Constants and parameters for this test.
        objID_a, objID_b = '10', '20'
        pos_a = [0, 0, 0]
        pos_b = [3, 0, 0]

        # Create two identical spheres. Place one left and one right (x-axis).
        radius = 2
        cs_a = {'csfoo': getCSSphere(radius=radius)}
        cs_b = {'csbar': getCSSphere(radius=radius)}
        obj_a = getRigidBody(position=pos_a, cshapes=cs_a)
        obj_b = getRigidBody(position=pos_b, cshapes=cs_b)
        del cs_a, cs_b, pos_a, pos_b

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send objects to Bullet and progress the simulation. Nothing must
        # happen because the bodies do not touch and no forces are active.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Request the object back and inspect the radius of the spherical
        # collision shapes.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        tmp_cs = sim.rigidBodies[objID_a].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == radius
        tmp_cs = sim.rigidBodies[objID_b].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == radius

        # Enlarge the second object until it overlaps with the first.
        obj_b = obj_b._replace(scale=2.5)
        sim.setRigidBodyData(objID_b, obj_b)
        ret = sim.getRigidBodyData(objID_b)
        assert ret.ok
        tmp_cs = sim.rigidBodies[objID_b].getCollisionShape()
        assert tmp_cs.getChildShape(0).getRadius() == 2.5 * radius

        # Step the simulation. This ensures that Bullet accesses each
        # object without segfaulting despite swapping out C-pointers
        # underneath the hood.
        sim.compute([objID_a, objID_b], 1.0, 60)
Exemplo n.º 7
0
    def test_constraint_p2p(self, clsLeonard):
        """
        Link two bodies together with a Point2Point constraint and verify that
        they move together.
        """
        # Get a Leonard instance.
        leo = getLeonard(clsLeonard)

        # Convenience.
        id_a, id_b = 1, 2
        pos_a, pos_b = (-2, 0, 0), (2, 0, 0)
        distance = abs(pos_a[0] - pos_b[0])
        assert distance >= 4

        body_a = getRigidBody(position=pos_a, cshapes={'cssphere': getCSSphere()})
        body_b = getRigidBody(position=pos_b, cshapes={'cssphere': getCSSphere()})

        # Specify the constraints.
        con = getP2P(rb_a=id_a, rb_b=id_b, pivot_a=pos_b, pivot_b=pos_a)
        self.igor.addConstraints([con])

        # Spawn both objects.
        assert leoAPI.addCmdSpawn([(id_a, body_a), (id_b, body_b)]).ok
        leo.processCommandsAndSync()

        # Apply a force to the left sphere only.
        assert leoAPI.addCmdDirectForce(id_a, [-10, 0, 0], [0, 0, 0]).ok
        leo.processCommandsAndSync()

        # Both object must have moved the same distance 'delta' because they
        # are linked. Their distance must not have changed.
        leo.step(1.0, 60)
        allObjs = leo.allBodies
        delta_a = allObjs[id_a].position - np.array(pos_a)
        delta_b = allObjs[id_b].position - np.array(pos_b)
        assert delta_a[0] < pos_a[0]
        assert np.allclose(delta_a, delta_b)
        tmp = abs(allObjs[id_a].position[0] - allObjs[id_b].position[0])
        assert abs(tmp - distance) < 0.01
        del tmp

        # Unlink the objects again, apply a right-pointing force to the
        # right object and verify that the left continues to move left and the
        # right does not.
        assert self.igor.deleteConstraints([con]) == (True, None, 1)
        assert leoAPI.addCmdDirectForce(id_b, [10, 0, 0], [0, 0, 0]).ok
        leo.processCommandsAndSync()
        leo.step(1.0, 60)

        # The distance between the spheres must have increases since they are
        # not linked anymore.
        tmp = abs(allObjs[id_a].position[0] - allObjs[id_b].position[0])
        assert tmp > (distance + 1)
Exemplo n.º 8
0
    def test_getset_object(self):
        """
        Send/retrieve object to/from Bullet and verify the integrity.
        """
        # Define a set of collision shapes.
        pos = (0, 1, 2)
        rot = (0, 0, 0, 1)
        cshapes = {'1': getCSEmpty(pos, rot), '2': getCSSphere(pos, rot)}
        del pos, rot

        # Create an object and serialise it.
        obj_a = getRigidBody(
            scale=3.5,
            imass=4.5,
            cshapes=cshapes,
            restitution=5.5,
            rotation=(0, 1, 0, 0),
            position=(0.2, 0.4, 0.6),
            velocityLin=(0.8, 1.0, 1.2),
            velocityRot=(1.4, 1.6, 1.8))
        assert obj_a is not None

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Request an invalid object ID.
        ret = sim.getRigidBodyData(0)
        assert not ret.ok

        # Send object to Bullet and request it back.
        sim.setRigidBodyData(0, obj_a)
        ret = sim.getRigidBodyData(0)
        assert (ret.ok, ret.data) == (True, obj_a)
Exemplo n.º 9
0
    def test_setRigidBody_advanced(self, clsLeonard):
        """
        Similar to test_setRigidBody_basic but modify the collision shape
        information as well, namely their mass- and type.
        """
        # Get a Leonard instance.
        leo = getLeonard(clsLeonard)

        # Parameters and constants for this test.
        cshape_box = {'1': getCSBox()}
        cshape_sphere = {'1': getCSSphere()}
        body = getRigidBody(imass=2, scale=3, cshapes=cshape_sphere)

        # Spawn an object.
        objID = 1
        assert leoAPI.addCmdSpawn([(objID, body)]).ok
        del body

        # Verify the body data.
        leo.processCommandsAndSync()
        assert leo.allBodies[objID].imass == 2
        assert leo.allBodies[objID].scale == 3
        assert leo.allBodies[objID].cshapes == cshape_sphere

        # Update the body.
        cs_new = {'imass': 4, 'scale': 5, 'cshapes': cshape_box}
        assert leoAPI.addCmdModifyBodyState(objID, cs_new).ok

        # Verify the body data.
        leo.processCommandsAndSync()
        ret = leo.allBodies[objID]
        assert (ret.imass == 4) and (ret.scale == 5)
        assert ret.cshapes == cshape_box
Exemplo n.º 10
0
    def test_add_get_remove_single(self):
        """
        Add an object to the SV database.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create an object ID for the test.
        id_1 = 1

        # The number of SV entries must now be zero.
        assert len(leo.allBodies) == 0

        # Query an object. Since none exists yet this must fail.
        assert id_1 not in leo.allBodies

        # Create an object and serialise it.
        body = getRigidBody(cshapes={'cssphere': getCSSphere()})

        # Add the object to Leonard and verify it worked.
        assert leoAPI.addCmdSpawn([(id_1, body)])
        leo.processCommandsAndSync()
        assert leo.allBodies[id_1] == body

        # Remove object id_1.
        assert leoAPI.addCmdRemoveObject(id_1).ok
        leo.processCommandsAndSync()

        # Object must not exist anymore in the simulation.
        assert id_1 not in leo.allBodies
        assert len(leo.allBodies) == 0
Exemplo n.º 11
0
    def test_add_get_remove_single(self):
        """
        Add an object to the SV database.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create an object ID for the test.
        id_1 = '1'

        # The number of SV entries must now be zero.
        assert len(leo.allBodies) == 0

        # Query an object. Since none exists yet this must fail.
        assert id_1 not in leo.allBodies

        # Create an object and serialise it.
        body = getRigidBody(cshapes={'cssphere': getCSSphere()})

        # Add the object to Leonard and verify it worked.
        assert leoAPI.addCmdSpawn([(id_1, body)])
        leo.processCommandsAndSync()
        assert leo.allBodies[id_1] == body

        # Remove object id_1.
        assert leoAPI.addCmdRemoveObject(id_1).ok
        leo.processCommandsAndSync()

        # Object must not exist anymore in the simulation.
        assert id_1 not in leo.allBodies
        assert len(leo.allBodies) == 0
Exemplo n.º 12
0
    def test_getset_object(self):
        """
        Send/retrieve object to/from Bullet and verify the integrity.
        """
        aid = '0'

        # Define a set of collision shapes.
        pos = (0, 1, 2)
        rot = (0, 0, 0, 1)
        cshapes = {'1': getCSEmpty(pos, rot), '2': getCSSphere(pos, rot)}
        del pos, rot

        # Create a test object.
        obj_a = getRigidBody(scale=3.5,
                             imass=4.5,
                             cshapes=cshapes,
                             restitution=5.5,
                             rotation=(0, 1, 0, 0),
                             position=(0.2, 0.4, 0.6),
                             velocityLin=(0.8, 1.0, 1.2),
                             velocityRot=(1.4, 1.6, 1.8))
        assert obj_a is not None

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Request an invalid object ID.
        ret = sim.getRigidBodyData(0)
        assert not ret.ok

        # Send object to Bullet and request it back.
        sim.setRigidBodyData(aid, obj_a)
        ret = sim.getRigidBodyData(aid)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)
Exemplo n.º 13
0
    def test_skipEmpty(self):
        """
        Verify that _skipEmptyBodies removes all bodies that have a) exactly
        one collision shape and b) that collision shape is empty.
        """
        # Convenience: some collision shapes.
        empty = getCSEmpty()
        sphere = getCSSphere(radius=1)

        # Create several bodies with various collision shape combinations.
        bodies = {
            1: getRigidBody(cshapes={'foo': empty}),
            2: getRigidBody(cshapes={'bar': sphere}),
            3: getRigidBody(cshapes={'foo': empty, 'bar': sphere}),
            4: getRigidBody(cshapes={'foo': empty, 'bar': empty})
        }

        # Shallow copy of the original dictionary for the comparison
        # afterwards.
        bodies_copy = dict(bodies)
        ret = azrael.leonard._skipEmptyBodies(bodies_copy)

        # Verify that the function has no side effect (ie that it does not
        # alter the dictionary we pass in).
        assert bodies == bodies_copy

        # The function must have removed the first body.
        assert ret == {2: bodies[2], 3: bodies[3]}
Exemplo n.º 14
0
    def test_modify_cshape(self):
        """
        Change the collision shape type. This is more intricate than
        changing the mass (see previous test) because this time the entire
        collision shape must be swapped out underneath.

        To test this we create two spheres that (just) do not touch. They are
        offset along the x/y axis. Once we change the spheres to cubes the
        their edges will interpenetrate and Bullet will move them apart. We can
        identify this movement.
        """
        # Constants and parameters for this test.
        objID_a, objID_b = 10, 20
        pos_a = [-0.8, -0.8, 0]
        pos_b = [0.8, 0.8, 0]
        p, q = (0, 0, 0), (0, 0, 0, 1)
        cshape_box = {'csbox': getCSBox(p, q)}
        cshape_sph = {'cssphere': getCSSphere(p, q)}
        del p, q

        # Create two identical unit spheres at different positions.
        obj_a = getRigidBody(position=pos_a, cshapes=cshape_sph)
        obj_b = getRigidBody(position=pos_b, cshapes=cshape_sph)

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send objects to Bullet and progress the simulation. The sole point of
        # progressing the simulation is to make sure Bullet actually accesses
        # the objects; we do not actually care if/how the objects moved.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Verify the collision shapes are as expected.
        ret = sim.getRigidBodyData(objID_a)
        assert ret.ok
        assert ret.data.cshapes == cshape_sph
        ret = sim.getRigidBodyData(objID_b)
        assert ret.ok
        assert ret.data.cshapes == cshape_sph

        # Change both collision shape to unit cubes. Then step the simulation
        # again to ensure Bullet accesses each object and nothing bad happens
        # (eg a segfault).
        obj_a = getRigidBody(position=pos_a, cshapes=cshape_box)
        obj_b = getRigidBody(position=pos_b, cshapes=cshape_box)
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Verify the collision shapes have been updated to boxes.
        ret = sim.getRigidBodyData(objID_a)
        assert ret.ok
        assert ret.data.cshapes == cshape_box
        ret = sim.getRigidBodyData(objID_b)
        assert ret.ok
        assert ret.data.cshapes == cshape_box
Exemplo n.º 15
0
    def test_modify_cshape(self):
        """
        Replace the entire collision shape.

        Place two spheres diagonally along the x/y axis. Their distance is such
        that they do not touch, but only just. Then replace them with boxes and
        step the simulation again. Ideally, nothing happens, in particular no
        segfault because we swapped out C-pointers under the hood.
        """
        # Constants and parameters for this test.
        objID_a, objID_b = '10', '20'
        pos_a = [-0.8, -0.8, 0]
        pos_b = [0.8, 0.8, 0]
        p, q = (0, 0, 0), (0, 0, 0, 1)
        cshape_box = {'csbox': getCSBox(p, q)}
        cshape_sph = {'cssphere': getCSSphere(p, q)}
        del p, q

        # Create two identical unit spheres at different positions.
        obj_a = getRigidBody(position=pos_a, cshapes=cshape_sph)
        obj_b = getRigidBody(position=pos_b, cshapes=cshape_sph)

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send objects to Bullet and progress the simulation. The sole point of
        # progressing the simulation is to make sure Bullet actually accesses
        # the objects; we do not actually care if/how the objects moved.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Verify the collision shapes are spheres.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        cs_a = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        cs_b = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        assert cs_a.getName() == cs_b.getName() == b'SPHERE'
        del ret_a, ret_b, cs_a, cs_b

        # Replace the collision spheres with collision cubes. Then step the
        # simulation again to ensure Bullet touches the shapes without
        # segfaulting.
        obj_a = getRigidBody(position=pos_a, cshapes=cshape_box)
        obj_b = getRigidBody(position=pos_b, cshapes=cshape_box)
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Verify the collision shapes are now boxes.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        cs_a = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        cs_b = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        assert cs_a.getName() == cs_b.getName() == b'Box'
        del ret_a, ret_b, cs_a, cs_b
Exemplo n.º 16
0
    def test_modify_cshape(self):
        """
        Replace the entire collision shape.

        Place two spheres diagonally along the x/y axis. Their distance is such
        that they do not touch, but only just. Then replace them with boxes and
        step the simulation again. Ideally, nothing happens, in particular no
        segfault because we swapped out C-pointers under the hood.
        """
        # Constants and parameters for this test.
        objID_a, objID_b = '10', '20'
        pos_a = [-0.8, -0.8, 0]
        pos_b = [0.8, 0.8, 0]
        p, q = (0, 0, 0), (0, 0, 0, 1)
        cshape_box = {'csbox': getCSBox(p, q)}
        cshape_sph = {'cssphere': getCSSphere(p, q)}
        del p, q

        # Create two identical unit spheres at different positions.
        obj_a = getRigidBody(position=pos_a, cshapes=cshape_sph)
        obj_b = getRigidBody(position=pos_b, cshapes=cshape_sph)

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send objects to Bullet and progress the simulation. The sole point of
        # progressing the simulation is to make sure Bullet actually accesses
        # the objects; we do not actually care if/how the objects moved.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Verify the collision shapes are spheres.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        cs_a = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        cs_b = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        assert cs_a.getName() == cs_b.getName() == b'SPHERE'
        del ret_a, ret_b, cs_a, cs_b

        # Replace the collision spheres with collision cubes. Then step the
        # simulation again to ensure Bullet touches the shapes without
        # segfaulting.
        obj_a = getRigidBody(position=pos_a, cshapes=cshape_box)
        obj_b = getRigidBody(position=pos_b, cshapes=cshape_box)
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Verify the collision shapes are now boxes.
        ret_a = sim.getRigidBodyData(objID_a)
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_a.ok and ret_b.ok
        cs_a = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        cs_b = sim.rigidBodies[objID_a].getCollisionShape().getChildShape(0)
        assert cs_a.getName() == cs_b.getName() == b'Box'
        del ret_a, ret_b, cs_a, cs_b
Exemplo n.º 17
0
    def test_specify_6DofSpring2_constraint(self):
        """
        Create two objects and linke them with a 6DOF constraint. The
        constraint mimicks a spring-loaded slider that will pull the objects
        together.
        """
        # Create physics simulation.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Create identical unit spheres 10 meters apart.
        id_a, id_b = '10', '20'
        pos_a = (-5, 0, 0)
        pos_b = (5, 0, 0)
        obj_a = getRigidBody(position=pos_a,
                             cshapes={'cssphere': getCSSphere()})
        obj_b = getRigidBody(position=pos_b,
                             cshapes={'cssphere': getCSSphere()})

        # Load the objects into the physics engine.
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)

        # Compile the 6DOF constraint.
        constraints = [get6DofSpring2(rb_a=id_a, rb_b=id_b)]

        # Step the simulation. Nothing must happen because no forces or
        # constraints act upon the objects.
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)

        # Load the constraints into the physics engine and step the simulation
        # again. This time the objects must move closer together.
        assert sim.setConstraints(constraints).ok

        # Step the simulation --> the objects must move closer together.
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert ret_a.data.position[0] > pos_a[0]
        assert ret_b.data.position[0] < pos_b[0]
Exemplo n.º 18
0
    def test_specify_6DofSpring2_constraint(self):
        """
        Create two objects and linke them with a 6DOF constraint. The
        constraint mimicks a spring-loaded slider that will pull the objects
        together.
        """
        # Create physics simulation.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Create identical unit spheres 10 meters apart.
        id_a, id_b = 10, 20
        pos_a = (-5, 0, 0)
        pos_b = (5, 0, 0)
        obj_a = getRigidBody(position=pos_a, cshapes={'cssphere': getCSSphere()})
        obj_b = getRigidBody(position=pos_b, cshapes={'cssphere': getCSSphere()})

        # Load the objects into the physics engine.
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)

        # Compile the 6DOF constraint.
        constraints = [get6DofSpring2(rb_a=id_a, rb_b=id_b)]

        # Step the simulation. Nothing must happen because no forces or
        # constraints act upon the objects.
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)

        # Load the constraints into the physics engine and step the simulation
        # again. This time the objects must move closer together.
        assert sim.setConstraints(constraints).ok

        # Step the simulation --> the objects must move closer together.
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert ret_a.data.position[0] > pos_a[0]
        assert ret_b.data.position[0] < pos_b[0]
Exemplo n.º 19
0
    def test_set_get_AABB(self):
        """
        Create a new object with an AABB and query it back again.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create two IDs and body instances for this test.
        id_1, id_2 = 0, 1
        aabb_2 = {'cssphere': [0, 0, 0, 1, 1, 1]}
        aabb_3 = {'cssphere': [0, 0, 0, 2, 2, 2]}
        body_a = getRigidBody(cshapes={'cssphere': getCSSphere(radius=1)})
        body_b = getRigidBody(cshapes={'cssphere': getCSSphere(radius=2)})

        # Add two new objects to the DB.
        tmp = [(id_1, body_a), (id_2, body_b)]
        assert leoAPI.addCmdSpawn(tmp).ok
        leo.processCommandsAndSync()

        # Verify the two AABBs
        assert leo.allAABBs[id_1] == aabb_2
        assert leo.allAABBs[id_2] == aabb_3
Exemplo n.º 20
0
    def test_set_get_AABB(self):
        """
        Create a new object with an AABB and query it back again.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create two IDs and body instances for this test.
        id_1, id_2 = '0', '1'
        aabb_2 = {'cssphere': [0, 0, 0, 1, 1, 1]}
        aabb_3 = {'cssphere': [0, 0, 0, 2, 2, 2]}
        body_a = getRigidBody(cshapes={'cssphere': getCSSphere(radius=1)})
        body_b = getRigidBody(cshapes={'cssphere': getCSSphere(radius=2)})

        # Add two new objects to the DB.
        tmp = [(id_1, body_a), (id_2, body_b)]
        assert leoAPI.addCmdSpawn(tmp).ok
        leo.processCommandsAndSync()

        # Verify the two AABBs
        assert leo.allAABBs[id_1] == aabb_2
        assert leo.allAABBs[id_2] == aabb_3
Exemplo n.º 21
0
    def test_specify_constraints_invalid(self):
        """
        Call the constraint- related methods with invalid data and verify that
        nothing breaks.
        """
        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Create to spheres.
        id_a, id_b = '10', '20'
        obj_a = getRigidBody(cshapes={'cssphere': getCSSphere()})
        obj_b = getRigidBody(cshapes={'cssphere': getCSSphere()})

        # An empty list is valid, albeit nothing will happen.
        assert sim.setConstraints([]).ok

        # Invalid constraint types.
        assert not sim.setConstraints([1]).ok

        # Compile the constraint.
        pivot_a, pivot_b = (0, 0, 0), (1, 1, 1)
        con = [getP2P(rb_a=id_a, rb_b=id_b, pivot_a=pivot_a, pivot_b=pivot_b)]

        # Constraint is valid but the objects do not exist.
        assert not sim.setConstraints([con]).ok

        # Add one sphere to the world.
        sim.setRigidBodyData(id_a, obj_a)

        # Load the constraints into the physics engine.
        assert not sim.setConstraints(con).ok

        # Load the second sphere and apply the constraint. This time it must
        # have worked.
        sim.setRigidBodyData(id_b, obj_b)
        assert sim.setConstraints(con).ok

        # Clear all constraints
        assert sim.clearAllConstraints().ok
Exemplo n.º 22
0
    def test_specify_constraints_invalid(self):
        """
        Call the constraint- related methods with invalid data and verify that
        nothing breaks.
        """
        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Create to spheres.
        id_a, id_b = 10, 20
        obj_a = getRigidBody(cshapes={'cssphere': getCSSphere()})
        obj_b = getRigidBody(cshapes={'cssphere': getCSSphere()})

        # An empty list is valid, albeit nothing will happen.
        assert sim.setConstraints([]).ok

        # Invalid constraint types.
        assert not sim.setConstraints([1]).ok

        # Compile the constraint.
        pivot_a, pivot_b = (0, 0, 0), (1, 1, 1)
        con = [getP2P(rb_a=id_a, rb_b=id_b, pivot_a=pivot_a, pivot_b=pivot_b)]

        # Constraint is valid but the objects do not exist.
        assert not sim.setConstraints([con]).ok

        # Add one sphere to the world.
        sim.setRigidBodyData(id_a, obj_a)

        # Load the constraints into the physics engine.
        assert not sim.setConstraints(con).ok

        # Load the second sphere and apply the constraint. This time it must
        # have worked.
        sim.setRigidBodyData(id_b, obj_b)
        assert sim.setConstraints(con).ok

        # Clear all constraints
        assert sim.clearAllConstraints().ok
Exemplo n.º 23
0
    def test_modify_mass(self):
        """
        Create two identical spheres, double the mass of one, and apply the
        same force to both. The heavier sphere must have moved only half as
        far.
        """
        # Constants and parameters for this test.
        objID_a, objID_b = '10', '20'
        pos_a = [+5, 0, 0]
        pos_b = [-5, 0, 0]
        force = np.array([0, 1, 0], np.float64)
        torque = np.array([0, 0, 0], np.float64)
        cshapes = {'foo': getCSSphere()}

        # Create two identical spheres, one left, one right (x-axis).
        obj_a = getRigidBody(position=pos_a, cshapes=cshapes, imass=1)
        obj_b = getRigidBody(position=pos_b, cshapes=cshapes, imass=1)
        del pos_a, pos_b, cshapes

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send object to Bullet and progress the simulation by one second.
        # The objects must not move because no forces are at play.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)

        # Progress the simulation for one second. Nothing must happen.
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Update the mass of the second object.
        obj_b = obj_b._replace(imass=0.5 * obj_b.imass)
        sim.setRigidBodyData(objID_b, obj_b)

        # Apply the same central force that pulls both spheres forward
        # (y-axis).
        sim.applyForceAndTorque(objID_a, force, torque)
        sim.applyForceAndTorque(objID_b, force, torque)

        # Progress the simulation for another second.
        sim.compute([objID_a, objID_b], 1.0, 60)

        # The lighter sphere must have moved pretty exactly twice as far in
        # y-direction.
        ret_a = sim.getRigidBodyData(objID_a)
        assert ret_a.ok
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_b.ok
        assert abs(ret_a.data.position[1] - 2 * ret_b.data.position[1]) < 1E-5
Exemplo n.º 24
0
    def test_modify_mass(self):
        """
        Create two identical spheres, double the mass of one, and apply the
        same force to both. The heavier sphere must have moved only half as
        far.
        """
        # Constants and parameters for this test.
        objID_a, objID_b = 10, 20
        pos_a = [+5, 0, 0]
        pos_b = [-5, 0, 0]
        force = np.array([0, 1, 0], np.float64)
        torque = np.array([0, 0, 0], np.float64)
        cshapes = {'foo': getCSSphere()}

        # Create two identical spheres, one left, one right (x-axis).
        obj_a = getRigidBody(position=pos_a, cshapes=cshapes, imass=1)
        obj_b = getRigidBody(position=pos_b, cshapes=cshapes, imass=1)
        del pos_a, pos_b, cshapes

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send object to Bullet and progress the simulation by one second.
        # The objects must not move because no forces are at play.
        sim.setRigidBodyData(objID_a, obj_a)
        sim.setRigidBodyData(objID_b, obj_b)

        # Progress the simulation for one second. Nothing must happen.
        sim.compute([objID_a, objID_b], 1.0, 60)

        # Update the mass of the second object.
        obj_b = obj_b._replace(imass=0.5 * obj_b.imass)
        sim.setRigidBodyData(objID_b, obj_b)

        # Apply the same central force that pulls both spheres forward
        # (y-axis).
        sim.applyForceAndTorque(objID_a, force, torque)
        sim.applyForceAndTorque(objID_b, force, torque)

        # Progress the simulation for another second.
        sim.compute([objID_a, objID_b], 1.0, 60)

        # The lighter sphere must have moved pretty exactly twice as far in
        # y-direction.
        ret_a = sim.getRigidBodyData(objID_a)
        assert ret_a.ok
        ret_b = sim.getRigidBodyData(objID_b)
        assert ret_b.ok
        assert abs(ret_a.data.position[1] - 2 * ret_b.data.position[1]) < 1E-5
Exemplo n.º 25
0
    def test_spawnTemplates(self):
        """
        Spawn a template and verify it is available via WebServer.
        """
        self.dibbler.reset()
        azrael.datastore.init(flush=True)
        clerk = azrael.clerk.Clerk()

        # # Create two Templates. The first has only one Raw- and two
        # # Collada geometries, the other has it the other way around.
        frags_t1 = {'raw1': getFragRaw(),
                    'dae2': getFragDae(),
                    'dae3': getFragDae()}
        frags_t2 = {'raw4': getFragRaw(),
                    'raw5': getFragRaw(),
                    'dae6': getFragDae()}
        body_t1 = getRigidBody(cshapes={'cssphere': getCSSphere()})
        body_t2 = getRigidBody(cshapes={'csbox': getCSBox()})
        t1 = getTemplate('t1', rbs=body_t1, fragments=frags_t1)
        t2 = getTemplate('t2', rbs=body_t2, fragments=frags_t2)
        del frags_t1, frags_t2

        # Add both templates and verify they are available.
        assert clerk.addTemplates([t1, t2]).ok
        self.verifyTemplate('{}/t1'.format(config.url_templates), t1.fragments)
        self.verifyTemplate('{}/t2'.format(config.url_templates), t2.fragments)

        # No object instance with ID=1 must exist yet.
        url_inst = config.url_instances
        with pytest.raises(AssertionError):
            self.verifyTemplate('{}/{}'.format(url_inst, 1), t1.fragments)

        # Spawn the first template (it must get objID=1).
        ret = clerk.spawn([{'templateID': 't1', 'rbs': {'imass': 1}}])
        assert ret.data == ['1']
        self.verifyTemplate('{}/{}'.format(url_inst, 1), t1.fragments)

        # Spawn two more templates and very their instance models.
        new_objs = [{'templateID': 't2', 'rbs': {'imass': 1}},
                    {'templateID': 't1', 'rbs': {'imass': 1}}]
        ret = clerk.spawn(new_objs)
        assert ret.data == ['2', '3']
        self.verifyTemplate('{}/{}'.format(url_inst, 2), t2.fragments)
        self.verifyTemplate('{}/{}'.format(url_inst, 3), t1.fragments)
Exemplo n.º 26
0
    def test_worker_respawn(self):
        """
        Ensure the objects move correctly even though the Workers will restart
        themselves after every step.

        The test code is similar to ``test_move_two_objects_no_collision``.
        """
        # Instantiate Leonard.
        leo = azrael.leonard.LeonardDistributedZeroMQ()
        leo.workerStepsUntilQuit = (1, 10)
        leo.setup()

        # Define a force grid (not used in this test but prevent a plethora
        # of meaningleass warning messages).
        vg = azrael.vectorgrid
        assert vg.defineGrid(name='force', vecDim=3, granularity=1).ok

        # Constants and parameters for this test.
        id_0, id_1 = 0, 1
        cshapes = {'cssphere': getCSSphere(radius=1)}

        # Two State Vectors for this test.
        body_0 = getRigidBody(
            position=[0, 0, 0], velocityLin=[1, 0, 0], cshapes=cshapes)
        body_1 = getRigidBody(
            position=[0, 10, 0], velocityLin=[0, -1, 0], cshapes=cshapes)

        # Create two objects.
        tmp = [(id_0, body_0), (id_1, body_1)]
        assert leoAPI.addCmdSpawn(tmp).ok

        # Advance the simulation by 1s, but use many small time steps. This
        # ensures that the Workers will restart themselves frequently.
        for ii in range(60):
            leo.step(1.0 / 60, 1)

        # The objects must have moved according to their initial velocity.
        pos_0 = leo.allBodies[id_0].position
        pos_1 = leo.allBodies[id_1].position
        assert pos_0[1] == pos_0[2] == 0
        assert pos_1[0] == pos_1[2] == 0
        assert 0.9 <= pos_0[0] <= 1.1
        assert 8.9 <= pos_1[1] <= 9.1
Exemplo n.º 27
0
    def setup_method(self, method):
        # Reset the database.
        azrael.database.init()

        # Flush the model database.
        self.dibbler.reset()

        # Insert default objects. None of them has an actual geometry but
        # their collision shapes are: none, sphere, box.
        clerk = azrael.clerk.Clerk()

        frag = {'NoName': getFragRaw()}
        rbs_empty = getRigidBody(cshapes={'csempty': getCSEmpty()})
        rbs_sphere = getRigidBody(cshapes={'cssphere': getCSSphere()})
        rbs_box = getRigidBody(cshapes={'csbox': getCSBox()})
        t1 = getTemplate('_templateEmpty', rbs=rbs_empty, fragments=frag)
        t2 = getTemplate('_templateSphere', rbs=rbs_sphere, fragments=frag)
        t3 = getTemplate('_templateBox', rbs=rbs_box, fragments=frag)
        ret = clerk.addTemplates([t1, t2, t3])
        assert ret.ok
Exemplo n.º 28
0
    def test_update_object(self):
        """
        Add an object to Bullet, then change its parameters.
        """
        aid = '0'
        cshapes = {'foo': getCSSphere()}

        # Create an object and serialise it.
        obj_a = getRigidBody(
            scale=3.5,
            imass=4.5,
            cshapes=cshapes,
            restitution=5.5,
            rotation=(0, 1, 0, 0),
            position=(0.2, 0.4, 0.6),
            velocityLin=(0.8, 1.0, 1.2),
            velocityRot=(1.4, 1.6, 1.8))
        assert obj_a is not None

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send object to Bullet and request it back.
        sim.setRigidBodyData(aid, obj_a)
        ret = sim.getRigidBodyData(aid)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)

        # Update the object.
        obj_a = getRigidBody(
            scale=6.5,
            imass=7.5,
            cshapes=cshapes,
            restitution=8.5,
            rotation=(0, 0, 1, 0),
            position=(1.2, 1.4, 1.6),
            velocityLin=(2.8, 2.0, 2.2),
            velocityRot=(2.4, 2.6, 2.8))
        assert obj_a is not None
        sim.setRigidBodyData(aid, obj_a)
        ret = sim.getRigidBodyData(aid)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)
Exemplo n.º 29
0
    def test_update_object(self):
        """
        Add an object to Bullet, then change its parameters.
        """
        aid = '0'
        cshapes = {'foo': getCSSphere()}

        # Create an object and serialise it.
        obj_a = getRigidBody(scale=3.5,
                             imass=4.5,
                             cshapes=cshapes,
                             restitution=5.5,
                             rotation=(0, 1, 0, 0),
                             position=(0.2, 0.4, 0.6),
                             velocityLin=(0.8, 1.0, 1.2),
                             velocityRot=(1.4, 1.6, 1.8))
        assert obj_a is not None

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send object to Bullet and request it back.
        sim.setRigidBodyData(aid, obj_a)
        ret = sim.getRigidBodyData(aid)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)

        # Update the object.
        obj_a = getRigidBody(scale=6.5,
                             imass=7.5,
                             cshapes=cshapes,
                             restitution=8.5,
                             rotation=(0, 0, 1, 0),
                             position=(1.2, 1.4, 1.6),
                             velocityLin=(2.8, 2.0, 2.2),
                             velocityRot=(2.4, 2.6, 2.8))
        assert obj_a is not None
        sim.setRigidBodyData(aid, obj_a)
        ret = sim.getRigidBodyData(aid)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)
Exemplo n.º 30
0
    def test_addTemplates(self):
        """
        Add and query a template with one Raw fragment.
        """
        self.dibbler.reset()
        azrael.datastore.init(flush=True)
        clerk = azrael.clerk.Clerk()

        # Create two Templates. The first has only one Raw- and two
        # Collada geometries, the other has it the other way around.
        frags_t1 = {'foo1': getFragRaw(),
                    'bar2': getFragDae(),
                    'bar3': getFragDae()}
        frags_t2 = {'foo4': getFragRaw(),
                    'foo5': getFragRaw(),
                    'bar6': getFragDae()}
        body_a = getRigidBody(cshapes={'cssphere': getCSSphere()})
        body_b = getRigidBody(cshapes={'csbox': getCSBox()})
        t1 = getTemplate('t1', rbs=body_a, fragments=frags_t1)
        t2 = getTemplate('t2', rbs=body_b, fragments=frags_t2)
        del frags_t1, frags_t2

        # Add the first template.
        assert clerk.addTemplates([t1]) == (True, None, {'t1': True})

        # Attempt to add the same template a second time. This must not do
        # anything.
        assert clerk.addTemplates([t1]) == (True, None, {'t1': False})

        # Verify the first template is available for download via WebServer.
        url_template = config.url_templates
        self.verifyTemplate('{}/t1'.format(url_template), t1.fragments)

        # Add the second template and verify both are available for download
        # via WebServer.
        assert clerk.addTemplates([t2]).ok
        self.verifyTemplate('{}/t1'.format(url_template), t1.fragments)
        self.verifyTemplate('{}/t2'.format(url_template), t2.fragments)
Exemplo n.º 31
0
    def test_apply_force_and_torque(self):
        """
        Create object, send it to Bullet, apply a force, progress the
        simulation, and verify the object moved correctly.
        """
        # Constants and parameters for this test.
        objID = '10'
        force = np.array([0, 0, 1], np.float64)
        torque = np.array([0, 0, 1], np.float64)
        dt, maxsteps = 1.0, 60

        # Create a spherical object. Adjust the mass so that the sphere's
        # inertia is roughly unity.
        cshapes = {'foo': getCSSphere()}
        obj_a = getRigidBody(cshapes=cshapes, imass=2 / 5)

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send object to Bullet and progress the simulation by one second.
        # The objects must not move because no forces are at play.
        sim.setRigidBodyData(objID, obj_a)
        sim.compute([objID], dt, maxsteps)
        ret = sim.getRigidBodyData(objID)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)

        # Now apply a central force of one Newton in z-direction and a torque
        # of two NewtonMeters.
        sim.applyForceAndTorque(objID, force, torque)

        # Nothing must have happened because the simulation has not progressed.
        ret = sim.getRigidBodyData(objID)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)

        # Progress the simulation for another second.
        sim.compute([objID], dt, maxsteps)
        ret = sim.getRigidBodyData(objID)
        assert ret.ok

        # The object must have accelerated to the linear velocity
        #   v = a * t                  (1)
        # where the acceleration $a$ follows from
        #   F = m * a --> a = F / m    (2)
        # Substitue (2) into (1) to obtain
        #   v = t * F / m
        # or in terms of the inverse mass:
        #   v = t * F * imass
        assert np.allclose(ret.data.vLin, dt * force * (2 / 5), atol=1E-2)

        # The object must have accelerated to the angular velocity omega
        #   omega = OMEGA * t                  (1)
        #
        # where the torque $T$ follows from angular acceleration OMEGA
        #   T = I * OMEGA --> OMEGA = T / I    (2)
        #
        # Substitue (2) into (1) to obtain
        #   omega = t * (T / I)
        #
        # Our Inertia is roughly unity because we adjusted the sphere's mass
        # accordingly when we created it (ie. set it 5/2kg or 2/5 for the
        # inverse mass).
        assert np.allclose(ret.data.vRot, dt * torque * 1, atol=1E-2)
Exemplo n.º 32
0
    def test_needNewCollisionShapes(self):
        """
        Verify the conditions under which 'bullet_api' must throw out its
        current compound shape and compile a new one from scratch.

        Specifically, create a new compound shape only if:
          * type of at least one collision shape changed,
          * size of at least one collision shape changed,
          * centre of mass position changed,
          * principal axs of inertia changed.

        To clarify, there is no need to recompile the compound shape if only
        mass, moments of inertia (without a change in principal axis),
        position, rotation, etc changed.
        """
        id_a = '1'
        p, q = (0, 0, 0), (0, 0, 0, 1)
        csdefault = {'foo': getCSSphere(p, q, radius=1)}

        # Create test body with spherical collision shape.
        obj_a = getRigidBody(cshapes=csdefault)
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setRigidBodyData(id_a, obj_a)

        # Same object: do not rebuild.
        obj_new = getRigidBody(cshapes=csdefault)
        assert sim.needNewCollisionShape(id_a, obj_new) is False

        # Different position and rotation: do not rebuild.
        obj_new = getRigidBody(
            position=[0, 1, 2],
            rotation=[0, 1, 0, 1],
            cshapes=csdefault,
        )
        assert sim.needNewCollisionShape(id_a, obj_new) is False

        # New collision shape name: rebuild CS (Azrael cannot know if the old
        # one was just renamed or if it was removed and replaced by a new one
        # that just happens to have the same properties).
        obj_new = getRigidBody(cshapes={'bar': getCSSphere(p, q, radius=1)})
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New collision shape parameter (radius):
        obj_new = getRigidBody(cshapes={'foo': getCSSphere(p, q, radius=2)})
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # Changed collision shape type (box):
        obj_new = getRigidBody(cshapes={'foo': getCSBox(p, q)})
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # Additional collision shape: rebuild.
        obj_new = getRigidBody(
            cshapes={
                'foo': getCSSphere(p, q),
                'bar': getCSBox(p, q),
            }
        )
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New center of mass: rebuild.
        obj_new = getRigidBody(cshapes=csdefault, com=[1, 2, 3])
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New principal axis of inertia: rebuild.
        obj_new = getRigidBody(cshapes=csdefault, paxis=[1, 2, 3, 4])
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New inertia (but same principal axis): do not rebuild.
        obj_new = getRigidBody(cshapes=csdefault, inertia=[1, 2, 3])
        assert sim.needNewCollisionShape(id_a, obj_new) is False
Exemplo n.º 33
0
    def test_needNewCollisionShapes(self):
        """
        Verify the conditions under which 'bullet_api' must throw out its
        current compound shape and compile a new one from scratch.

        Specifically, create a new compound shape only if:
          * type of at least one collision shape changed,
          * size of at least one collision shape changed,
          * centre of mass position changed,
          * principal axs of inertia changed.

        To clarify, there is no need to recompile the compound shape if only
        mass, moments of inertia (without a change in principal axis),
        position, rotation, etc changed.
        """
        id_a = '1'
        p, q = (0, 0, 0), (0, 0, 0, 1)
        csdefault = {'foo': getCSSphere(p, q, radius=1)}

        # Create test body with spherical collision shape.
        obj_a = getRigidBody(cshapes=csdefault)
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setRigidBodyData(id_a, obj_a)

        # Same object: do not rebuild.
        obj_new = getRigidBody(cshapes=csdefault)
        assert sim.needNewCollisionShape(id_a, obj_new) is False

        # Different position and rotation: do not rebuild.
        obj_new = getRigidBody(
            position=[0, 1, 2],
            rotation=[0, 1, 0, 1],
            cshapes=csdefault,
        )
        assert sim.needNewCollisionShape(id_a, obj_new) is False

        # New collision shape name: rebuild CS (Azrael cannot know if the old
        # one was just renamed or if it was removed and replaced by a new one
        # that just happens to have the same properties).
        obj_new = getRigidBody(cshapes={'bar': getCSSphere(p, q, radius=1)})
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New collision shape parameter (radius):
        obj_new = getRigidBody(cshapes={'foo': getCSSphere(p, q, radius=2)})
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # Changed collision shape type (box):
        obj_new = getRigidBody(cshapes={'foo': getCSBox(p, q)})
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # Additional collision shape: rebuild.
        obj_new = getRigidBody(cshapes={
            'foo': getCSSphere(p, q),
            'bar': getCSBox(p, q),
        })
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New center of mass: rebuild.
        obj_new = getRigidBody(cshapes=csdefault, com=[1, 2, 3])
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New principal axis of inertia: rebuild.
        obj_new = getRigidBody(cshapes=csdefault, paxis=[1, 2, 3, 4])
        assert sim.needNewCollisionShape(id_a, obj_new) is True

        # New inertia (but same principal axis): do not rebuild.
        obj_new = getRigidBody(cshapes=csdefault, inertia=[1, 2, 3])
        assert sim.needNewCollisionShape(id_a, obj_new) is False
Exemplo n.º 34
0
    def test_specify_P2P_constraint(self):
        """
        Use a P2P constraint to test the various methods to add- and remove
        constraints.
        """
        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Create identical unit spheres at x=+/-1.
        id_a, id_b = 10, 20
        pos_a = (-1, 0, 0)
        pos_b = (1, 0, 0)
        obj_a = getRigidBody(position=pos_a, cshapes={'cssphere': getCSSphere()})
        obj_b = getRigidBody(position=pos_b, cshapes={'cssphere': getCSSphere()})

        # Load the objects into the physics engine.
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)

        # Compile the constraint.
        pivot_a, pivot_b = pos_b, pos_a
        con = [getP2P(rb_a=id_a, rb_b=id_b, pivot_a=pivot_a, pivot_b=pivot_b)]

        # Load the constraints into the physics engine.
        assert sim.setConstraints(con).ok

        # Step the simulation. Nothing must happen.
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)

        # Apply a force that will pull the left object further to the left.
        sim.applyForceAndTorque(id_a, (-10, 0, 0), (0, 0, 0))

        # Step the simulation. Both objects must have moved (almost) exactly
        # the same amount "delta".
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        delta_a = np.array(ret_a.data.position) - np.array(pos_a)
        delta_b = np.array(ret_b.data.position) - np.array(pos_b)
        assert np.allclose(delta_a, delta_b)
        assert delta_a[1] == delta_a[2] == 0

        # Remove all constraints (do it twice to test the case when there are
        # no constraints).
        assert sim.clearAllConstraints().ok
        assert sim.clearAllConstraints().ok

        # Overwrite the objects with the default data (ie put them back into
        # the original position and set their velocity to zero).
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)

        # Apply a force that will pull the left object further to the left.
        # However, now *only* the left one must move because there are not
        # constraint anymore.
        sim.applyForceAndTorque(id_a, (-10, 0, 0), (0, 0, 0))
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert not np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)
Exemplo n.º 35
0
    def test_compute_AABB(self):
        """
        Create some collision shapes and verify that 'computeAABBs' returns the
        correct results.
        """
        # Convenience.
        computeAABBs = azrael.leo_api.computeAABBs

        # Empty set of Collision shapes.
        assert computeAABBs({}) == (True, None, {})

        # Cubes with different side lengths. The algorithm must always pick
        # the largest side length times sqrt(3)
        for size in (0, 0.5, 1, 2):
            # The three side lengths used for the cubes.
            s1, s2, s3 = size, 2 * size, 3 * size

            # The AABB dimensions must always be the largest side lengths time
            # sqrt(3) to accommodate all rotations. However, Azreal adds some
            # slack and uses sqrt(3.1).
            v = s3 * np.sqrt(3.1)
            correct = (1, 2, 3, v, v, v)
            pos = correct[:3]

            cs = getCSBox(dim=(s1, s2, s3), pos=pos)
            assert computeAABBs({'1': cs}) == (True, None, {'1': correct})

            cs = getCSBox(dim=(s2, s3, s1), pos=pos)
            assert computeAABBs({'2': cs}) == (True, None, {'2': correct})

            cs = getCSBox(dim=(s3, s1, s2), pos=pos)
            assert computeAABBs({'3': cs}) == (True, None, {'3': correct})

        # The AABB for a sphere must always exactly bound the sphere.
        for radius in (0, 0.5, 1, 2):
            correct = (0, 0, 0, radius, radius, radius)
            cs = getCSSphere(radius=radius)
            assert computeAABBs({'': cs}) == (True, None, {'': correct})

        # Sphere at origin but with a rotation: must remain at origin.
        pos, rot = (0, 0, 0), (np.sqrt(2), 0, 0, np.sqrt(2))
        correct = {'': (0, 0, 0, 1, 1, 1)}
        cs = getCSSphere(radius=1, pos=pos, rot=rot)
        assert computeAABBs({'': cs}) == (True, None, correct)

        # Sphere at y=1 and rotated 180degrees around x-axis. This must result
        # in a sphere at y=-1.
        pos, rot = (0, 1, 0), (1, 0, 0, 0)
        correct = {'': (0, -1, 0, 1, 1, 1)}
        cs = getCSSphere(radius=1, pos=pos, rot=rot)
        assert computeAABBs({'': cs}) == (True, None, correct)

        # Sphere at y=1 and rotated 90degrees around x-axis. This must move the
        # sphere onto the z-axis, ie to position (x, y, z) = (0, 0, 1). Due to
        # roundoff errors it will be necessary to test with np.allclose instead
        # for exact equality.
        pos = (0, 1, 0)
        rot = (1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2))
        correct = {'': (0, 0, 1, 1, 1, 1)}
        cs = getCSSphere(radius=1, pos=pos, rot=rot)
        ret = computeAABBs({'': cs})
        assert ret.ok
        assert np.allclose(ret.data[''], correct[''])

        # Use an empty shape. This must not return any AABB.
        cs = getCSEmpty()
        assert computeAABBs({'': cs}) == (True, None, {})

        # Pass in multiple collision shapes, namely [box, empty, sphere]. This
        # must return 2 collision shapes because the empty one is skipped.
        cs = {'1': getCSSphere(), '2': getCSEmpty(), '3': getCSBox()}
        correct = {
            '1': (0, 0, 0, 1, 1, 1),
            '3': (0, 0, 0, np.sqrt(3.1), np.sqrt(3.1), np.sqrt(3.1))
        }
        assert computeAABBs(cs) == (True, None, correct)

        # Pass in invalid arguments. This must return with an error.
        assert not computeAABBs({'x': (1, 2)}).ok
Exemplo n.º 36
0
    def test_specify_P2P_constraint(self):
        """
        Use a P2P constraint to test the various methods to add- and remove
        constraints.
        """
        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Create identical unit spheres at x=+/-1.
        id_a, id_b = '10', '20'
        pos_a = (-1, 0, 0)
        pos_b = (1, 0, 0)
        obj_a = getRigidBody(position=pos_a,
                             cshapes={'cssphere': getCSSphere()})
        obj_b = getRigidBody(position=pos_b,
                             cshapes={'cssphere': getCSSphere()})

        # Load the objects into the physics engine.
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)

        # Compile the constraint.
        pivot_a, pivot_b = pos_b, pos_a
        con = [getP2P(rb_a=id_a, rb_b=id_b, pivot_a=pivot_a, pivot_b=pivot_b)]

        # Load the constraints into the physics engine.
        assert sim.setConstraints(con).ok

        # Step the simulation. Both objects must stay put.
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)

        # Apply a force that will pull the left object further to the left.
        sim.applyForceAndTorque(id_a, (-10, 0, 0), (0, 0, 0))

        # Step the simulation. Both objects must have moved (almost) exactly
        # the same amount "delta".
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        delta_a = np.array(ret_a.data.position) - np.array(pos_a)
        delta_b = np.array(ret_b.data.position) - np.array(pos_b)
        assert np.allclose(delta_a, delta_b)
        assert delta_a[1] == delta_a[2] == 0

        # Remove all constraints (do it twice to test the case when there are
        # no constraints).
        assert sim.clearAllConstraints().ok
        assert sim.clearAllConstraints().ok

        # Overwrite the objects with the default data (ie put them back into
        # the original position and set their velocity to zero).
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)

        # Apply a force that will pull the left object further to the left.
        # However, now *only* the left one must move because there are not
        # constraint anymore.
        sim.applyForceAndTorque(id_a, (-10, 0, 0), (0, 0, 0))
        sim.compute([id_a, id_b], 1.0, 60)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok
        assert not np.allclose(ret_a.data.position, pos_a)
        assert np.allclose(ret_b.data.position, pos_b)
Exemplo n.º 37
0
    def test_get_contacts(self, num_sub_steps):
        """
        Verify that every call to 'compute' also creates collision contacts.

        This test runs twice, once with a single sub-step and once with
        multiple sub-steps. The reason is due to a changed collision callback
        in Bullet. The original method could only gather contacts from the last
        sub-step, which means the number of sub-steps had to be set to 1. Now
        there is a better version which is independent of the number of
        sub-steps. However, just to be sure, I am testing both here.
        """
        # Create a test object.
        cshapes = {'1': getCSSphere((0, 0, 0), (0, 0, 0, 1))}
        obj_a = getRigidBody(
            scale=1,
            imass=1,
            cshapes=cshapes,
            restitution=1,
            rotation=(0, 1, 0, 0),
            position=(0, 0, 0),
            velocityLin=(0, 0, 0),
            velocityRot=(0, 0, 0)
        )

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Verify there are no collision contacts, and that a simulation for
        # unknown objects does not produce any.
        assert sim.getLastContacts() == (True, None, [])
        assert not sim.compute(['0'], 1, num_sub_steps).ok
        assert sim.getLastContacts() == (True, None, [])

        # Add the object and verify again that there are still no collisions (a
        # single object cannot collide with anything).
        sim.setRigidBodyData('0', obj_a)
        assert sim.getLastContacts() == (True, None, [])
        assert sim.compute(['0'], 1, num_sub_steps).ok
        assert sim.getLastContacts() == (True, None, [])

        # Add another body with the same attributes as the first. This ensures
        # they touch and must create a collision *after* the 'compute'
        # method was called.
        sim.setRigidBodyData('1', obj_a)
        assert sim.getLastContacts() == (True, None, [])

        assert sim.compute(['0', '1'], 1, num_sub_steps).ok

        # Query the collision contacts produced by both methods.
        ret = sim.getLastContacts()

        # Verify the returned contacts from both methods.
        assert ret.ok

        # The collision informaiton must be provided in the form (aidA, aidB,
        # [colPosA_0, colPosB_0, colPosA_1, colPosB_2, ...]). In this test
        # exactly one collision must have been created. The next few lines
        # disassemble this result and verify it is correct. We will also verify
        # that all types are native to Python.
        assert len(ret.data) == 1
        data = ret.data[0]

        # Unpack the constituents.
        aidA, aidB, colPositions = data
        assert aidA == '0' and aidB == '1'

        # The collision points must be a list of 3-tuples. There must be
        # exactly two such 3-tuple if we only simulated a single sub-step:
        # one for the collision contact on object A and B, respectively. If we
        # did multiple sub-steps, then Bullet will probably have generated
        # contacts in several sub-steps until it was resolved. How many exactly
        # is difficult to predict however.
        if num_sub_steps == 1:
            assert isinstance(colPositions, list) and len(colPositions) == 2
        else:
            assert isinstance(colPositions, list) and len(colPositions) >= 2
        assert len(colPositions) % 2 == 0
        for colPosA, colPosB in zip(colPositions[0::2], colPositions[1::2]):
            assert isinstance(colPosA, tuple) and len(colPosA) == 3
            assert isinstance(colPosB, tuple) and len(colPosB) == 3

        # The 'compute' method must clear the contacts every time. To test
        # this, call it with an empty list of objects. This must do nothing in
        # terms of physics, but the contacts data must still be erased.
        assert sim.compute([], 1, num_sub_steps).ok
        assert sim.getLastContacts() == (True, None, [])
Exemplo n.º 38
0
    def test_apply_force_and_torque(self):
        """
        Create object, send it to Bullet, apply a force, progress the
        simulation, and verify the object moved correctly.
        """
        # Constants and parameters for this test.
        objID = 10
        force = np.array([0, 0, 1], np.float64)
        torque = np.array([0, 0, 1], np.float64)
        dt, maxsteps = 1.0, 60

        # Create a spherical object. Adjust the mass so that the sphere's
        # inertia is roughly unity.
        cshapes = {'foo': getCSSphere()}
        obj_a = getRigidBody(cshapes=cshapes, imass=2 / 5)

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Send object to Bullet and progress the simulation by one second.
        # The objects must not move because no forces are at play.
        sim.setRigidBodyData(objID, obj_a)
        sim.compute([objID], dt, maxsteps)
        ret = sim.getRigidBodyData(objID)
        assert (ret.ok, ret.data) == (True, obj_a)

        # Now apply a central force of one Newton in z-direction and a torque
        # of two NewtonMeters.
        sim.applyForceAndTorque(objID, force, torque)

        # Nothing must have happened because the simulation has not progressed.
        ret = sim.getRigidBodyData(objID)
        assert (ret.ok, ret.data) == (True, obj_a)

        # Progress the simulation for another second.
        sim.compute([objID], dt, maxsteps)
        ret = sim.getRigidBodyData(objID)
        assert ret.ok
        velLin, velRot = ret.data.velocityLin, ret.data.velocityRot

        # The object must have accelerated to the linear velocity
        #   v = a * t                  (1)
        # where the acceleration $a$ follows from
        #   F = m * a --> a = F / m    (2)
        # Substitue (2) into (1) to obtain
        #   v = t * F / m
        # or in terms of the inverse mass:
        #   v = t * F * imass
        assert np.allclose(velLin, dt * force * (2 / 5), atol=1E-2)

        # The object must have accelerated to the angular velocity omega
        #   omega = OMEGA * t                  (1)
        #
        # where the torque $T$ follows from angular acceleration OMEGA
        #   T = I * OMEGA --> OMEGA = T / I    (2)
        #
        # Substitue (2) into (1) to obtain
        #   omega = t * (T / I)
        #
        # Our Inertia is roughly unity because we adjusted the sphere's mass
        # accordingly when we created it (ie. set it 5/2kg or 2/5 for the
        # inverse mass).
        assert np.allclose(velRot, dt * torque * 1, atol=1E-2)
Exemplo n.º 39
0
    def test_setRigidBody(self):
        """
        Set and retrieve object attributes like position, velocity,
        acceleration, and rotation.
        """
        # Instantiate a Leonard.
        leo = getLeonard()

        # Test constants.
        body_new = {
            'imass': 2,
            'scale': 3,
            'cshapes': {
                'csempty': getCSEmpty()
            },
            'position': (1, 2, 5),
            'velocityLin': (8, 9, 10.5),
            'velocityRot': (9, 10, 11.5),
            'rotation': (11, 12.5, 13, 13.5)
        }

        # Create a test body.
        id_1 = '0'
        body = getRigidBody(cshapes={'csempty': getCSEmpty()})

        # Add the object to the DB with ID=0.
        assert leoAPI.addCmdSpawn([(id_1, body)]).ok
        leo.processCommandsAndSync()

        # Modify the state vector for body with id_1.
        assert leoAPI.addCmdModifyBodyState(id_1, body_new).ok
        leo.processCommandsAndSync()

        # Query the body again and verify the changes are in effect.
        ret = leo.allBodies[id_1]
        assert ret.imass == body_new['imass']
        assert ret.scale == body_new['scale']
        assert np.array_equal(ret.position, body_new['position'])
        assert np.array_equal(ret.velocityLin, body_new['velocityLin'])
        assert np.array_equal(ret.velocityRot, body_new['velocityRot'])
        assert np.array_equal(ret.rotation, body_new['rotation'])

        # Query the AABB, update the collision shapes, and verify that the new
        # AABBs are in effect.
        assert leo.allAABBs[id_1] == {}

        # Modify the body state by adding a collision shape.
        body_new = {'cshapes': {'cssphere': getCSSphere(radius=1)}}
        assert body_new is not None
        assert leoAPI.addCmdModifyBodyState(id_1, body_new).ok
        leo.processCommandsAndSync()
        assert leo.allAABBs[id_1] == {'cssphere': [0, 0, 0, 1, 1, 1]}

        # Modify the body state by adding a collision shape.
        cs_a = getCSSphere(radius=1, pos=(1, 2, 3))
        cs_b = getCSSphere(radius=2, pos=(4, 5, 6))
        cshapes = {'1': cs_a, '2': getCSEmpty(), '3': cs_b}
        body_new = {'cshapes': cshapes}
        assert leoAPI.addCmdModifyBodyState(id_1, body_new).ok
        leo.processCommandsAndSync()
        correct = {'1': [1, 2, 3, 1, 1, 1], '3': [4, 5, 6, 2, 2, 2]}
        assert leo.allAABBs[id_1] == correct
Exemplo n.º 40
0
    def test_computeCollisionSetsAABB_viaLeonard(self, dim):
        """
        Create a sequence of 10 test objects and sync them to Leonard. Their
        positions only differ in the ``dim`` dimension.

        Then use subsets of these 10 objects to test basic collision detection.

        This uses the Azrael toolchain to create objects and sync them the
        Leonard. This ensures the data propagates coorectly from the
        interfaces, via Leonard, to the broadphase algorithm.
        """
        # Get a Leonard instance.
        leo = getLeonard(azrael.leonard.LeonardBase)

        # Create the IDs for the test bodies.
        num_bodies = 10

        # Create several rigid bodies with a spherical collision shape.
        cs = {'1': getCSSphere(radius=1)}
        if dim == 0:
            states = [getRigidBody(position=[_, 0, 0], cshapes=cs) for _ in range(10)]
        elif dim == 1:
            states = [getRigidBody(position=[0, _, 0], cshapes=cs) for _ in range(10)]
        elif dim == 2:
            states = [getRigidBody(position=[0, 0, _], cshapes=cs) for _ in range(10)]
        else:
            print('Invalid dimension for this test')
            assert False

        # Add all objects to the Body State DB and sync with Leonard.
        for objID, bs in enumerate(states):
            assert leoAPI.addCmdSpawn([(objID, bs)]).ok
        del states
        leo.processCommandsAndSync()

        # Sanity check: the number of test IDs must match the number of objects
        # in Leonard.
        assert len(leo.allBodies) == num_bodies

        def ccsWrapper(test_objIDs, expected_objIDs):
            """
            Assert that ``test_objIDs`` form the ``expected_objIDs`` collision
            sets.

            This is a convenience wrapper to facilitate readable tests.
            """
            # Compile the set of bodies- and their AABBs for this test run.
            bodies = {_: leo.allBodies[_] for _ in test_objIDs}
            AABBs = {_: leo.allAABBs[_] for _ in test_objIDs}

            # Determine the list of broadphase collision sets.
            ret = azrael.leonard.computeCollisionSetsAABB(bodies, AABBs)
            assert ret.ok

            # Convert the reference data to a sorted list of sets.
            expected_objIDs = sorted([set(_) for _ in expected_objIDs])
            computed_objIDs = sorted([set(_) for _ in ret.data])

            # Return the equality of the two list of lists.
            assert expected_objIDs == computed_objIDs

        # Two non-overlapping objects.
        ccsWrapper([0, 9], [[0], [9]])

        # Two overlapping objects.
        ccsWrapper([0, 1], [[0, 1]])

        # Three sets.
        ccsWrapper([0, 1, 5, 8, 9], [[0, 1], [5], [8, 9]])

        # Same test, but objects are passed in a different sequence. This must
        # not alter the test outcome.
        ccsWrapper([0, 5, 1, 9, 8], [[0, 1], [5], [8, 9]])

        # All objects must form one connected set.
        ccsWrapper(list(range(10)), [list(range(10))])
Exemplo n.º 41
0
    def test_compute_AABB(self):
        """
        Create some collision shapes and verify that 'computeAABBs' returns the
        correct results.
        """
        # Convenience.
        computeAABBs = azrael.leo_api.computeAABBs

        # Empty set of Collision shapes.
        assert computeAABBs({}) == (True, None, {})

        # Cubes with different side lengths. The algorithm must always pick
        # the largest side length times sqrt(3)
        for size in (0, 0.5, 1, 2):
            # The three side lengths used for the cubes.
            s1, s2, s3 = size, 2 * size, 3 * size

            # The AABB dimensions must always be the largest side lengths time
            # sqrt(3) to accommodate all rotations. However, Azreal adds some
            # slack and uses sqrt(3.1).
            v = s3 * np.sqrt(3.1)
            correct = (1, 2, 3, v, v, v)
            pos = correct[:3]

            cs = getCSBox(dim=(s1, s2, s3), pos=pos)
            assert computeAABBs({'1': cs}) == (True, None, {'1': correct})

            cs = getCSBox(dim=(s2, s3, s1), pos=pos)
            assert computeAABBs({'2': cs}) == (True, None, {'2': correct})

            cs = getCSBox(dim=(s3, s1, s2), pos=pos)
            assert computeAABBs({'3': cs}) == (True, None, {'3': correct})

        # The AABB for a sphere must always exactly bound the sphere.
        for radius in (0, 0.5, 1, 2):
            correct = (0, 0, 0, radius, radius, radius)
            cs = getCSSphere(radius=radius)
            assert computeAABBs({'': cs}) == (True, None, {'': correct})

        # Sphere at origin but with a rotation: must remain at origin.
        pos, rot = (0, 0, 0), (np.sqrt(2), 0, 0, np.sqrt(2))
        correct = {'': (0, 0, 0, 1, 1, 1)}
        cs = getCSSphere(radius=1, pos=pos, rot=rot)
        assert computeAABBs({'': cs}) == (True, None, correct)

        # Sphere at y=1 and rotated 180degrees around x-axis. This must result
        # in a sphere at y=-1.
        pos, rot = (0, 1, 0), (1, 0, 0, 0)
        correct = {'': (0, -1, 0, 1, 1, 1)}
        cs = getCSSphere(radius=1, pos=pos, rot=rot)
        assert computeAABBs({'': cs}) == (True, None, correct)

        # Sphere at y=1 and rotated 90degrees around x-axis. This must move the
        # sphere onto the z-axis, ie to position (x, y, z) = (0, 0, 1). Due to
        # roundoff errors it will be necessary to test with np.allclose instead
        # for exact equality.
        pos = (0, 1, 0)
        rot = (1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2))
        correct = {'': (0, 0, 1, 1, 1, 1)}
        cs = getCSSphere(radius=1, pos=pos, rot=rot)
        ret = computeAABBs({'': cs})
        assert ret.ok
        assert np.allclose(ret.data[''], correct[''])

        # Use an empty shape. This must not return any AABB.
        cs = getCSEmpty()
        assert computeAABBs({'': cs}) == (True, None, {})

        # Pass in multiple collision shapes, namely [box, empty, sphere]. This
        # must return 2 collision shapes because the empty one is skipped.
        cs = {'1': getCSSphere(), '2': getCSEmpty(), '3': getCSBox()}
        correct = {
            '1': (0, 0, 0, 1, 1, 1),
            '3': (0, 0, 0, np.sqrt(3.1), np.sqrt(3.1), np.sqrt(3.1))
        }
        assert computeAABBs(cs) == (True, None, correct)

        # Pass in invalid arguments. This must return with an error.
        assert not computeAABBs({'x': (1, 2)}).ok
Exemplo n.º 42
0
    def test_create_fetch_template(self, client_type):
        """
        Add a new object to the templateID DB and query it again.
        """
        # Get the client for this test.
        client = self.clients[client_type]

        # Request an invalid ID.
        assert not client.getTemplates(['blah']).ok

        # Clerk has default objects. This one has an empty collision shape...
        name_1 = '_templateEmpty'
        ret = client.getTemplates([name_1])
        assert ret.ok and (len(ret.data) == 1)
        assert ret.data[name_1]['template'].rbs.cshapes == {'csempty': getCSEmpty()}

        # ... this one is a sphere...
        name_2 = '_templateSphere'
        ret = client.getTemplates([name_2])
        assert ret.ok and (len(ret.data) == 1)
        assert ret.data[name_2]['template'].rbs.cshapes == {'cssphere': getCSSphere()}

        # ... and this one is a box.
        name_3 = '_templateBox'
        ret = client.getTemplates([name_3])
        assert ret.ok and (len(ret.data) == 1)
        assert ret.data[name_3]['template'].rbs.cshapes == {'csbox': getCSBox()}

        # Retrieve all three again but with a single call.
        ret = client.getTemplates([name_1, name_2, name_3])
        assert ret.ok
        assert set(ret.data.keys()) == set((name_1, name_2, name_3))
        assert ret.data[name_2]['template'].rbs.cshapes == {'cssphere': getCSSphere()}
        assert ret.data[name_3]['template'].rbs.cshapes == {'csbox': getCSBox()}
        assert ret.data[name_1]['template'].rbs.cshapes == {'csempty': getCSEmpty()}

        # Add a new object template.
        frag = {'bar': getFragRaw()}
        body = getRigidBody()
        temp_name = 't1'
        temp_orig = getTemplate(temp_name, rbs=body, fragments=frag)
        assert client.addTemplates([temp_orig]).ok

        # Fetch the just added template again and verify its content (skip the
        # geometry because it contains only meta information and will be
        # checked afterwards).
        ret = client.getTemplates([temp_name])
        assert ret.ok and (len(ret.data) == 1)
        temp_out = ret.data[temp_name]['template']
        assert temp_out.boosters == temp_orig.boosters
        assert temp_out.factories == temp_orig.factories
        assert temp_out.rbs == temp_orig.rbs

        # Fetch the geometry from the web server and verify it.
        ret = client.getTemplateGeometry(ret.data[temp_name])
        assert ret.ok
        assert ret.data['bar'] == frag['bar'].fragdata
        del ret, temp_out, temp_orig

        # Define a new object with two boosters and one factory unit.
        # The 'boosters' and 'factories' arguments are a list of named
        # tuples. Their first argument is the unit ID (Azrael does not
        # automatically assign any).
        boosters = {
            '0': types.Booster(pos=(0, 0, 0), direction=(0, 0, 1),
                               minval=0, maxval=0.5, force=0),
            '1': types.Booster(pos=(0, 0, 0), direction=(0, 0, 1),
                               minval=0, maxval=0.5, force=0),
        }
        factories = {
            '0': types.Factory(pos=(0, 0, 0), direction=(0, 0, 1),
                               templateID='_templateBox',
                               exit_speed=(0.1, 0.5))
        }

        # Attempt to query the geometry of a non-existing object.
        assert client.getFragments([1]) == (True, None, {1: None})

        # Define a new template, add it to Azrael, spawn it, and record its
        # object ID.
        body = getRigidBody(cshapes={'csbox': getCSBox()})
        temp = getTemplate('t2',
                           rbs=body,
                           fragments=frag,
                           boosters=boosters,
                           factories=factories)
        assert client.addTemplates([temp]).ok
        init = {'templateID': temp.aid,
                'rbs': {'position': (0, 0, 0)}}
        ret = client.spawn([init])
        assert ret.ok and len(ret.data) == 1
        objID = ret.data[0]

        # Retrieve- and verify the geometry of the just spawned object.
        ret = client.getFragments([objID])
        assert ret.ok
        assert ret.data[objID]['bar']['fragtype'] == 'RAW'

        # Retrieve the entire template and verify the CS and geometry, and
        # number of boosters/factories.
        ret = client.getTemplates([temp.aid])
        assert ret.ok and (len(ret.data) == 1)
        t_data = ret.data[temp.aid]['template']
        assert t_data.rbs == body
        assert t_data.boosters == temp.boosters
        assert t_data.factories == temp.factories

        # Fetch the geometry from the Web server and verify it is correct.
        ret = client.getTemplateGeometry(ret.data[temp.aid])
        assert ret.ok
        assert ret.data['bar'] == frag['bar'].fragdata
Exemplo n.º 43
0
    def test_get_contacts(self, num_sub_steps):
        """
        Verify that every call to 'compute' also creates collision contacts.

        This test runs twice, once with a single sub-step and once with
        multiple sub-steps. The reason is due to a changed collision callback
        in Bullet. The original method could only gather contacts from the last
        sub-step, which means the number of sub-steps had to be set to 1. Now
        there is a better version which is independent of the number of
        sub-steps. However, just to be sure, I am testing both here.
        """
        # Create a test object.
        cshapes = {'1': getCSSphere((0, 0, 0), (0, 0, 0, 1))}
        obj_a = getRigidBody(scale=1,
                             imass=1,
                             cshapes=cshapes,
                             restitution=1,
                             rotation=(0, 1, 0, 0),
                             position=(0, 0, 0),
                             velocityLin=(0, 0, 0),
                             velocityRot=(0, 0, 0))

        # Instantiate Bullet engine.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)

        # Verify there are no collision contacts, and that a simulation for
        # unknown objects does not produce any.
        assert sim.getLastContacts() == (True, None, [])
        assert not sim.compute(['0'], 1, num_sub_steps).ok
        assert sim.getLastContacts() == (True, None, [])

        # Add the object and verify again that there are still no collisions (a
        # single object cannot collide with anything).
        sim.setRigidBodyData('0', obj_a)
        assert sim.getLastContacts() == (True, None, [])
        assert sim.compute(['0'], 1, num_sub_steps).ok
        assert sim.getLastContacts() == (True, None, [])

        # Add another body with the same attributes as the first. This ensures
        # they touch and must create a collision *after* the 'compute'
        # method was called.
        sim.setRigidBodyData('1', obj_a)
        assert sim.getLastContacts() == (True, None, [])

        assert sim.compute(['0', '1'], 1, num_sub_steps).ok

        # Query the collision contacts produced by both methods.
        ret = sim.getLastContacts()

        # Verify the returned contacts from both methods.
        assert ret.ok

        # The collision informaiton must be provided in the form (aidA, aidB,
        # [colPosA_0, colPosB_0, colPosA_1, colPosB_2, ...]). In this test
        # exactly one collision must have been created. The next few lines
        # disassemble this result and verify it is correct. We will also verify
        # that all types are native to Python.
        assert len(ret.data) == 1
        data = ret.data[0]

        # Unpack the constituents.
        aidA, aidB, colPositions = data
        assert aidA == '0' and aidB == '1'

        # The collision points must be a list of 3-tuples. There must be
        # exactly two such 3-tuple if we only simulated a single sub-step:
        # one for the collision contact on object A and B, respectively. If we
        # did multiple sub-steps, then Bullet will probably have generated
        # contacts in several sub-steps until it was resolved. How many exactly
        # is difficult to predict however.
        if num_sub_steps == 1:
            assert isinstance(colPositions, list) and len(colPositions) == 2
        else:
            assert isinstance(colPositions, list) and len(colPositions) >= 2
        assert len(colPositions) % 2 == 0
        for colPosA, colPosB in zip(colPositions[0::2], colPositions[1::2]):
            assert isinstance(colPosA, tuple) and len(colPosA) == 3
            assert isinstance(colPosB, tuple) and len(colPosB) == 3

        # The 'compute' method must clear the contacts every time. To test
        # this, call it with an empty list of objects. This must do nothing in
        # terms of physics, but the contacts data must still be erased.
        assert sim.compute([], 1, num_sub_steps).ok
        assert sim.getLastContacts() == (True, None, [])