def BoostercubeTemplate(scale=1.0): """ Return template for BoosterCube. """ # Get a Client instance. client = pyazrael.AzraelClient() # Load the model. vert, uv, rgb = demolib.loadBoosterCubeBlender() frag_cube = {'vert': vert, 'uv': uv, 'rgb': rgb, 'scale': scale, 'pos': (0, 0, 0), 'rot': (0, 0, 0, 1)} del vert, uv, rgb # Attach six boosters, two for every axis. dir_x = np.array([1, 0, 0]) dir_y = np.array([0, 1, 0]) dir_z = np.array([0, 0, 1]) pos = (0, 0, 0) B = aztypes.Booster boosters = { 'b_x': B(pos, direction=(1, 0, 0), force=0), 'b_y': B(pos, direction=(0, 1, 0), force=0), 'b_z': B(pos, direction=(0, 0, 1), force=0) } del dir_x, dir_y, dir_z, pos, B # Load sphere and colour it blue(ish). This is going to be the (super # simple) "flame" that comes out of the (still invisible) boosters. p = os.path.dirname(os.path.abspath(__file__)) fname = os.path.join(p, 'models', 'sphere', 'sphere.obj') vert, uv, rgb = demolib.loadModel(fname) rgb = np.tile([0, 0, 0.8], len(vert) // 3) rgb += 0.2 * np.random.rand(len(rgb)) rgb = np.array(255 * rgb.clip(0, 1), np.uint8) frag_flame = {'vert': vert, 'uv': [], 'rgb': rgb, 'pos': (0, 0, 0), 'rot': (0, 0, 0, 1)} del p, fname, vert, uv, rgb # Add the template to Azrael. tID = 'spaceship' cs = aztypes.CollShapeBox(scale, scale, scale) cs = aztypes.CollShapeMeta('box', (0, 0, 0), (0, 0, 0, 1), cs) body = demolib.getRigidBody(cshapes={'0': cs}) frags = { 'frag_1': demolib.getFragMetaRaw(**frag_cube), 'b_x': demolib.getFragMetaRaw(**frag_flame), 'b_y': demolib.getFragMetaRaw(**frag_flame), 'b_z': demolib.getFragMetaRaw(**frag_flame), } template = Template(tID, body, frags, boosters, {}) return template
def getSmallAsteroidTemplate(hlen): """ Return the template for a small asteroid. A small asteroid is merely a cube with half length ``hlen``. """ vert, cshapes = demolib.cubeGeometry(hlen, hlen, hlen) # Define the geometry of the Asteroids. fm = demolib.getFragMetaRaw( vert=vert, uv=[], rgb=[], scale=1, pos=(0, 0, 0), rot=(0, 0, 0, 1) ) frags = {'frag_1': fm} # Define the physics parameters for the Asteroids. body = demolib.getRigidBody( imass=80, inertia=[1, 1, 1], cshapes={'cs': cshapes}, ) return Template('Asteroid_small', body, frags, {}, {})
def _buildTemplate(name, imass, hlen, lf, rf): """ Return a complete template for a cube object. This is a helper function only. """ # Geometry and collision shape for platform. vert, cshapes = demolib.cubeGeometry(hlen, hlen, hlen) # Rigid body data for platforms (defines their physics, not appearance). body = demolib.getRigidBody( cshapes={'cs': cshapes}, linFactor=lf, rotFactor=rf, imass=imass, ) # Geometry for the platforms (defines their appearance, not physics). fm = demolib.getFragMetaRaw( vert=vert, uv=[], rgb=[], scale=1, pos=(0, 0, 0), rot=(0, 0, 0, 1) ) frags = {'frag_1': fm} # Define the platform template and upload it to Azrael. return Template(name, body, frags, {}, {})
def _buildTemplate(name, imass, hlen, lf, rf): """ Return a complete template for a cube object. This is a helper function only. """ # Geometry and collision shape for platform. vert, cshapes = demolib.cubeGeometry(hlen, hlen, hlen) # Rigid body data for platforms (defines their physics, not appearance). body = demolib.getRigidBody( cshapes={'cs': cshapes}, linFactor=lf, rotFactor=rf, imass=imass, ) # Geometry for the platforms (defines their appearance, not physics). fm = demolib.getFragMetaRaw(vert=vert, uv=[], rgb=[], scale=1, pos=(0, 0, 0), rot=(0, 0, 0, 1)) frags = {'frag_1': fm} # Define the platform template and upload it to Azrael. return Template(name, body, frags, {}, {})
def addLineOfCubes(client, objName: str, numCubes: int): """ Spawn a single body with ``numCubes`` fragments. The body will also be tagged with `objName`. """ # Get the mesh and collision shape for a single cube. vert, csmetabox = demolib.cubeGeometry(0.5, 0.5, 0.5) # Create a fragment template (convenience only for the loop below). ref_frag = demolib.getFragMetaRaw(vert=vert, uv=[], rgb=[]) # Create the fragments and line them up in a line along the x-axis. frags = {} centre = (numCubes - 1) / 2 for idx in range(numCubes): frag_name = str(idx) frag_pos = [(idx - centre) * 2, 0, 0] frags[frag_name] = ref_frag._replace(position=frag_pos) # Rigid body description (defines its physics, not its appearance). body = demolib.getRigidBody(cshapes={'cs': csmetabox}) # Define the object template (physics and appearance) and upload to Azrael. template = Template('PlotObject', body, frags, {}, {}) ret = client.addTemplates([template]) # Spawn one instance. templates = [{'templateID': 'PlotObject', 'rbs': {'position': [0, 0, 0]}}] ret = client.spawn(templates) assert ret.ok objID = ret.data[0] # Tag the object. assert client.setObjectTags({objID: objName})
def getLargeAsteroidTemplate(hlen): """ Return the template for a large asteroid. A large asteroids consists of multiple 6 patched together cubes. Each cube has a half length of ``hlen``. """ def _randomise(scale, pos): pos = scale * np.random.uniform(-1, 1, 3) + pos return pos.tolist() # Geometry and collision shape for a single cube. vert, csmetabox = demolib.cubeGeometry(hlen, hlen, hlen) # Define the positions of the individual cubes/fragments in that will # constitute the Asteroid. The positions are slightly randomised to make the # Asteroids somewhat irregular. ofs = 1.5 * hlen noise = 0.6 frag_src = { 'up': _randomise(noise, [0, ofs, 0]), 'down': _randomise(noise, [0, -ofs, 0]), 'left': _randomise(noise, [ofs, 0, 0]), 'right': _randomise(noise, [-ofs, 0, 0]), 'front': _randomise(noise, [0, 0, ofs]), 'back': _randomise(noise, [0, 0, -ofs]), } del ofs # Patch together the fragments that will constitute the geometry of a # single large Asteroid. While at it, also create the individual collision # shapes for each fragment (not part of the geometry but needed # afterwards). frags, cshapes = {}, {} for name, pos in frag_src.items(): frags[name] = demolib.getFragMetaRaw( vert=vert, uv=[], rgb=[], scale=1, pos=pos, rot=[0, 0, 0, 1], ) # Collision shape for this fragment. cshapes[name] = csmetabox._replace(position=pos) del name, pos del frag_src # Specify the physics of the overall bodies. body = demolib.getRigidBody( imass=0.01, inertia=[3, 3, 3], cshapes=cshapes, ) # Return the completed Template. return Template('Asteroid_large', body, frags, {}, {})
def addMolecule(): # Connect to Azrael. client = azrael.clerk.Clerk() # The molecule will consist of several cubes. vert, csmetabox = demolib.cubeGeometry(0.5, 0.5, 0.5) # The molecule object. ofs = 2 frag_src = { 'up': [0, ofs, 0], 'down': [0, -ofs, 0], 'left': [ofs, 0, 0], 'right': [-ofs, 0, 0], 'front': [0, 0, ofs], 'back': [0, 0, -ofs], 'center': [0, 0, 0], } del ofs # frag_src = {'down': frag_src['down']} # frag_src = {'center': frag_src['center']} frags, cshapes = {}, {} for name, pos in frag_src.items(): frags[name] = demolib.getFragMetaRaw( vert=vert, uv=[], rgb=[], scale=1, pos=pos, rot=[0, 0, 0, 1], ) cshapes[name] = csmetabox._replace(position=pos) del name, pos del frag_src # Rigid body data parameters (defines its physics, not appearance). Specify # the centre of mass (com) and principal axis rotation (paxis) for the # inertia values. body = demolib.getRigidBody( imass=0.1, com=[0, -1, 0], inertia=[1, 2, 3], paxis=[0, 0, 0, 1], cshapes=cshapes, ) # Define the object template and upload it to Azrael. template = Template('molecule', body, frags, {}, {}) ret = client.addTemplates([template]) # Spawn one molecule. templates = [{'templateID': 'molecule', 'rbs': {'position': [0, 0.5, 0]}}] objID = client.spawn(templates)
def addPlatforms(): # Connect to Azrael. client = azrael.clerk.Clerk() # Geometry and collision shape for platform. vert, cshapes = demolib.cubeGeometry(2, 0.1, 2) # Rigid body data for platforms (defines their physics, not appearance). body = demolib.getRigidBody( cshapes={'cs': cshapes}, linFactor=[0, 0, 0], rotFactor=[0, 0, 0] ) # Geometry for the platforms (defines their appearance, not physics). fm = demolib.getFragMetaRaw( vert=vert, uv=[], rgb=[], scale=1, pos=(0, 0, 0), rot=(0, 0, 0, 1) ) frags = {'frag_1': fm} # Define the platform template and upload it to Azrael. template = Template('platform', body, frags, {}, {}) ret = client.addTemplates([template]) # Spawn several platforms at different positions. Their positions are # supposed to create the impression of a stairway. The values assume the # camera is at [0, 0, 10] and points in -z direction. platforms = [] for ii in range(5): pos = (-10 + ii * 5, -ii * 2, -20) platforms.append( { 'templateID': 'platform', 'rbs': { 'position': pos, 'velocityLin': (0, 0, 0), 'scale': 1, 'imass': 20 } } ) platformIDs = client.spawn(platforms) # Tag all platforms with a custom string. cmd = {platformID: 'Platform' for platformID in platformIDs.data} assert client.setObjectTags(cmd)
def addPlatforms(): # Connect to Azrael. client = azrael.clerk.Clerk() # Geometry and collision shape for platform. vert, cshapes = demolib.cubeGeometry(2, 0.1, 2) # Rigid body data for platforms (defines their physics, not appearance). body = demolib.getRigidBody(cshapes={'cs': cshapes}, linFactor=[0, 0, 0], rotFactor=[0, 0, 0]) # Geometry for the platforms (defines their appearance, not physics). fm = demolib.getFragMetaRaw(vert=vert, uv=[], rgb=[], scale=1, pos=(0, 0, 0), rot=(0, 0, 0, 1)) frags = {'frag_1': fm} # Define the platform template and upload it to Azrael. template = Template('platform', body, frags, {}, {}) ret = client.addTemplates([template]) # Spawn several platforms at different positions. Their positions are # supposed to create the impression of a stairway. The values assume the # camera is at [0, 0, 10] and points in -z direction. platforms = [] for ii in range(5): pos = (-10 + ii * 5, -ii * 2, -20) platforms.append({ 'templateID': 'platform', 'rbs': { 'position': pos, 'velocityLin': (0, 0, 0), 'scale': 1, 'imass': 20 } }) platformIDs = client.spawn(platforms) # Tag all platforms with a custom string. cmd = {platformID: 'Platform' for platformID in platformIDs.data} assert client.setObjectTags(cmd)
def spawnAsteroid(client): """Spawn the asteroid 67P and return its Azrael ID (AID). """ # This is a convenience function that returns geometry and collision shape # for a sphere. vert, cshapes = demolib.loadSphere(10) # This is a convenience function that compiles the rigid body parameters to # compute the Newtonian physics. body = demolib.getRigidBody(cshapes={'cs': cshapes}) # Define the visual geometry of the Asteroid. frags = {'foo': demolib.getFragMetaRaw(vert=vert, uv=[], rgb=[])} # Define the template for the asteroid and upload it to Azrael. template = pyazrael.aztypes.Template('asteroid', body, frags, {}, {}) ret = client.addTemplates([template]) # Specify the parameters for the asteroid and spawn it. spawn_param = { 'templateID': 'asteroid', 'rbs': { 'position': (0, 0, -100), 'velocityLin': (0, 0, 0), 'imass': 0.0001, } } ret = client.spawn([spawn_param]) assert ret.ok # The return value contains the Azrael IDs (AIDs) of all spawned objects. # In this case we have only spawned one, namely the asteroid. asteroidID = ret.data[0] # Tag the asteroid with a custom string (optional). assert client.setObjectTags({asteroidID: '67P'}).ok return asteroidID
def spawnCubes(numCols, numRows, numLayers, center=(0, 0, 0)): """ Spawn multiple cubes in a regular grid. The number of cubes equals ``numCols`` * ``numRows`` * ``numLayers``. The center of this "prism" is at ``center``. Every cube has two boosters and two factories. The factories can themselves spawn more (purely passive) cubes. """ # Get a Client instance. client = pyazrael.AzraelClient() # Geometry and collision shape for cube. vert, cs = demolib.cubeGeometry() # Assign the UV coordinates. Each vertex needs a coordinate pair. That # means each triangle needs 6 coordinates. And the cube has 12 triangles. uv = np.zeros(12 * 6, np.float64) uv[0:6] = [0, 0, 1, 0, 1, 1] uv[6:12] = [0, 0, 1, 1, 0, 1] uv[12:18] = [1, 0, 0, 1, 0, 0] uv[18:24] = [1, 0, 1, 1, 0, 1] uv[24:30] = [0, 0, 1, 1, 0, 1] uv[30:36] = [0, 0, 1, 0, 1, 1] uv[36:42] = [1, 1, 1, 0, 0, 0] uv[42:48] = [1, 1, 0, 0, 0, 1] uv[48:54] = [0, 1, 1, 0, 1, 1] uv[54:60] = [0, 1, 0, 0, 1, 0] uv[60:66] = [0, 1, 0, 0, 1, 0] uv[66:72] = [1, 1, 0, 1, 1, 0] uv = np.array(uv, np.float64) # Compile the path to the texture file. path_base = os.path.dirname(os.path.abspath(__file__)) path_base = os.path.join(path_base, '..', 'azrael', 'static', 'img') fname = os.path.join(path_base, 'texture_5.jpg') # Load the texture and convert it to flat vector because this is how OpenGL # will want it. img = PIL.Image.open(fname) img = np.array(img) rgb = np.rollaxis(np.flipud(img), 1).flatten() # ---------------------------------------------------------------------- # Define a cube with boosters and factories. # ---------------------------------------------------------------------- # Two boosters, one left, one right. Both point in the same direction. boosters = { '0': aztypes.Booster(position=[+0.05, 0, 0], direction=[0, 0, 1], force=0), '1': aztypes.Booster(position=[-0.05, 0, 0], direction=[0, 0, 1], force=0) } # ---------------------------------------------------------------------- # Define more booster cubes, each with a different texture. # ---------------------------------------------------------------------- tID_cube = {} templates = [] texture_errors = 0 for ii in range(numRows * numCols * numLayers): # File name of texture. fname = os.path.join(path_base, 'texture_{}.jpg'.format(ii + 1)) # Load the texture image. If the image is unavailable do not endow the # cube with a texture. try: img = PIL.Image.open(fname) img = np.array(img) rgb = np.rollaxis(np.flipud(img), 1) curUV = uv except FileNotFoundError: texture_errors += 1 rgb = curUV = np.array([]) # Create the template. tID = ('BoosterCube_{}'.format(ii)) frags = { 'frag_1': demolib.getFragMetaRaw(vert, curUV, rgb), 'frag_2': demolib.getFragMetaRaw(vert, curUV, rgb) } body = demolib.getRigidBody(cshapes={'0': cs}) tmp = Template(tID, body, frags, boosters, {}) templates.append(tmp) # Add the templateID to a dictionary because we will need it in the # next step to spawn the templates. tID_cube[ii] = tID del frags, tmp, tID, fname if texture_errors > 0: print('Could not load texture for {} of the {} objects'.format( texture_errors, ii + 1)) # Define all templates. print('Adding {} templates: '.format(ii + 1), end='', flush=True) t0 = time.time() assert client.addTemplates(templates).ok print('{:.1f}s'.format(time.time() - t0)) # ---------------------------------------------------------------------- # Spawn the differently textured cubes in a regular grid. # ---------------------------------------------------------------------- allObjs = [] cube_idx = 0 cube_spacing = 0.0 # Determine the template and position for every cube. The cubes are *not* # spawned in this loop, but afterwards. print('Compiling scene: ', end='', flush=True) t0 = time.time() for row in range(numRows): for col in range(numCols): for lay in range(numLayers): # Base position of cube. pos = np.array([col, row, lay], np.float64) # Add space in between cubes. pos *= -(4 + cube_spacing) # Correct the cube's position to ensure the center of the # grid coincides with the origin. pos[0] += (numCols // 2) * (1 + cube_spacing) pos[1] += (numRows // 2) * (1 + cube_spacing) pos[2] += (numLayers // 2) * (1 + cube_spacing) # Move the grid to position ``center``. pos += np.array(center) # Store the position and template for this cube. allObjs.append({ 'template': tID_cube[cube_idx], 'position': pos }) cube_idx += 1 del pos # Since the first four cubes will be chained together we need at least four # of them! assert len(allObjs) >= 4 allObjs = [] pos_0 = [2, 0, -10] pos_1 = [-2, 0, -10] pos_2 = [-6, 0, -10] pos_3 = [-10, 0, -10] allObjs.append({'templateID': tID_cube[0], 'rbs': {'position': pos_0}}) allObjs.append({'templateID': tID_cube[1], 'rbs': {'position': pos_1}}) allObjs.append({'templateID': tID_cube[2], 'rbs': {'position': pos_2}}) allObjs.append({'templateID': tID_cube[3], 'rbs': {'position': pos_3}}) # The first object cannot move (only rotate). It serves as an anchor for # the connected bodies. allObjs[0]['rbs']['linFactor'] = [0, 0, 0] allObjs[0]['rbs']['rotFactor'] = [1, 1, 1] # Add a small damping factor to all bodies to avoid them moving around # perpetually. for oo in allObjs[1:]: oo['rbs']['linFactor'] = [0.9, 0.9, 0.9] oo['rbs']['rotFactor'] = [0.9, 0.9, 0.9] print('{:,} objects ({:.1f}s)'.format(len(allObjs), time.time() - t0)) del cube_idx, cube_spacing, row, col, lay # Spawn the cubes from the templates at the just determined positions. print('Spawning {} objects: '.format(len(allObjs)), end='', flush=True) t0 = time.time() ret = client.spawn(allObjs) assert ret.ok objIDs = ret.data print('{:.1f}s'.format(time.time() - t0)) # Define the constraints. p2p_0 = ConstraintP2P(pivot_a=[-2, 0, 0], pivot_b=[2, 0, 0]) p2p_1 = ConstraintP2P(pivot_a=[-2, 0, 0], pivot_b=[2, 0, 0]) p2p_2 = ConstraintP2P(pivot_a=[-2, 0, 0], pivot_b=[2, 0, 0]) dof = Constraint6DofSpring2( frameInA=[0, 0, 0, 0, 0, 0, 1], frameInB=[0, 0, 0, 0, 0, 0, 1], stiffness=[2, 2, 2, 1, 1, 1], damping=[1, 1, 1, 1, 1, 1], equilibrium=[-2, -2, -2, 0, 0, 0], linLimitLo=[-4.5, -4.5, -4.5], linLimitHi=[4.5, 4.5, 4.5], rotLimitLo=[-0.1, -0.2, -0.3], rotLimitHi=[0.1, 0.2, 0.3], bounce=[1, 1.5, 2], enableSpring=[True, False, False, False, False, False]) constraints = [ ConstraintMeta('', 'p2p', objIDs[0], objIDs[1], p2p_0), ConstraintMeta('', 'p2p', objIDs[1], objIDs[2], p2p_1), ConstraintMeta('', '6DOFSPRING2', objIDs[2], objIDs[3], dof), ] assert client.addConstraints(constraints) == (True, None, [True] * len(constraints))
def BoostercubeTemplate(scale=1.0): """ Return template for BoosterCube. """ # Get a Client instance. client = pyazrael.AzraelClient() # Load the model. vert, uv, rgb = demolib.loadBoosterCubeBlender() frag_cube = { 'vert': vert, 'uv': uv, 'rgb': rgb, 'scale': scale, 'pos': (0, 0, 0), 'rot': (0, 0, 0, 1) } del vert, uv, rgb # Attach six boosters, two for every axis. dir_x = np.array([1, 0, 0]) dir_y = np.array([0, 1, 0]) dir_z = np.array([0, 0, 1]) pos = (0, 0, 0) B = aztypes.Booster boosters = { 'b_x': B(pos, direction=(1, 0, 0), force=0), 'b_y': B(pos, direction=(0, 1, 0), force=0), 'b_z': B(pos, direction=(0, 0, 1), force=0) } del dir_x, dir_y, dir_z, pos, B # Load sphere and colour it blue(ish). This is going to be the (super # simple) "flame" that comes out of the (still invisible) boosters. p = os.path.dirname(os.path.abspath(__file__)) fname = os.path.join(p, 'models', 'sphere', 'sphere.obj') vert, uv, rgb = demolib.loadModel(fname) rgb = np.tile([0, 0, 0.8], len(vert) // 3) rgb += 0.2 * np.random.rand(len(rgb)) rgb = np.array(255 * rgb.clip(0, 1), np.uint8) frag_flame = { 'vert': vert, 'uv': [], 'rgb': rgb, 'pos': (0, 0, 0), 'rot': (0, 0, 0, 1) } del p, fname, vert, uv, rgb # Add the template to Azrael. tID = 'spaceship' cs = aztypes.CollShapeBox(scale, scale, scale) cs = aztypes.CollShapeMeta('box', (0, 0, 0), (0, 0, 0, 1), cs) body = demolib.getRigidBody(cshapes={'0': cs}) frags = { 'frag_1': demolib.getFragMetaRaw(**frag_cube), 'b_x': demolib.getFragMetaRaw(**frag_flame), 'b_y': demolib.getFragMetaRaw(**frag_flame), 'b_z': demolib.getFragMetaRaw(**frag_flame), } template = Template(tID, body, frags, boosters, {}) return template
def addTexturedCubeTemplates(numCols, numRows, numLayers): # Get a Client instance. client = pyazrael.AzraelClient() # Geometry and collision shape for cube. vert, cs = demolib.cubeGeometry() # Assign the UV coordinates. Each vertex needs a coordinate pair. That # means each triangle needs 6 coordinates. And the cube has 12 triangles. uv = np.zeros(12 * 6, np.float64) uv[0:6] = [0, 0, 1, 0, 1, 1] uv[6:12] = [0, 0, 1, 1, 0, 1] uv[12:18] = [1, 0, 0, 1, 0, 0] uv[18:24] = [1, 0, 1, 1, 0, 1] uv[24:30] = [0, 0, 1, 1, 0, 1] uv[30:36] = [0, 0, 1, 0, 1, 1] uv[36:42] = [1, 1, 1, 0, 0, 0] uv[42:48] = [1, 1, 0, 0, 0, 1] uv[48:54] = [0, 1, 1, 0, 1, 1] uv[54:60] = [0, 1, 0, 0, 1, 0] uv[60:66] = [0, 1, 0, 0, 1, 0] uv[66:72] = [1, 1, 0, 1, 1, 0] uv = np.array(uv, np.float64) # Compile the path to the texture file. path_base = os.path.dirname(os.path.abspath(__file__)) path_base = os.path.join(path_base, '..', 'azrael', 'static', 'img') fname = os.path.join(path_base, 'texture_5.jpg') # Load the texture and convert it to a flat vector because this is how # OpenGL will want it. img = PIL.Image.open(fname) img = np.array(img) rgb = np.rollaxis(np.flipud(img), 1) # ---------------------------------------------------------------------- # Create templates for the factory output. # ---------------------------------------------------------------------- tID_1 = 'Product1' tID_2 = 'Product2' frags_1 = {'frag_1': demolib.getFragMetaRaw(0.75 * vert, uv, rgb)} frags_2 = {'frag_1': demolib.getFragMetaRaw(0.24 * vert, uv, rgb)} body = demolib.getRigidBody(cshapes={'0': cs}) t1 = Template(tID_1, body, frags_1, {}, {}) t2 = Template(tID_2, body, frags_2, {}, {}) assert client.addTemplates([t1, t2]).ok del frags_1, frags_2, t1, t2 # ---------------------------------------------------------------------- # Define a cube with boosters and factories. # ---------------------------------------------------------------------- # Two boosters, one left, one right. Both point in the same direction. boosters = { '0': aztypes.Booster(position=[+0.05, 0, 0], direction=[0, 0, 1], force=0), '1': aztypes.Booster(position=[-0.05, 0, 0], direction=[0, 0, 1], force=0) } # Two factories, one left one right. They will eject the new objects # forwards and backwards, respectively. factories = { '0': aztypes.Factory(position=[+1.5, 0, 0], direction=[+1, 0, 0], templateID=tID_1, exit_speed=[0.1, 1]), '1': aztypes.Factory(position=[-1.5, 0, 0], direction=[-1, 0, 0], templateID=tID_2, exit_speed=[0.1, 1]) } # Add the template. tID_3 = 'BoosterCube' frags = {'frag_1': demolib.getFragMetaRaw(vert, uv, rgb)} body = demolib.getRigidBody(cshapes={'0': cs}) t3 = Template(tID_3, body, frags, boosters, factories) assert client.addTemplates([t3]).ok del frags, t3 # ---------------------------------------------------------------------- # Define more booster cubes, each with a different texture. # ---------------------------------------------------------------------- tID_cube = {} templates = [] texture_errors = 0 for ii in range(numRows * numCols * numLayers): # File name of texture. fname = os.path.join(path_base, 'texture_{}.jpg'.format(ii + 1)) # Load the texture image. If the image is unavailable do not endow the # cube with a texture. try: img = PIL.Image.open(fname) img = np.array(img) rgb = np.rollaxis(np.flipud(img), 1) curUV = uv except FileNotFoundError: texture_errors += 1 rgb = curUV = np.array([]) # Create the template. tID = ('BoosterCube_{}'.format(ii)) frags = {'frag_1': demolib.getFragMetaRaw(vert, curUV, rgb), 'frag_2': demolib.getFragMetaRaw(vert, curUV, rgb)} body = demolib.getRigidBody(cshapes={'0': cs}) tmp = Template(tID, body, frags, boosters, {}) templates.append(tmp) # Add the templateID to a dictionary because we will need it in the # next step to spawn the templates. tID_cube[ii] = tID del frags, tmp, tID, fname if texture_errors > 0: print('Could not load texture for {} of the {} objects' .format(texture_errors, ii + 1)) # Define all templates. print('Adding {} templates: '.format(ii + 1), end='', flush=True) t0 = time.time() assert client.addTemplates(templates).ok print('{:.1f}s'.format(time.time() - t0)) return tID_cube
def addBoosterCubeTemplate(scale, vert, uv, rgb): # Get a Client instance. client = pyazrael.AzraelClient() # Ensure the data has the correct format. vert = scale * np.array(vert) uv = np.array(uv, np.float32) rgb = np.array(rgb, np.uint8) print('done') # Attach four boosters: left (points down), front (points back), right # (points up), and back (point forward). dir_up = np.array([0, +1, 0]) dir_forward = np.array([0, 0, -1]) pos_left = np.array([-1.5, 0, 0]) pos_center = np.zeros(3) boosters = { '0': aztypes.Booster(position=pos_left, direction=-dir_up, force=0), '1': aztypes.Booster(position=pos_center, direction=dir_forward, force=0), '2': aztypes.Booster(position=-pos_left, direction=dir_up, force=0), '3': aztypes.Booster(position=pos_center, direction=-dir_forward, force=0) } del dir_up, dir_forward, pos_left, pos_center # Construct a Tetrahedron (triangular Pyramid). This is going to be the # (super simple) "flame" that comes out of the (still invisible) boosters. y = 0.5 * np.arctan(np.pi / 6) a = (-0.5, -y, 1) b = (0.5, -y, 1) c = (0, 3 / 4 - y, 1) d = (0, 0, 0) vert_b = [(a + b + c) + (a + b + d) + (a + c + d) + (b + c + d)] vert_b = np.array(vert_b[0], np.float64) del a, b, c, d, y # Add the template to Azrael. print(' Adding template to Azrael... ', end='', flush=True) tID = 'ground' cs = CollShapeBox(1, 1, 1) cs = CollShapeMeta('box', (0, 0, 0), (0, 0, 0, 1), cs) z = np.array([]) frags = { 'frag_1': demolib.getFragMetaRaw(vert, uv, rgb), 'b_left': demolib.getFragMetaRaw(vert_b, z, z), 'b_right': demolib.getFragMetaRaw(vert_b, z, z), } body = demolib.getRigidBody() temp = Template(tID, body, frags, boosters, {}) assert client.addTemplates([temp]).ok del cs, frags, temp, z print('done') # Spawn the template near the center. print(' Spawning object... ', end='', flush=True) pos, orient = [0, 0, -10], [0, 1, 0, 0] new_obj = { 'templateID': tID, 'rbs': { 'scale': scale, 'imass': 0.1, 'position': pos, 'rotation': orient, 'linFactor': [1, 1, 1], 'rotFactor': [1, 1, 1]} } ret = client.spawn([new_obj]) objID = ret.data[0] print('done (ID=<{}>)'.format(objID)) # Disable the booster fragments by settings their scale to Zero. newStates = {objID: { 'b_left': {'op': 'mod', 'scale': 0}, 'b_right': {'op': 'mod', 'scale': 0}, }} assert client.setFragments(newStates).ok
def loadBlender(fname_blender: str): """ Compile and return the fragments and collision shapes from ``fname``. The ``fname`` file must specify a Blender file. :param str fname: Blender file name :return: (frags dict, cshapes dict) """ # Use Blender itself to load the Blender file. This will trigger a script # inside Blender that creates a JSON file with custom scene data. The name # of that file is returned in 'fname_az'. fname_az = processBlenderFile(fname_blender) # Load the collision shape data and geometry. pp = pyassimp.postprocess azFile = json.loads(open(fname_az, 'rb').read().decode('utf8')) scene = pyassimp.load(fname_blender, pp.aiProcess_Triangulate) del fname_az, pp # Sanity check: both must contain information about the exact same objects. assert set(azFile.keys()) == {_.name for _ in scene.meshes} # Load all the materials. materials = loadMaterials(scene, fname_blender) # Compile fragments and collision shapes for Azrael. frags, cshapes = {}, {} for mesh in scene.meshes: # Get the material for this mesh. material = materials[mesh.materialindex] # Iterate over the faces and rebuild the vertices. azData = azFile[mesh.name] vert = np.zeros(len(mesh.faces) * 9) for idx, face in enumerate(mesh.faces): tmp = [mesh.vertices[_] for _ in face] start, stop = idx * 9, (idx + 1) * 9 vert[start:stop] = np.hstack(tmp) del start, stop, tmp, idx, face # Copy the UV coordinates, if there are any. if len(mesh.texturecoords) > 0: uv = np.zeros(len(mesh.faces) * 6) for idx, face in enumerate(mesh.faces): tmp = [mesh.texturecoords[0][_, :2] for _ in face] start, stop = 6 * idx, 6 * (idx + 1) uv[start:stop] = np.hstack(tmp) del start, stop, tmp, idx, face # Sanity check. assert (len(uv) // 2 == len(vert) // 3) else: uv = 0.5 * np.ones(2 * (len(vert) // 3)) uv = [] # Azrael does not allow 'dots', yet Bullet uses it prominently to name # objects. As a quick fix, simply replace the dots with something else. # fixme: should be redundant once #149 (https://trello.com/c/wcHX3qGd) # is implemented. azname = mesh.name.replace('.', 'x') # Unpack the position and orientation of the mesh in world coordinates. pos, rot, dim = azData['pos'], azData['rot'], np.array(azData['dimensions']) # Unpack the interior points and put them into any kind of byte string. # This will be attached as yet another file to the fragment data. interior_points = json.dumps(azData['interior_points']).encode('utf8') # Create a RAW fragment. scale = 1 rgb, width, height = material['RGB'], material['width'], material['height'] frag = demolib.getFragMetaRaw(vert, uv, rgb, scale, pos, rot) frag.files['interior_points'] = interior_points frags[azname] = frag # Construct the BOX collision shape based on the Blender dimensions. hlen_x, hlen_y, hlen_z = (dim / 2).tolist() box = aztypes.CollShapeBox(hlen_x, hlen_y, hlen_z) # Construct the CollshapeMeta data. cshapes[azname] = aztypes.CollShapeMeta('box', pos, rot, box) del azData, vert, dim, scale, rgb, width, height, hlen_x, hlen_y, hlen_z return frags, cshapes
def spawnCubes(numCols, numRows, numLayers, center=(0, 0, 0)): """ Spawn multiple cubes in a regular grid. The number of cubes equals ``numCols`` * ``numRows`` * ``numLayers``. The center of this "prism" is at ``center``. Every cube has two boosters and two factories. The factories can themselves spawn more (purely passive) cubes. """ # Get a Client instance. client = pyazrael.AzraelClient() # Geometry and collision shape for cube. vert, cs = demolib.cubeGeometry() # Assign the UV coordinates. Each vertex needs a coordinate pair. That # means each triangle needs 6 coordinates. And the cube has 12 triangles. uv = np.zeros(12 * 6, np.float64) uv[0:6] = [0, 0, 1, 0, 1, 1] uv[6:12] = [0, 0, 1, 1, 0, 1] uv[12:18] = [1, 0, 0, 1, 0, 0] uv[18:24] = [1, 0, 1, 1, 0, 1] uv[24:30] = [0, 0, 1, 1, 0, 1] uv[30:36] = [0, 0, 1, 0, 1, 1] uv[36:42] = [1, 1, 1, 0, 0, 0] uv[42:48] = [1, 1, 0, 0, 0, 1] uv[48:54] = [0, 1, 1, 0, 1, 1] uv[54:60] = [0, 1, 0, 0, 1, 0] uv[60:66] = [0, 1, 0, 0, 1, 0] uv[66:72] = [1, 1, 0, 1, 1, 0] uv = np.array(uv, np.float64) # Compile the path to the texture file. path_base = os.path.dirname(os.path.abspath(__file__)) path_base = os.path.join(path_base, '..', 'azrael', 'static', 'img') fname = os.path.join(path_base, 'texture_5.jpg') # Load the texture and convert it to flat vector because this is how OpenGL # will want it. img = PIL.Image.open(fname) img = np.array(img) rgb = np.rollaxis(np.flipud(img), 1).flatten() # ---------------------------------------------------------------------- # Define a cube with boosters and factories. # ---------------------------------------------------------------------- # Two boosters, one left, one right. Both point in the same direction. boosters = { '0': aztypes.Booster(position=[+0.05, 0, 0], direction=[0, 0, 1], force=0), '1': aztypes.Booster(position=[-0.05, 0, 0], direction=[0, 0, 1], force=0) } # ---------------------------------------------------------------------- # Define more booster cubes, each with a different texture. # ---------------------------------------------------------------------- tID_cube = {} templates = [] texture_errors = 0 for ii in range(numRows * numCols * numLayers): # File name of texture. fname = os.path.join(path_base, 'texture_{}.jpg'.format(ii + 1)) # Load the texture image. If the image is unavailable do not endow the # cube with a texture. try: img = PIL.Image.open(fname) img = np.array(img) rgb = np.rollaxis(np.flipud(img), 1) curUV = uv except FileNotFoundError: texture_errors += 1 rgb = curUV = np.array([]) # Create the template. tID = ('BoosterCube_{}'.format(ii)) frags = {'frag_1': demolib.getFragMetaRaw(vert, curUV, rgb), 'frag_2': demolib.getFragMetaRaw(vert, curUV, rgb)} body = demolib.getRigidBody(cshapes={'0': cs}) tmp = Template(tID, body, frags, boosters, {}) templates.append(tmp) # Add the templateID to a dictionary because we will need it in the # next step to spawn the templates. tID_cube[ii] = tID del frags, tmp, tID, fname if texture_errors > 0: print('Could not load texture for {} of the {} objects' .format(texture_errors, ii + 1)) # Define all templates. print('Adding {} templates: '.format(ii + 1), end='', flush=True) t0 = time.time() assert client.addTemplates(templates).ok print('{:.1f}s'.format(time.time() - t0)) # ---------------------------------------------------------------------- # Spawn the differently textured cubes in a regular grid. # ---------------------------------------------------------------------- allObjs = [] cube_idx = 0 cube_spacing = 0.0 # Determine the template and position for every cube. The cubes are *not* # spawned in this loop, but afterwards. print('Compiling scene: ', end='', flush=True) t0 = time.time() for row in range(numRows): for col in range(numCols): for lay in range(numLayers): # Base position of cube. pos = np.array([col, row, lay], np.float64) # Add space in between cubes. pos *= -(4 + cube_spacing) # Correct the cube's position to ensure the center of the # grid coincides with the origin. pos[0] += (numCols // 2) * (1 + cube_spacing) pos[1] += (numRows // 2) * (1 + cube_spacing) pos[2] += (numLayers // 2) * (1 + cube_spacing) # Move the grid to position ``center``. pos += np.array(center) # Store the position and template for this cube. allObjs.append({'template': tID_cube[cube_idx], 'position': pos}) cube_idx += 1 del pos # Since the first four cubes will be chained together we need at least four # of them! assert len(allObjs) >= 4 allObjs = [] pos_0 = [2, 0, -10] pos_1 = [-2, 0, -10] pos_2 = [-6, 0, -10] pos_3 = [-10, 0, -10] allObjs.append({'templateID': tID_cube[0], 'rbs': {'position': pos_0}}) allObjs.append({'templateID': tID_cube[1], 'rbs': {'position': pos_1}}) allObjs.append({'templateID': tID_cube[2], 'rbs': {'position': pos_2}}) allObjs.append({'templateID': tID_cube[3], 'rbs': {'position': pos_3}}) # The first object cannot move (only rotate). It serves as an anchor for # the connected bodies. allObjs[0]['rbs']['linFactor'] = [0, 0, 0] allObjs[0]['rbs']['rotFactor'] = [1, 1, 1] # Add a small damping factor to all bodies to avoid them moving around # perpetually. for oo in allObjs[1:]: oo['rbs']['linFactor'] = [0.9, 0.9, 0.9] oo['rbs']['rotFactor'] = [0.9, 0.9, 0.9] print('{:,} objects ({:.1f}s)'.format(len(allObjs), time.time() - t0)) del cube_idx, cube_spacing, row, col, lay # Spawn the cubes from the templates at the just determined positions. print('Spawning {} objects: '.format(len(allObjs)), end='', flush=True) t0 = time.time() ret = client.spawn(allObjs) assert ret.ok objIDs = ret.data print('{:.1f}s'.format(time.time() - t0)) # Define the constraints. p2p_0 = ConstraintP2P(pivot_a=[-2, 0, 0], pivot_b=[2, 0, 0]) p2p_1 = ConstraintP2P(pivot_a=[-2, 0, 0], pivot_b=[2, 0, 0]) p2p_2 = ConstraintP2P(pivot_a=[-2, 0, 0], pivot_b=[2, 0, 0]) dof = Constraint6DofSpring2( frameInA=[0, 0, 0, 0, 0, 0, 1], frameInB=[0, 0, 0, 0, 0, 0, 1], stiffness=[2, 2, 2, 1, 1, 1], damping=[1, 1, 1, 1, 1, 1], equilibrium=[-2, -2, -2, 0, 0, 0], linLimitLo=[-4.5, -4.5, -4.5], linLimitHi=[4.5, 4.5, 4.5], rotLimitLo=[-0.1, -0.2, -0.3], rotLimitHi=[0.1, 0.2, 0.3], bounce=[1, 1.5, 2], enableSpring=[True, False, False, False, False, False]) constraints = [ ConstraintMeta('', 'p2p', objIDs[0], objIDs[1], p2p_0), ConstraintMeta('', 'p2p', objIDs[1], objIDs[2], p2p_1), ConstraintMeta('', '6DOFSPRING2', objIDs[2], objIDs[3], dof), ] assert client.addConstraints(constraints) == ( True, None, [True] * len(constraints))
def loadColladaModel(filename): """ Load the Collada modle created in Blender. This function makes the following hard coded assumptions about that file: * It contains a node called 'Parent' * That parent contains a mesh called 'MainBody'. """ # Load the scene. scene = pyassimp.load(filename) # Find the node called 'Parent' (hard coded assumption that must be matched # by Blender model). p = None print('\nModel file contains the following meshes:') for child in scene.rootnode.children: print(' *', child.name) if child.name == 'Parent': p = child # The Parent node must exist, and it must contain one mesh called # 'MainBody'. assert p is not None objs = {_.name: _ for _ in p.children} assert 'MainBody' in objs # For later: compute the inverse transform of MainBody. scale, i_transform = inverseTransform(objs['MainBody'].transformation) frags, cshapes = {}, {} for name in objs: # 4x4 transformation matrix for current mesh (this is always in global # coordinates, not relative to anything). t = np.array(objs[name].transformation) # Vertices of current mesh (Nx3). vert = objs[name].meshes[0].vertices # Transpose vertex matrix to obtain the 3xN matrix. Then add a row of # 1s to obtain a 4xN matrix. vert = vert.T vert = np.vstack((vert, np.ones(vert.shape[1]))) # Scale/rotatate/translate the vertices relative to the position of the # MainBody. Then drop the (auxiliary) last row again to get back to a # Nx3 matrix of vertices. vert = i_transform.dot(t.dot(vert)) vert = vert[:-1, :].T # Flatten the vertex matrix into a simple list and compile it into a # fragment. vert = vert.flatten() frags[name] = demolib.getFragMetaRaw(vert, uv=[], rgb=[], scale=1) # All bodies whose name starts with 'cs' get a spherical collision # shape. if name.lower().startswith('cs'): # Compute the combined transformation matrix that represents the # difference between the MainBody's position and this one. To find # this, simpy apply the invers transform of the MainBody to the # transform of the locat body. The latter would move the current # object to its correct position in world coordinates, and the # former will undo offset and rotation of the MainBody. t = i_transform.dot(t) # Determine the position. # Fixme: also determine the quaternion and scale from the total # transformation matrix. _pos = tuple(t[:3, 3].tolist()) _rot = (0, 0, 0, 1) # Create a spherical collision shape and place it relative to the # MainBody. sphere = aztypes.CollShapeSphere(1) cshapes[name] = aztypes.CollShapeMeta('sphere', _pos, _rot, sphere) return frags, cshapes