Exemple #1
0
    def test_add_get_multiple(self):
        """
        Add multiple objects to the DB.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create two object IDs for this test.
        id_1, id_2 = '1', '2'

        # The number of bodies in Leonard must be zero.
        assert len(leo.allBodies) == 0

        # Create an object and serialise it.
        body_1 = getRigidBody(position=[0, 0, 0])
        body_2 = getRigidBody(position=[10, 10, 10])

        # Add the bodies to Leonard.
        tmp = [(id_1, body_1), (id_2, body_2)]
        assert leoAPI.addCmdSpawn(tmp)
        leo.processCommandsAndSync()

        # Verify the bodies.
        assert leo.allBodies[id_1] == body_1
        assert leo.allBodies[id_2] == body_2
Exemple #2
0
    def test_add_get_multiple(self):
        """
        Add multiple objects to the DB.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create two object IDs for this test.
        id_1, id_2 = 1, 2

        # The number of bodies in Leonard must be zero.
        assert len(leo.allBodies) == 0

        # Create an object and serialise it.
        body_1 = getRigidBody(position=[0, 0, 0])
        body_2 = getRigidBody(position=[10, 10, 10])

        # Add the bodies to Leonard.
        tmp = [(id_1, body_1), (id_2, body_2)]
        assert leoAPI.addCmdSpawn(tmp)
        leo.processCommandsAndSync()

        # Verify the bodies.
        assert leo.allBodies[id_1] == body_1
        assert leo.allBodies[id_2] == body_2
Exemple #3
0
    def test_move_two_objects_no_collision(self, clsLeonard):
        """
        Same as previous test but with two objects.
        """
        # Get a Leonard instance.
        leo = getLeonard(clsLeonard)

        # Constants and parameters for this test.
        id_0, id_1 = 0, 1
        body_0 = getRigidBody(position=[0, 0, 0], velocityLin=[1, 0, 0])
        body_1 = getRigidBody(position=[0, 10, 0], velocityLin=[0, -1, 0])

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

        # Advance the simulation by 1s.
        leo.step(1.0, 60)

        # 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
Exemple #4
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]}
Exemple #5
0
    def test_updateLocalCache(self):
        """
        Update the local object cache in Leonard based on a Work Package.
        """
        # Get a Leonard instance.
        leo = getLeonard(azrael.leonard.LeonardDistributedZeroMQ)

        # Convenience.
        body_1 = getRigidBody(imass=1)
        body_2 = getRigidBody(imass=2)
        id_1, id_2 = 1, 2

        # Spawn new objects.
        tmp = [(id_1, body_1), (id_2, body_2)]
        assert leoAPI.addCmdSpawn(tmp).ok
        leo.processCommandsAndSync()

        # Create a new State Vector to replace the old one.
        body_3 = getRigidBody(imass=4, position=[1, 2, 3])
        newWP = [(id_1, body_3)]

        # Check the State Vector for objID=id_1 before and after the update.
        assert getRigidBody(*leo.allBodies[id_1]) == body_1
        leo.updateLocalCache(newWP)
        assert getRigidBody(*leo.allBodies[id_1]) == body_3
    def test_GetRigidBodyData(self):
        """
        Test codec for GetRigidBodyData.
        """
        # Clerk --> Client.
        frag_states = {
            '1': {'scale': 1, 'position': [0, 1, 2], 'rotation': [0, 0, 0, 1]},
            '2': {'scale': 2, 'position': [3, 4, 5], 'rotation': [1, 0, 0, 0]},
        }

        # The payload contains fragment- and body state data for each object.
        # The payload used here covers all cases where both, only one of the
        # two, or neither are defined.
        payload = {
            1: {'frag': frag_states, 'rbs': getRigidBody()},
            2: {'frag': {}, 'rbs': getRigidBody()},
            3: None,
        }
        del frag_states

        # Encode source data and simulate wire transmission.
        enc = protocol.FromClerk_GetRigidBodyData_Encode(payload)
        enc = json.loads(json.dumps(enc))

        # Verify that the rigid bodies survived the serialisation.
        for objID in [1, 2]:
            # Convenience.
            src = payload[objID]
            dst = enc[str(objID)]

            # Compile a rigid body from the returned data and compare it to the
            # original.
            assert src['rbs'] == aztypes.RigidBodyData(**dst['rbs'])

        assert enc['3'] is None
Exemple #7
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
Exemple #8
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
    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
Exemple #10
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)
Exemple #11
0
    def test_processCommandQueue(self):
        """
        Create commands to spawn-, delete, and modify objects or their booster
        values. Then verify that ``processCommandQueue`` corrently updates
        Leonard's object cache.
        """
        # Get a Leonard instance.
        leo = getLeonard(azrael.leonard.LeonardDistributedZeroMQ)

        # Convenience.
        body_1 = getRigidBody(imass=1)
        body_2 = getRigidBody(imass=2)
        id_1, id_2 = 1, 2

        # Cache must be empty.
        assert len(leo.allBodies) == len(leo.allForces) == 0

        # Spawn two objects.
        tmp = [(id_1, body_1), (id_2, body_2)]
        assert leoAPI.addCmdSpawn(tmp).ok
        leo.processCommandsAndSync()

        # Verify the local cache (forces and torques must default to zero).
        assert getRigidBody(*leo.allBodies[id_1]) == body_1
        assert getRigidBody(*leo.allBodies[id_2]) == body_2
        tmp = leo.allForces[id_1]
        assert tmp.forceDirect == tmp.torqueDirect == [0, 0, 0]
        assert tmp.forceBoost == tmp.torqueBoost == [0, 0, 0]
        del tmp

        # Remove first object.
        assert leoAPI.addCmdRemoveObject(id_1).ok
        leo.processCommandsAndSync()
        assert id_1 not in leo.allBodies
        assert id_1 not in leo.allForces

        # Change the State Vector of id_2.
        pos = (10, 11.5, 12)
        body_3 = {'position': pos}
        assert leo.allBodies[id_2].position == (0, 0, 0)
        assert leoAPI.addCmdModifyBodyState(id_2, body_3).ok
        leo.processCommandsAndSync()
        assert leo.allBodies[id_2].position == pos

        # Apply a direct force and torque to id_2.
        force, torque = [1, 2, 3], [4, 5, 6]
        assert leoAPI.addCmdDirectForce(id_2, force, torque).ok
        leo.processCommandsAndSync()
        assert leo.allForces[id_2].forceDirect == force
        assert leo.allForces[id_2].torqueDirect == torque

        # Specify a new force- and torque value due to booster activity.
        force, torque = [1, 2, 3], [4, 5, 6]
        assert leoAPI.addCmdBoosterForce(id_2, force, torque).ok
        leo.processCommandsAndSync()
        assert leo.allForces[id_2].forceBoost == force
        assert leo.allForces[id_2].torqueBoost == torque
    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)
