Пример #1
0
def createTemplate():
    # Create the vertices for a unit cube.
    vert = defineCube()

    # Define initial fragment size and position relative to rigid body.
    scale = 1
    pos, rot = (0, 0, 0), (0, 0, 0, 1)

    # Define the one and only geometry fragment for this template.
    data_raw = FragRaw(vert, [], [])
    frags = {'frag_foo': FragMeta('raw', scale, pos, rot, data_raw)}
    del scale, pos, rot

    # We will need that collision shape to construct the rigid body below.
    cs_sphere = CollShapeMeta(cstype='Sphere',
                              position=(0, 0, 0),
                              rotation=(0, 0, 0, 1),
                              csdata=CollShapeSphere(radius=1))

    # Create the rigid body.
    body = azrael.aztypes.RigidBodyData(
        scale=1,
        imass=1,
        restitution=0.9,
        rotation=(0, 0, 0, 1),
        position=(0, 0, 0),
        velocityLin=(0, 0, 0),
        velocityRot=(0, 0, 0),
        cshapes={'foo_sphere': cs_sphere},
        linFactor=(1, 1, 1),
        rotFactor=(1, 1, 1),
        version=0)

    # Compile and return the template.
    return Template('my_first_template', body, frags, {}, {})
Пример #2
0
def spawn_model():
    # Load the geometry.
    p = os.path.dirname(os.path.abspath(__file__))
    filename = os.path.join(p, 'models', 'cshape_experiment', 'csbodies.dae')
    frags, cshapes = loadColladaModel(filename)
    body = demolib.getRigidBody(cshapes=cshapes, position=(0, 0, 0))

    # Get a Clerk instance. Then upload the template to Azrael.
    print('\nAdding model template... ', end='', flush=True)
    client = azrael.clerk.Clerk()
    tID = 'mymodel'
    assert client.addTemplates([Template(tID, body, frags, {}, {})]).ok
    del body, frags
    print('done')

    # Spawn the template at the center.
    print('Spawning model... ', end='', flush=True)
    new_obj = {
        'templateID': tID,
        'rbs': {
            'scale': 1,
            'imass': 0.1,
            'position': [0, 0, 0],
            'rotation': [0, 0, 0, 1],
            'linFactor': [1, 1, 1],
            'rotFactor': [1, 1, 1]
        }
    }
    ret = client.spawn([new_obj])
    assert ret.ok

    # Status message. Then return the object ID of the model.
    print('done (ID=<{}>)'.format(ret.data[0]))
    return ret.data[0]
Пример #3
0
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, {}, {})
Пример #4
0
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})
Пример #5
0
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, {}, {})
Пример #6
0
def spawnSpaceship(scale, fname):
    """
    Define a sphere with three boosters and four fragments.

    The first fragment comprises the hull (ie. the sphere itself), whereas
    remaining three fragments reprsent the "flames" that come out of the
    boosters.
    """
    # Get a Client instance.
    client = pyazrael.AzraelClient()

    # Load the model.
    vert, uv, rgb = demolib.loadBoosterCubeBlender()
    frag_cube = demolib.compileRawFragment(vert, uv, rgb)
    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 color 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 = demolib.compileRawFragment(vert, np.array([]), rgb)
    del p, fname, vert, uv, rgb

    # Add the template to Azrael.
    print('  Adding template to Azrael... ', end='', flush=True)
    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})
    pos, rot = (0, 0, 0), (0, 0, 0, 1)
    frags = {
        'frag_1': FragMeta('raw', scale, pos, rot, frag_cube),
        'b_x': FragMeta('raw', 0, pos, rot, frag_flame),
        'b_y': FragMeta('raw', 0, pos, rot, frag_flame),
        'b_z': FragMeta('raw', 0, pos, rot, frag_flame),
    }
    temp = Template(tID, body, frags, boosters, {})
    assert client.addTemplates([temp]).ok
    del cs, frags, temp, frag_cube, frag_flame, scale, pos, rot
    print('done')
