Adding movement

Let's not move the cube in the scene.

Rin uses a data oriented paradigm called Entity Component System (ECS). Understanding this model is relatively simple but a bit different to the way you might be used to do things if you come from object oriented programming or other paradigms.

As it's obvious from its name, an ECS application is composed by entities, components and systems.

When in the previous example we added the cube, the ground or the lights, those are entities. Entities, really are nothing but an id, a number that identifies each of this elements. When we added the cube in the first example we could have stored is entity:

let cube = scene.add_model("Cube")
    .geometry(cube)
    .material(material)
    .transformation(pnt3!(0., 0.5, 0.))
    .build();

The variable cube is the entity, the id that represents the cube we just added. It doesn't have any methods or any data. We can't do cube.materialor anything similar cause the entity doesn't really contain the material or the geometry or even the transformation.

Instead that data is stored as components, the C in ECS. Each entity has associated components. In the case of our cube, it has a geometry, a material and a transformation. Those components live in separate storages and can be accessed independently as we'll see next.

Adding custom components

So let's see how to move our cube. As we've said before entities components can be accessed independently which means that usually we won't be moving our cube alone but everything that has a certain set of types of data which means that those entities have to be moved. For example in order to move something it usually will need to have a velocity and a position. So we can create two components, one for position and one for velocity and then move everything that has those two things.

We already have a position in the cube transformation. And indeed other entities like the ground also have a transformation. Since we only want to move the cube we'll only add a velocity to that entity and then create a system that moves everything that has a velocity and a transformation.

In order to add a velocity let's create a new component of type Velocity and add it to our cube.

At the beginning of our app.rs, right below all the imports, all the lines at the beginning that start with use ..., lets add a new struct of type Velocity with a Vec2 in it that will mean the three dimensional velocity an entity has:

use serde_derive::{Serialize, Deserialize};
use rin::ecs::Component;

#[derive(Component, Debug, Serialize, Deserialize)]
struct Velocity(Vec3);

Now we can use Velocity as a component. Let's add it to our cube. In the lines where we created the cube let's add a new one with it's velocity:

let cube = scene.add_model("Cube")
    .geometry(cube)
    .material(material)
    .transformation(pnt3!(0., 0.5, 0.))
    .add(Velocity(vec3(1., 0., 0.)))
    .build();

By itself, velocity doesn't do anything, we now need code that interprets this new data and moves any entity that has a velocity and a transformation. To do that we'll need the final part in ECS the S, systems.

Systems or how to add movement

First of all let's import some types we'll need. At the beginning of the file where we imported rin::ecs::Component let's change that line with:

use rin::ecs::{Component, Write, Read, system_foreach, Res};
use rin::scene::time::Clock;

And finally at the end of our app.rs file below the setup function let's add a new function:

#[system_foreach(name = "movement")]
fn movement(node: Write<Node>, velocity: Read<Velocity>, clock: Res<Clock>) {
    let delta = clock.game_time_delta().as_seconds();
    let translation = velocity.0 * delta as f32;
    node.translate(&translation);
}

This is a system that gets the time in seconds for the last frame, multiplies it by the object velocity and applies this as an increment to its position. This happens for every entity that has both a Node and a Velocity that entity might, and surely will, have more components but this system only cares about those two.

Now the system won't run by itself we need to tell the scene to run it every frame. In order to do that we'll add a new line anywhere in setup. Systems can be added to the scene builder or to the scene cause sometimes their creation need a full scene although most of the time the scene builder is fine so let's do that:

scene_builder.add_system(movement);

Now if we run our application it'll show the cube moving.