Exemple #13
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)
    def test_cshape_with_offset(self):
        """
        Same as above except that the collision shape has a different position
        relative to the rigid body.

        This test is to establish that the relative positions of the collision
        shapes are correctly passed to Bullet and taken into account in a
        simulation.

        Setup: place a box above a plane and verify that after a long time the
        box will have come to rest on the infinitely large plane.
        """
        aid_1, aid_2 = '1', '2'

        # Instantiate Bullet engine and activate gravity.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setGravity((0, 0, -10))

        # Create a box above a static plane. The ground plane is at z=-1. The
        # rigid body for the box is initially at z = 5, however, the collision
        # shape for that rigid body is actually z = 5 + ofs_z.
        ofs_z = 10
        cs_plane = getCSPlane(normal=(0, 0, 1), ofs=-1)
        cs_box = getCSBox(pos=(0, 0, ofs_z))
        b_plane = getRigidBody(imass=0, cshapes={'csplane': cs_plane})
        b_box = getRigidBody(position=(0, 0, 5), cshapes={'csbox': cs_box})
        assert b_box is not None
        assert b_plane is not None

        # Add the objects to the simulation and verify their positions.
        sim.setRigidBodyData(aid_1, b_plane)
        sim.setRigidBodyData(aid_2, b_box)
        ret_plane = sim.getRigidBodyData(aid_1)
        ret_box = sim.getRigidBodyData(aid_2)
        assert (ret_plane.ok is True) and (ret_box.ok is True)
        assert ret_plane.data.position[2] == 0
        assert ret_box.data.position[2] == 5

        # Step the simulation often enough for the box to fall down and come to
        # rest on the surface.
        dt, maxsteps = 1.0, 60
        for ii in range(10):
            sim.compute([aid_1, aid_2], dt, maxsteps)

        # Verify that the plane has not moved (because it is static). If the
        # position of the box' collision shape were at the origin of the body,
        # then the body's position should be approximately zero. However, since
        # the collisions shape is at 'ofs_z' higher, the final resting position
        # of the body must be 'ofs_z' lower, ie rb_position + ofs_z must now be
        # approximately zero.
        ret_plane = sim.getRigidBodyData(aid_1)
        ret_box = sim.getRigidBodyData(aid_2)
        assert (ret_plane.ok is True) and (ret_box.ok is True)
        assert ret_plane.data.position[2] == 0
        assert abs(ret_box.data.position[2] + ofs_z) < 1E-3
Exemple #15
0
    def test_cshape_with_offset(self):
        """
        Same as above except that the collision shape has a different position
        relative to the rigid body.

        This test is to establish that the relative positions of the collision
        shapes are correctly passed to Bullet and taken into account in a
        simulation.

        Setup: place a box above a plane and verify that after a long time the
        box will have come to rest on the infinitely large plane.
        """
        aid_1, aid_2 = '1', '2'

        # Instantiate Bullet engine and activate gravity.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setGravity((0, 0, -10))

        # Create a box above a static plane. The ground plane is at z=-1. The
        # rigid body for the box is initially at z = 5, however, the collision
        # shape for that rigid body is actually z = 5 + ofs_z.
        ofs_z = 10
        cs_plane = getCSPlane(normal=(0, 0, 1), ofs=-1)
        cs_box = getCSBox(pos=(0, 0, ofs_z))
        b_plane = getRigidBody(imass=0, cshapes={'csplane': cs_plane})
        b_box = getRigidBody(position=(0, 0, 5), cshapes={'csbox': cs_box})
        assert b_box is not None
        assert b_plane is not None

        # Add the objects to the simulation and verify their positions.
        sim.setRigidBodyData(aid_1, b_plane)
        sim.setRigidBodyData(aid_2, b_box)
        ret_plane = sim.getRigidBodyData(aid_1)
        ret_box = sim.getRigidBodyData(aid_2)
        assert (ret_plane.ok is True) and (ret_box.ok is True)
        assert ret_plane.data.position[2] == 0
        assert ret_box.data.position[2] == 5

        # Step the simulation often enough for the box to fall down and come to
        # rest on the surface.
        dt, maxsteps = 1.0, 60
        for ii in range(10):
            sim.compute([aid_1, aid_2], dt, maxsteps)

        # Verify that the plane has not moved (because it is static). If the
        # position of the box' collision shape were at the origin of the body,
        # then the body's position should be approximately zero. However, since
        # the collisions shape is at 'ofs_z' higher, the final resting position
        # of the body must be 'ofs_z' lower, ie rb_position + ofs_z must now be
        # approximately zero.
        ret_plane = sim.getRigidBodyData(aid_1)
        ret_box = sim.getRigidBodyData(aid_2)
        assert (ret_plane.ok is True) and (ret_box.ok is True)
        assert ret_plane.data.position[2] == 0
        assert abs(ret_box.data.position[2] + ofs_z) < 1E-3
Exemple #16
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)
Exemple #17
0
    def test_RigidBodyData_tuple(self):
        """
        Test the ``RigidBodyData`` class, most notably the __eq__ method.
        """
        # Compare two identical objects.
        sv1 = getRigidBody()
        sv2 = getRigidBody()
        assert sv1 == sv2

        # Compare two different objects.
        sv1 = getRigidBody()
        sv2 = getRigidBody(position=[1, 2, 3])
        assert sv1 != sv2
Exemple #18
0
    def test_RigidBodyData_tuple(self):
        """
        Test the ``RigidBodyData`` class, most notably the __eq__ method.
        """
        # Compare two identical objects.
        sv1 = getRigidBody()
        sv2 = getRigidBody()
        assert sv1 == sv2

        # Compare two different objects.
        sv1 = getRigidBody()
        sv2 = getRigidBody(position=[1, 2, 3])
        assert sv1 != sv2
    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