Пример #7
0
def getTemplate(name='template',
                rbs=None,
                fragments={},
                boosters={},
                factories={},
                custom=''):
    if rbs is None:
        rbs = getRigidBody(cshapes={'cssphere': getCSSphere()})

    return Template(name, rbs, fragments, boosters, factories, custom)
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)
Пример #9
0
def spawnObject(client, frags, cshapes):
    """
    """
    # Rigid body description (defines its physics, not its appearance).
    body = demolib.getRigidBody(cshapes=cshapes)

    # Define the object template (physics and appearance) and upload to Azrael.
    template = Template('BlenderObject', body, frags, {}, {})
    ret = client.addTemplates([template])

    # Spawn one instance.
    templates = [{'templateID': 'BlenderObject', 'rbs': {'position': [0, 0, 0]}}]
    ret = client.spawn(templates)
    assert ret.ok
    objID = ret.data[0]
Пример #10
0
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)
Пример #11
0
def createTemplate():
    # Create the vertices for a unit cube.
    vert = defineCube()

    # Define initial fragment size and position relative to rigid body.
    scale = 1
    pos, rot = (0, 0, 0), (0, 0, 0, 1)

    # Define the one and only geometry fragment for this template.
    data_raw = FragRaw(vert, [], [])
    frags = {
        'body': FragMeta('raw', scale, pos, rot, data_raw),
        'satellite': FragMeta('raw', scale, pos, rot, data_raw),
    }
    del scale, pos, rot

    # We will need that collision shape to construct the rigid body below.
    cs_sphere = CollShapeMeta(cstype='Sphere',
                              position=(0, 0, 0),
                              rotation=(0, 0, 0, 1),
                              csdata=CollShapeSphere(radius=1))

    # Create the rigid body.
    body = RigidBodyData(scale=1,
                         imass=1,
                         restitution=0.9,
                         rotation=(0, 0, 0, 1),
                         position=(0, 0, 0),
                         velocityLin=(0, 0, 0),
                         velocityRot=(0, 0, 0),
                         cshapes={'foo_sphere': cs_sphere},
                         linFactor=(1, 1, 1),
                         rotFactor=(1, 1, 1),
                         version=0)

    # Define a booster
    booster = Booster(
        position=[0, 0, 0],  # Booster is located here and...
        direction=[1, 0, 0],  # points in this direction.
        force=0  # Initial force.
    )
    boosters = {'booster_foo': booster}

    return Template('my_first_template', body, frags, boosters, {})
Пример #12
0
def spawn_city():
    # Add the template to Azrael.
    print('  Adding city template... ', end='', flush=True)
    tID = 'city'

    # Collision shape for city.
    cs = aztypes.CollShapeBox(1, 1, 1)
    cs = aztypes.CollShapeMeta('box', (0, 0, 0), (0, 0, 0, 1), cs)
    body = demolib.getRigidBody(cshapes={'0': cs}, position=(0, 0, 0))

    # Load the Manhatten geometry.
    FM3JS = demolib.getFragMeta3JS
    frags = {'main': FM3JS(['manhatten.json'], scale=0.1, pos=(0, -10, 0))}

    # Get a Clerk instance. Then upload the template to Azrael.
    client = azrael.clerk.Clerk()
    assert client.addTemplates([Template(tID, body, frags, {}, {})]).ok
    del cs, body, frags
    print('done')

    # Spawn the template near the center.
    print('  Spawning city... ', end='', flush=True)
    pos, orient = [0, 0, -20], [0, 1, 0, 0]
    new_obj = {
        'templateID': tID,
        'rbs': {
            'scale': 1,
            'imass': 0.1,
            'position': pos,
            'rotation': orient,
            'linFactor': [1, 1, 1],
            'rotFactor': [1, 1, 1]}
    }
    ret = client.spawn([new_obj])
    assert ret.ok

    # Status message. Then return the object ID of the model.
    print('done (ID=<{}>)'.format(ret.data[0]))
    return ret.data[0]
Пример #13
0
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
Пример #14
0
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
Пример #15
0
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))