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
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)
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]}
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)
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
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
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
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