Exemple #20
0
    def test_add_same(self):
        """
        Try to add two objects with the same ID.
        """
        # Instantiate a Leonard.
        leo = getLeonard()

        # Convenience.
        id_1 = '1'

        # The number of bodies in Leonard must be zero.
        assert len(leo.allBodies) == 0

        # Create three bodies.
        body_1 = getRigidBody(imass=1)
        body_2 = getRigidBody(imass=2)
        body_3 = getRigidBody(imass=3)

        # The command queue for spawning objects must be empty.
        ret = leoAPI.dequeueCommands()
        assert ret.ok and (ret.data['spawn'] == [])

        # Spawn the first object, then attempt to spawn another with the same
        # objID *before* Leonard gets around to add even the first one --> this
        # must fail and not add anything.
        assert leoAPI.addCmdSpawn([(id_1, body_1)]).ok
        assert not leoAPI.addCmdSpawn([(id_1, body_2)]).ok
        ret = leoAPI.dequeueCommands()
        spawn = ret.data['spawn']
        assert ret.ok and (len(spawn) == 1) and (spawn[0]['objID'] == id_1)

        # Similar test as before, but this time Leonard has already pulled id_1
        # into the simulation *before* we (attempt to) spawn another object
        # with the same ID. The 'addSpawnCmd' must succeed because it cannot
        # reliably verify if Leonard has an object id_1 (it can only verify if
        # another such request is in the queue already -- see above). However,
        # Leonard itself must ignore that request. To verify this claim we will
        # now spawn a new object with the same id_1 but a different state data,
        # let Leonard process the queue, and then verify that it did not
        # add/modify the object with id_1.
        assert leoAPI.addCmdSpawn([(id_1, body_1)]).ok
        leo.processCommandsAndSync()
        assert leo.allBodies[id_1] == body_1

        # Spawn anoter object with id_1 but different state data and verify
        # that Leonard did not modify the original body.
        assert leoAPI.addCmdSpawn([(id_1, body_3)]).ok
        leo.processCommandsAndSync()
        assert leo.allBodies[id_1] == body_1
Exemple #21
0
    def test_add_same(self):
        """
        Try to add two objects with the same ID.
        """
        # Instantiate a Leonard.
        leo = getLeonard()

        # Convenience.
        id_1 = 1

        # The number of bodies in Leonard must be zero.
        assert len(leo.allBodies) == 0

        # Create three bodies.
        body_1 = getRigidBody(imass=1)
        body_2 = getRigidBody(imass=2)
        body_3 = getRigidBody(imass=3)

        # The command queue for spawning objects must be empty.
        ret = leoAPI.dequeueCommands()
        assert ret.ok and (ret.data['spawn'] == [])

        # Spawn the first object, then attempt to spawn another with the same
        # objID *before* Leonard gets around to add even the first one --> this
        # must fail and not add anything.
        assert leoAPI.addCmdSpawn([(id_1, body_1)]).ok
        assert not leoAPI.addCmdSpawn([(id_1, body_2)]).ok
        ret = leoAPI.dequeueCommands()
        spawn = ret.data['spawn']
        assert ret.ok and (len(spawn) == 1) and (spawn[0]['objID'] == id_1)

        # Similar test as before, but this time Leonard has already pulled id_1
        # into the simulation *before* we (attempt to) spawn another object
        # with the same ID. The 'addSpawnCmd' must succeed because it cannot
        # reliably verify if Leonard has an object id_1 (it can only verify if
        # another such request is in the queue already -- see above). However,
        # Leonard itself must ignore that request. To verify this claim we will
        # now spawn a new object with the same id_1 but a different state data,
        # let Leonard process the queue, and then verify that it did not
        # add/modify the object with id_1.
        assert leoAPI.addCmdSpawn([(id_1, body_1)]).ok
        leo.processCommandsAndSync()
        assert leo.allBodies[id_1] == body_1

        # Spawn anoter object with id_1 but different state data and verify
        # that Leonard did not modify the original body.
        assert leoAPI.addCmdSpawn([(id_1, body_3)]).ok
        leo.processCommandsAndSync()
        assert leo.allBodies[id_1] == body_1
