Пример #1
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
Пример #2
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
Пример #3
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
Пример #4
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
Пример #5
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
Пример #6
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
Пример #7
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
Пример #8
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)
Пример #9
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
Пример #10
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
Пример #11
0
    def setup_method(self, method):
        # Reset the database.
        azrael.database.init()

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

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

        frag = {'NoName': getFragRaw()}
        rbs_empty = getRigidBody(cshapes={'csempty': getCSEmpty()})
        rbs_sphere = getRigidBody(cshapes={'cssphere': getCSSphere()})
        rbs_box = getRigidBody(cshapes={'csbox': getCSBox()})
        t1 = getTemplate('_templateEmpty', rbs=rbs_empty, fragments=frag)
        t2 = getTemplate('_templateSphere', rbs=rbs_sphere, fragments=frag)
        t3 = getTemplate('_templateBox', rbs=rbs_box, fragments=frag)
        ret = clerk.addTemplates([t1, t2, t3])
        assert ret.ok
Пример #12
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)
Пример #13
0
    def test_needNewCollisionShapes(self):
        """
        Verify the conditions under which 'bullet_api' must throw out its
        current compound shape and compile a new one from scratch.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # New inertia (but same principal axis): do not rebuild.
        obj_new = getRigidBody(cshapes=csdefault, inertia=[1, 2, 3])
        assert sim.needNewCollisionShape(id_a, obj_new) is False
Пример #15
0
    def test_computeCollisionSetsAABB_rotate_scale(self):
        """
        Test broadphase when body has a different scale and/or is rotated.

        Create two bodies with one AABB each. The AABB of the first body is
        a centered unit cube used for testing. The second body has an AABB with
        an offset. Use different scales and rotations to verify it is
        correctly taken into account during the broadphase.
        """
        # Create the test body at the center. It is a centered unit cube.
        body_a = getRigidBody(position=(0, 0, 0), cshapes={'csbox': getCSBox()})

        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

        # Test object intersects (just) to the right of probe.
        pos = (0.99, 0, 0)
        rot = (0, 0, 0, 1)
        scale = 1
        _verify(body_a, pos, rot, scale, intersect=True)

        # Test object does (just) not intersect with probe.
        pos = (1.01, 0, 0)
        rot = (0, 0, 0, 1)
        scale = 1
        _verify(body_a, pos, rot, scale, intersect=False)

        # Dummy is rotated 180 degrees around y axis. This causes the AABBs to
        # intersect again.
        pos = (2, 0, 0)
        rot = (0, 0, 0, 1)
        scale = 1
        _verify(body_a, pos, rot=(0, 0, 0, 1), scale=1, intersect=False)
        _verify(body_a, pos, rot=(0, 1, 0, 0), scale=1, intersect=True)

        # Place the dummy out of reach from the probe. However, double the
        # size of the probe wich makes the objects overlap.
        pos = (-4, 0, 0)
        rot = (0, 0, 0, 1)
        body_a_scaled = body_a._replace(scale=3)
        _verify(body_a, pos, rot, scale=1, intersect=False)
        _verify(body_a_scaled, pos, rot, scale=1, intersect=True)
Пример #16
0
    def test_create_fetch_template(self, client_type):
        """
        Add a new object to the templateID DB and query it again.
        """
        # Get the client for this test.
        client = self.clients[client_type]

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Fetch the geometry from the Web server and verify it is correct.
        ret = client.getTemplateGeometry(ret.data[temp.aid])
        assert ret.ok
        assert ret.data['bar'] == frag['bar'].fragdata
Пример #17
0
    def test_compute_AABB(self):
        """
        Create some collision shapes and verify that 'computeAABBs' returns the
        correct results.
        """
        # Convenience.
        computeAABBs = azrael.leo_api.computeAABBs

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

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

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

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

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

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

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

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

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

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

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

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

        # Pass in invalid arguments. This must return with an error.
        assert not computeAABBs({'x': (1, 2)}).ok
Пример #18
0
    def test_compute_AABB(self):
        """
        Create some collision shapes and verify that 'computeAABBs' returns the
        correct results.
        """
        # Convenience.
        computeAABBs = azrael.leo_api.computeAABBs

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

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

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

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

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

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

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

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

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

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

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

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

        # Pass in invalid arguments. This must return with an error.
        assert not computeAABBs({'x': (1, 2)}).ok