Exemple #22
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
Exemple #23
0
    def test_createWorkPackages(self):
        """
        Create a Work Package and verify its content.
        """
        # Get a Leonard instance.
        leo = getLeonard(azrael.leonard.LeonardDistributedZeroMQ)

        # Constants.
        id_1, id_2 = 1, 2
        dt, maxsteps = 2, 3

        # Invalid call: list of IDs must not be empty.
        assert not leo.createWorkPackage([], dt, maxsteps).ok

        # Invalid call: Leonard has no object with ID 10.
        assert not leo.createWorkPackage([10], dt, maxsteps).ok

        # Test data.
        body_1 = getRigidBody(imass=1)
        body_2 = getRigidBody(imass=2)

        # Add two new objects to Leonard.
        tmp = [(id_1, body_1), (id_2, body_2)]
        assert leoAPI.addCmdSpawn(tmp).ok
        leo.processCommandsAndSync()

        # Create a Work Package with two objects. The WPID must be 1.
        ret = leo.createWorkPackage([id_1], dt, maxsteps)
        ret_wpid, ret_wpdata = ret.data['wpid'], ret.data['wpdata']
        assert (ret.ok, ret_wpid, len(ret_wpdata)) == (True, 0, 1)

        # Create a second WP: it must have WPID=2 and contain two objects.
        ret = leo.createWorkPackage([id_1, id_2], dt, maxsteps)
        ret_wpid, ret_wpdata = ret.data['wpid'], ret.data['wpdata']
        assert (ret.ok, ret_wpid, len(ret_wpdata)) == (True, 1, 2)

        # Check the WP content.
        WPData = azrael.leonard.WPData
        WPMeta = azrael.leonard.WPMeta
        data = [WPData(*_) for _ in ret.data['wpdata']]
        meta = WPMeta(*ret.data['wpmeta'])
        assert (meta.dt, meta.maxsteps) == (dt, maxsteps)
        assert (ret.ok, len(data)) == (True, 2)
        assert (data[0].aid, data[1].aid) == (id_1, id_2)
        assert getRigidBody(*data[0].sv) == body_1
        assert getRigidBody(*data[1].sv) == body_2
        assert np.array_equal(data[0].force, [0, 0, 0])
        assert np.array_equal(data[1].force, [0, 0, 0])
    def test_apply_force_on_noncentred_body(self, forceFun):
        """
        Create two non-touching bodies. The centre of mass of the first
        coincides with its position. The center of mass for the second has an
        offset in y-direction.

        Then apply a force in z-direction at position of the rigid body and
        query them via the `bullet_api`. The first object must have moved
        *only* along the z-direction (we do not really care how much here). The
        second object must have moved only a bit in z *and* negative y
        direction (because it starts to rotate around the centre of mass which
        is at a negative y-position).
        """
        # Constants and parameters for this test.
        id_a, id_b = '10', '11'
        force = [0, 0, 1]
        pos_a, pos_b = [-5, 0, 0], [5, 0, 0]
        dt, maxsteps = 1.0, 60

        # Create two bodies. They do not touch and have different centres of
        # mass.
        obj_a = getRigidBody(position=pos_a, com=[0, 0, 0])
        obj_b = getRigidBody(position=pos_b, com=[0, -1, 0])

        # Instantiate a Bullet engine, load the objects, and apply the same
        # force to both with respect to the position in Azrael (not with
        # respect to their centre of mass!).
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)
        sim.applyForce(id_a, force, [0, 0, 0])
        sim.applyForce(id_b, force, [0, 0, 0])

        # Progress the simulation and query the objects.
        sim.compute([id_a, id_b], dt, maxsteps)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok

        # Verify that the centred body only moved in z-direction. Then verify
        # that the second object moved along the positive 'z' and negative 'y'
        # axis because it must have started to spin.
        rpos_a = ret_a.data.position
        rpos_b = ret_b.data.position
        assert (rpos_a[0] == pos_a[0]) and (rpos_a[1] == pos_a[1])
        assert rpos_a[2] > 0.1
        assert rpos_b[0] == pos_b[0]
        assert (rpos_b[1] < 0.1) and (rpos_b[2] > 0.1)
Exemple #25
0
    def test_apply_force_on_noncentred_body(self, forceFun):
        """
        Create two non-touching bodies. The centre of mass of the first
        coincides with its position. The center of mass for the second has an
        offset in y-direction.

        Then apply a force in z-direction at position of the rigid body and
        query them via the `bullet_api`. The first object must have moved
        *only* along the z-direction (we do not really care how much here). The
        second object must have moved only a bit in z *and* negative y
        direction (because it starts to rotate around the centre of mass which
        is at a negative y-position).
        """
        # Constants and parameters for this test.
        id_a, id_b = '10', '11'
        force = [0, 0, 1]
        pos_a, pos_b = [-5, 0, 0], [5, 0, 0]
        dt, maxsteps = 1.0, 60

        # Create two bodies. They do not touch and have different centres of
        # mass.
        obj_a = getRigidBody(position=pos_a, com=[0, 0, 0])
        obj_b = getRigidBody(position=pos_b, com=[0, -1, 0])

        # Instantiate a Bullet engine, load the objects, and apply the same
        # force to both with respect to the position in Azrael (not with
        # respect to their centre of mass!).
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setRigidBodyData(id_a, obj_a)
        sim.setRigidBodyData(id_b, obj_b)
        sim.applyForce(id_a, force, [0, 0, 0])
        sim.applyForce(id_b, force, [0, 0, 0])

        # Progress the simulation and query the objects.
        sim.compute([id_a, id_b], dt, maxsteps)
        ret_a = sim.getRigidBodyData(id_a)
        ret_b = sim.getRigidBodyData(id_b)
        assert ret_a.ok and ret_b.ok

        # Verify that the centred body only moved in z-direction. Then verify
        # that the second object moved along the positive 'z' and negative 'y'
        # axis because it must have started to spin.
        rpos_a = ret_a.data.position
        rpos_b = ret_b.data.position
        assert (rpos_a[0] == pos_a[0]) and (rpos_a[1] == pos_a[1])
        assert rpos_a[2] > 0.1
        assert rpos_b[0] == pos_b[0]
        assert (rpos_b[1] < 0.1) and (rpos_b[2] > 0.1)
Exemple #26
0
    def test_compute_invalid(self):
        """
        Call 'compute' method for non-existing object IDs.
        """
        # Constants and parameters for this test.
        objID = 10
        dt, maxsteps = 1.0, 60

        # Create an object and overwrite the CShape data to obtain a sphere.
        obj_a = getRigidBody()

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

        # Call 'compute' on non-existing object.
        assert not sim.compute([objID], dt, maxsteps).ok

        # 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)
        assert sim.compute([objID], dt, maxsteps).ok
        ret = sim.getRigidBodyData(objID)
        assert (ret.ok, ret.data) == (True, obj_a)

        # Call 'compute' again with one (in)valid object.
        assert not sim.compute([objID, 100], dt, maxsteps).ok
        assert not sim.compute([100, objID], dt, maxsteps).ok
Exemple #27
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
Exemple #28
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
Exemple #29
0
    def test_Template(self):
        # Define boosters and factories.
        boosters = {
            '0': Booster(position=(0, 1, 2), direction=(1, 0, 0), force=0),
            '1': Booster(position=(3, 4, 5), direction=(0, 1, 0), force=0)
        }
        factories = {
            '0': Factory(position=(0, 1, 2), direction=(0, 0, 1),
                         templateID='_templateBox', exit_speed=(0, 1)),
            '1': Factory(position=(3, 4, 5), direction=(0, 1, 0),
                         templateID='_templateBox', exit_speed=(0, 1))
        }

        rbs = getRigidBody(position=(1, 2, 3))

        # Define a new template with two boosters and add it to Azrael.
        frags = {'1': getFragRaw(), '2': getFragDae()}
        temp_t = getTemplate('t1',
                             rbs=rbs,
                             fragments=frags,
                             boosters=boosters,
                             factories=factories)

        # Verify that Template._asdict() method calls the _asdict() methods
        # for all collision shapes, fragments, boosters, and factories.
        temp_d = temp_t._asdict()
        fragments_d = {k: v._asdict() for (k, v) in temp_t.fragments.items()}
        boosters_d = {k: v._asdict() for (k, v) in temp_t.boosters.items()}
        factories_d = {k: v._asdict() for (k, v)in temp_t.factories.items()}
        rbs_d = rbs._asdict()
        assert temp_d['fragments'] == fragments_d
        assert temp_d['boosters'] == boosters_d
        assert temp_d['factories'] == factories_d
        assert temp_d['rbs'] == rbs_d
    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)
Exemple #31
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
Exemple #32
0
    def test_move_single_object(self, clsLeonard):
        """
        Create a single object with non-zero initial speed and ensure
        Leonard moves it accordingly.
        """
        # Get a Leonard instance.
        leo = getLeonard(clsLeonard)

        # Constants and parameters for this test.
        id_0 = 0

        # Spawn an object.
        assert leoAPI.addCmdSpawn([(id_0, getRigidBody())]).ok

        # Advance the simulation by 1s and verify that nothing has moved.
        leo.step(1.0, 60)
        assert np.array_equal(leo.allBodies[id_0].position, [0, 0, 0])

        # Give the object a velocity.
        body = {'velocityLin': np.array([1, 0, 0])}
        assert leoAPI.addCmdModifyBodyState(id_0, body).ok
        del body

        # Advance the simulation by another second and verify the objects have
        # moved accordingly.
        leo.step(1.0, 60)
        body = leo.allBodies[id_0]
        assert 0.9 <= body.position[0] < 1.1
        assert body.position[1] == body.position[2] == 0
    def getTestTemplate(self, templateID='templateID'):
        """
        Return a valid template with non-trivial data. The template contains
        multiple fragments (Raw and Collada), boosters, factories, and a rigid
        body.

        This is a convenience method only.
        """
        # 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 IDs).
        boosters = {
            '0': aztypes.Booster(position=(0, 1, 2), direction=(0, 0, 1), force=0),
            '1': aztypes.Booster(position=(6, 7, 8), direction=(0, 1, 0), force=0),
        }
        factories = {
            '0': aztypes.Factory(position=(0, 0, 0), direction=(0, 0, 1),
                                 templateID='_templateBox',
                                 exit_speed=(0.1, 0.5))
        }

        # Create some fragments...
        frags = {'f1': getFragRaw(), 'f2': getFragDae()}

        # ... and a body...
        body = getRigidBody(position=(1, 2, 3))

        # ... then compile and return the template.
        return azrael.test.test.getTemplate(
            templateID,
            rbs=body,
            fragments=frags,
            boosters=boosters,
            factories=factories)
Exemple #34
0
        def testCCS(pos, AABBs, expected_objIDs):
            """
            Compute broadphase results for bodies  at ``pos`` with ``AABBs``
            and verify that the ``expected_objIDs`` sets were produced.

            This function assumes that every body has exactly one AABB and with
            no relative offset to the body's position.
            """
            # Compile the set of bodies- and their AABBs for this test run.
            assert len(pos) == len(aabbs)
            bodies = [getRigidBody(position=_) for _ in pos]

            # By assumption for this function, every object has exactly one AABB
            # centered at position zero relative to their rigid body.
            AABBs = [{'1': (0, 0, 0, _[0], _[1], _[2])} for _ in AABBs]

            # Convert to dictionaries: the key is the bodyID in Azrael; here it
            # is a simple enumeration.
            bodies = {idx: val for (idx, val) in enumerate(bodies)}
            AABBs = {idx: val for (idx, val) in enumerate(AABBs)}

            # 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(tuple(_)) for _ in expected_objIDs]
            computed_objIDs = [sorted(tuple(_)) for _ in ret.data]

            # Return the equality of the two list of lists.
            assert sorted(expected_objIDs) == sorted(computed_objIDs)
            del bodies, AABBs, ret, expected_objIDs, computed_objIDs
Exemple #35
0
    def test_totalForceAndTorque_with_rotation(self):
        """
        Similar to the previou 'test_totalForceAndTorque_no_rotation'
        but this time the object does not have a neutral rotation in
        world coordinates. This must have no effect on the direct force
        values, but the booster forces must be re-oriented accordingly.
        """
        # Get a Leonard instance.
        leo = getLeonard(azrael.leonard.LeonardDistributedZeroMQ)

        # Spawn one object rotated 180 degress around x-axis.
        sv = getRigidBody(imass=1, rotation=(1, 0, 0, 0))
        objID = 1
        assert leoAPI.addCmdSpawn([(objID, sv)]).ok
        leo.processCommandsAndSync()
        del sv

        # Initial force and torque must be zero.
        assert leo.totalForceAndTorque(objID) == ([0, 0, 0], [0, 0, 0])

        # Add booster force in z-direction.
        assert leoAPI.addCmdBoosterForce(objID, [1, 2, 3], [-1, -2, -3]).ok
        leo.processCommandsAndSync()

        # The net forces in must have their signs flipped in the y/z
        # directions, and remain unchanged for x since the object itself is
        # rotated 180 degrees around the x-axis.
        assert leo.totalForceAndTorque(objID) == ([1, -2, -3], [-1, 2, 3])

        # The object's rotation must not effect the direct force and torque.
        assert leoAPI.addCmdBoosterForce(objID, [0, 0, 0], [0, 0, 0]).ok
        assert leoAPI.addCmdDirectForce(objID, [1, 2, 3], [4, 5, 6]).ok
        leo.processCommandsAndSync()
        assert leo.totalForceAndTorque(objID) == ([1, 2, 3], [4, 5, 6])
    def test_compute_invalid(self):
        """
        Call 'compute' method for non-existing object IDs.
        """
        # Constants and parameters for this test.
        objID = '10'
        dt, maxsteps = 1.0, 60

        # Create an object and overwrite the CShape data to obtain a sphere.
        obj_a = getRigidBody()

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

        # Call 'compute' on non-existing object.
        assert not sim.compute([objID], dt, maxsteps).ok

        # 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)
        assert sim.compute([objID], dt, maxsteps).ok
        ret = sim.getRigidBodyData(objID)
        assert ret.ok and self.isBulletRbsEqual(ret.data, obj_a)

        # Call 'compute' again with one (in)valid object.
        assert not sim.compute([objID, 100], dt, maxsteps).ok
        assert not sim.compute([100, objID], dt, maxsteps).ok
Exemple #37
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)
Exemple #38
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
Exemple #39
0
        def _verify(rba, pos, rot, scale, intersect: bool):
            """
            Assert that body ``rba`` and a new body (specified by ``pos``,
            ``rot``, and ``scale``) ``intersect``.

            This is a convenience function to facilitate more readable tests.
            """
            # Hard code collision shape offset for second object.
            cs_ofs = (1, 0, 0)

            # Create the second body. Its collision shape is a unit cube
            # at position `cs_ofs`.
            body_b = getRigidBody(position=pos, scale=scale, rotation=rot,
                                  cshapes={'csbox': getCSBox()})

            # Compile the input dictionaries for the broadphase algorithm.
            bodies = {1: rba, 2: body_b}
            aabbs = {1: {'1': [0, 0, 0, 1, 1, 1]},
                     2: {'1': [cs_ofs[0], cs_ofs[1], cs_ofs[2], 1, 1, 1]}}

            # Compute the broadphase collision sets.
            ret = azrael.leonard.computeCollisionSetsAABB(bodies, aabbs)
            assert ret.ok
            coll_sets = ret.data

            # If the bodies intersect there must be exactly one collision set
            # with two entries, otherwise it is the other way around.
            if intersect:
                assert len(coll_sets) == 1
                assert len(coll_sets[0]) == 2
            else:
                assert len(coll_sets) == 2
                assert len(coll_sets[0]) == len(coll_sets[1]) == 1
Exemple #40
0
    def test_setRigidBody_basic(self, clsLeonard):
        """
        Spawn an object, specify its State Variables explicitly, and verify the
        change propagated through Azrael.
        """
        # Get a Leonard instance.
        leo = getLeonard(clsLeonard)

        # Parameters and constants for this test.
        id_1 = 1

        # Body data.
        p = np.array([1, 2, 5])
        vl = np.array([8, 9, 10.5])
        vr = vl + 1
        body = {'position': p, 'velocityLin': vl, 'velocityRot': vr}
        del p, vl, vr

        # Spawn a new object. It must have ID=1.
        assert leoAPI.addCmdSpawn([(id_1, getRigidBody())]).ok

        # Update the object's body.
        assert leoAPI.addCmdModifyBodyState(id_1, body).ok

        # Sync the commands to Leonard.
        leo.processCommandsAndSync()

        # Verify that the attributes were correctly updated.
        ret = leo.allBodies[id_1]
        assert np.array_equal(ret.position, body['position'])
        assert np.array_equal(ret.velocityLin, body['velocityLin'])
        assert np.array_equal(ret.velocityRot, body['velocityRot'])
Exemple #41
0
    def test_Template(self):
        # Define boosters and factories.
        boosters = {
            "0": Booster(pos=(0, 1, 2), direction=(1, 0, 0), minval=0, maxval=1, force=0),
            "1": Booster(pos=(3, 4, 5), direction=(0, 1, 0), minval=0, maxval=2, force=0),
        }
        factories = {
            "0": Factory(pos=(0, 1, 2), direction=(0, 0, 1), templateID="_templateBox", exit_speed=(0, 1)),
            "1": Factory(pos=(3, 4, 5), direction=(0, 1, 0), templateID="_templateBox", exit_speed=(0, 1)),
        }

        rbs = getRigidBody(position=(1, 2, 3))

        # Define a new template with two boosters and add it to Azrael.
        temp_t = getTemplate(
            "t1", rbs=rbs, fragments={"1": getFragRaw(), "2": getFragDae()}, boosters=boosters, factories=factories
        )

        # Verify that it is JSON compatible.
        assert self.isJsonCompatible(temp_t, Template)

        # Verify that Template._asdict() method calls the _asdict() methods
        # for all collision shapes, fragments, boosters, and factories.
        temp_d = temp_t._asdict()
        fragments_d = {k: v._asdict() for (k, v) in temp_t.fragments.items()}
        boosters_d = {k: v._asdict() for (k, v) in temp_t.boosters.items()}
        factories_d = {k: v._asdict() for (k, v) in temp_t.factories.items()}
        rbs_d = rbs._asdict()
        assert temp_d["fragments"] == fragments_d
        assert temp_d["boosters"] == boosters_d
        assert temp_d["factories"] == factories_d
        assert temp_d["rbs"] == rbs_d
    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]
    def test_box_on_plane(self):
        """
        Create a simulation with gravity. Place a box above a plane and verify
        that after a long time the box will have come to rest on the infinitely
        large plane.
        """
        aid_1, aid_2 = '1', '2'

        # Instantiate Bullet engine and activate gravity.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setGravity((0, 0, -10))

        # Create a box above a static plane. The ground plane is at z=-1.
        cs_plane = getCSPlane(normal=(0, 0, 1), ofs=-1)
        cs_box = getCSBox()
        b_plane = getRigidBody(imass=0, cshapes={'csplane': cs_plane})
        b_box = getRigidBody(position=(0, 0, 5), cshapes={'csbox': cs_box})
        assert b_box is not None
        assert b_plane is not None

        # Add the objects to the simulation and verify their positions.
        sim.setRigidBodyData(aid_1, b_plane)
        sim.setRigidBodyData(aid_2, b_box)
        ret_plane = sim.getRigidBodyData(aid_1)
        ret_box = sim.getRigidBodyData(aid_2)
        assert (ret_plane.ok is True) and (ret_box.ok is True)
        assert ret_plane.data.position[2] == 0
        assert ret_box.data.position[2] == 5

        # Step the simulation often enough for the box to fall down and come to
        # rest on the surface.
        dt, maxsteps = 1.0, 60
        for ii in range(10):
            sim.compute([aid_1, aid_2], dt, maxsteps)

        # Verify that the plane has not moved (because it is static) and that
        # the box has come to rest atop. The position of the box rigid body
        # must be approximately zero, because the plane is at position z=-1,
        # and the half length of the box is 1 Meters.
        ret_plane = sim.getRigidBodyData(aid_1)
        ret_box = sim.getRigidBodyData(aid_2)
        assert (ret_plane.ok is True) and (ret_box.ok is True)
        assert ret_plane.data.position[2] == 0
        assert abs(ret_box.data.position[2]) < 1E-5
Exemple #44
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)
    def test_apply_torque_diagonal(self):
        """
        Create a body with neutral principal axis (ie aligned with the world
        coordinate system). Then apply a torque vector. The object must spin at
        the respective velocities in each direction.
        """
        # Constants and parameters for this test.
        objID = '10'
        inertia = [1, 2, 3]
        torque = np.array([2, 3, 1])

        # Bullet simulation step. This *must* happen in a *single* step to
        # produce the anticipated outcome. The reason is subtle: Bullet always
        # applies the torque in world coordinates yet the body progressively
        # rotate in each sub-step. Unless the moments of inertia are all equal
        # this means the induced rotation will change its axis a bit at each
        # step.
        dt, maxsteps = 1.0, 1

        # Create an object and overwrite the CShape data to obtain a sphere.
        obj_a = getRigidBody(inertia=inertia)

        # 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)

        # Apply the torque.
        sim.applyForceAndTorque(objID, [0, 0, 0], 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 by another 'dt' seconds.
        sim.compute([objID], dt, maxsteps)
        ret = sim.getRigidBodyData(objID)
        assert ret.ok

        # The object must have accelerated to the angular velocity
        #   v = a * t                  (1)
        # The (angular) acceleration $a$ follows from
        #   T = I * a --> a = T / I    (2)
        # based on (T)orque and (I)nertia. In this test, the torque and
        # principal axis coincide and T and I are thus diagonal (the "division"
        # of matrices above is thus justified in this special case). Now
        # substitute (2) into (1) to obtain the angular velocity v as
        #   v = t * T / I
        assert np.allclose(ret.data.vRot, dt * torque / inertia, atol=1E-1)
Exemple #46
0
    def test_get_set_forceandtorque(self):
        """
        Query and update the force- and torque vectors for an object.
        """
        # Reset the SV database and instantiate a Leonard.
        leo = getLeonard()

        # Create two object IDs for this test.
        id_1, id_2 = '0', '1'

        # Create two objects and serialise them.
        body_1 = getRigidBody(position=[0, 0, 0])
        body_2 = getRigidBody(position=[10, 10, 10])

        # Add the two objects to the simulation.
        tmp = [(id_1, body_1), (id_2, body_2)]
        assert leoAPI.addCmdSpawn(tmp).ok
        leo.processCommandsAndSync()

        # Update the direct force and torque of the second object only.
        force, torque = [1, 2, 3], [4, 5, 6]
        assert leoAPI.addCmdDirectForce(id_2, force, torque)
        leo.processCommandsAndSync()

        # Only the force an torque of the second object must have changed.
        assert np.array_equal(leo.allForces[id_1].forceDirect, [0, 0, 0])
        assert np.array_equal(leo.allForces[id_1].torqueDirect, [0, 0, 0])
        assert np.array_equal(leo.allForces[id_2].forceDirect, force)
        assert np.array_equal(leo.allForces[id_2].torqueDirect, torque)

        # Update the booster force and torque of the first object only.
        force, torque = [1, 2, 3], [4, 5, 6]
        assert leoAPI.addCmdBoosterForce(id_2, force, torque)
        leo.processCommandsAndSync()

        # Only the booster- force an torque of the second object must have
        # changed.
        assert np.array_equal(leo.allForces[id_1].forceDirect, [0, 0, 0])
        assert np.array_equal(leo.allForces[id_1].torqueDirect, [0, 0, 0])
        assert np.array_equal(leo.allForces[id_2].forceDirect, force)
        assert np.array_equal(leo.allForces[id_2].torqueDirect, torque)
    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)
    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
Exemple #49
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
    def test_apply_force(self, forceFun):
        """
        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'
        imass = 2
        force = np.array([1, 2, 3], np.float64)
        dt, maxsteps = 1.0, 60

        # Create an object and overwrite the CShape data to obtain a sphere.
        obj_a = getRigidBody(imass=imass)

        # 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.
        if forceFun == 'applyForce':
            applyForceFun = sim.applyForce
        elif forceFun == 'applyForceAndTorque':
            applyForceFun = sim.applyForceAndTorque
        else:
            assert False
        applyForceFun(objID, force, [0, 0, 0])

        # 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 by another 'dt' seconds.
        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)
        # Substitute (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 * imass, atol=1E-1)
Exemple #51
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)
Exemple #52
0
    def test_Template(self):
        # Define boosters and factories.
        boosters = {
            '0': Booster(position=(0, 1, 2), direction=(1, 0, 0), force=0),
            '1': Booster(position=(3, 4, 5), direction=(0, 1, 0), force=0)
        }
        factories = {
            '0':
            Factory(position=(0, 1, 2),
                    direction=(0, 0, 1),
                    templateID='_templateBox',
                    exit_speed=(0, 1)),
            '1':
            Factory(position=(3, 4, 5),
                    direction=(0, 1, 0),
                    templateID='_templateBox',
                    exit_speed=(0, 1))
        }

        rbs = getRigidBody(position=(1, 2, 3))

        # Define a new template with two boosters and add it to Azrael.
        frags = {'1': getFragRaw(), '2': getFragDae()}
        temp_t = getTemplate('t1',
                             rbs=rbs,
                             fragments=frags,
                             boosters=boosters,
                             factories=factories)

        # Verify that Template._asdict() method calls the _asdict() methods
        # for all collision shapes, fragments, boosters, and factories.
        temp_d = temp_t._asdict()
        fragments_d = {k: v._asdict() for (k, v) in temp_t.fragments.items()}
        boosters_d = {k: v._asdict() for (k, v) in temp_t.boosters.items()}
        factories_d = {k: v._asdict() for (k, v) in temp_t.factories.items()}
        rbs_d = rbs._asdict()
        assert temp_d['fragments'] == fragments_d
        assert temp_d['boosters'] == boosters_d
        assert temp_d['factories'] == factories_d
        assert temp_d['rbs'] == rbs_d
    def test_apply_angular_velocity_to_noncentred_body(self, forceFun):
        """
        Create a body at position (0, 0, 0). Its centre of mass is at (1, 0, 0). The
        apply an angular velocity of pi rad/s along the z-axis and step the
        simulation for one second. This must have rotated the body by 180
        degrees, which means the body's position must have gone from (0, 0, 0)
        to (2, 0, 0).
        """
        # Constants and parameters for this test.
        id_a = '10'

        # Create a rigid body that spins with pi rad/s around the z-axis.
        pi = float(np.pi)
        pos = (1, 2, 3)
        com = (4, 5, 0)
        obj_a = getRigidBody(position=pos, com=com, velocityRot=(0, 0, pi))

        # Instantiate a Bullet engine and load the object.
        sim = azrael.bullet_api.PyBulletDynamicsWorld(1)
        sim.setRigidBodyData(id_a, obj_a)

        # Query the object back immediately and verify that it is intact
        # (despite the centre of mass not being zero in this test).
        ret_a = sim.getRigidBodyData(id_a)
        assert self.isBulletRbsEqual(ret_a.data, obj_a)

        # Progress the simulation by one second. Use many small steps to ensure
        # the numerical errors stay small and the object does indeed rotate
        # ~180 degrees around the z-axis (recall that we specified an angular
        # velocity of ~Pi rad/s).
        sim.compute([id_a], 1, 60)

        # Query the object back.
        ret_a = sim.getRigidBodyData(id_a)
        assert ret_a.ok

        # Extract the position. The object position must have rotated 180
        # degrees around the centre of mass (around the z-axis).
        assert np.allclose(2 * np.array(com) + pos, ret_a.data.position, 0.1)
    def test_remove_object(self):
        """
        Remove an object from the Bullet cache.
        """
        aid = '0'

        # Create a spherical object.
        obj_a = getRigidBody()

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

        # Request an invalid object ID.
        ret = sim.getRigidBodyData(aid)
        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)

        # Delete the object. The attempt to request it afterwards must fail.
        assert sim.removeRigidBody([aid]).ok
        assert not sim.getRigidBodyData(aid).ok
    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)
Exemple #56
0
    def test_RigidBodyData(self):
        body_a = getRigidBody()
        body_b = getRigidBody()
        assert body_a == body_b

        assert self.isJsonCompatible(body_a, RigidBodyData)
Exemple #57
0
    def test_commandQueue(self):
        """
        Add-, query, and remove commands from the command queue.
        """
        # Convenience.
        body_1 = getRigidBody()
        body_2 = {'imass': 2, 'scale': 3}
        id_1, id_2 = '0', '1'

        # The command queue must be empty for every category.
        ret = leoAPI.dequeueCommands()
        assert ret.ok
        assert ret.data['spawn'] == []
        assert ret.data['remove'] == []
        assert ret.data['modify'] == []
        assert ret.data['direct_force'] == []
        assert ret.data['booster_force'] == []

        # Spawn two objects with id_1 and id_2.
        tmp = [(id_1, body_1), (id_2, body_1)]
        assert leoAPI.addCmdSpawn(tmp).ok

        # Verify that the spawn commands were added.
        ret = leoAPI.dequeueCommands()
        assert ret.ok
        spawn = ret.data['spawn']
        assert {spawn[0]['objID'], spawn[1]['objID']} == {id_1, id_2}
        assert ret.data['remove'] == []
        assert ret.data['modify'] == []
        assert ret.data['direct_force'] == []
        assert ret.data['booster_force'] == []

        # De-queuing the commands once more must not return any results because
        # they have already been removed.
        ret = leoAPI.dequeueCommands()
        assert ret.ok
        assert ret.data['spawn'] == []
        assert ret.data['remove'] == []
        assert ret.data['modify'] == []
        assert ret.data['direct_force'] == []
        assert ret.data['booster_force'] == []

        # Modify state variable for body with id_1.
        newSV = {'imass': 10, 'position': [3, 4, 5]}
        assert leoAPI.addCmdModifyBodyState(id_1, newSV).ok
        ret = leoAPI.dequeueCommands()
        modify = ret.data['modify']
        assert ret.ok and len(modify) == 1
        assert modify[0]['objID'] == id_1
        assert modify[0]['rbs'] == newSV
        del newSV

        # Set the direct force and torque for id_2.
        force, torque = [1, 2, 3], [4, 5, 6]
        assert leoAPI.addCmdDirectForce(id_2, force, torque).ok
        ret = leoAPI.dequeueCommands()
        fat = ret.data['direct_force']
        assert ret.ok
        assert len(fat) == 1
        assert fat[0]['objID'] == id_2
        assert fat[0]['force'] == force
        assert fat[0]['torque'] == torque

        # Set the booster force and torque for id_1.
        force, torque = [1, 2, 3], [4, 5, 6]
        assert leoAPI.addCmdBoosterForce(id_1, force, torque).ok
        ret = leoAPI.dequeueCommands()
        fat = ret.data['booster_force']
        assert ret.ok
        assert len(fat) == 1
        assert fat[0]['objID'] == id_1
        assert fat[0]['force'] == force
        assert fat[0]['torque'] == torque

        # Remove an object.
        assert leoAPI.addCmdRemoveObject(id_1).ok
        ret = leoAPI.dequeueCommands()
        assert ret.ok and ret.data['remove'][0]['objID'] == id_1

        # Add commands for two objects (it is perfectly ok to add commands for
        # non-existing body IDs since this is just a command queue - Leonard
        # will skip commands for non-existing IDs automatically).
        force, torque = [7, 8, 9], [10, 11.5, 12.5]
        for objID in (id_1, id_2):
            assert leoAPI.addCmdSpawn([(objID, body_1)]).ok
            assert leoAPI.addCmdModifyBodyState(objID, body_2).ok
            assert leoAPI.addCmdRemoveObject(objID).ok
            assert leoAPI.addCmdDirectForce(objID, force, torque).ok
            assert leoAPI.addCmdBoosterForce(objID, force, torque).ok

        # De-queue all commands.
        ret = leoAPI.dequeueCommands()
        assert ret.ok
        assert len(ret.data['spawn']) == 2
        assert len(ret.data['remove']) == 2
        assert len(ret.data['modify']) == 2
        assert len(ret.data['direct_force']) == 2
        assert len(ret.data['booster_force']) == 2
Exemple #58
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