Models
======

The full source code for this part of the tutorial is available in the file
`root/tutorials/s3d/model.bs`. You can run it using `storm -f tutorials.s3d.model`. As in the
previous parts, the code is fairly well-documented, and is likely possible to read on its own.

As before, this tutorial focuses on the differences from the [first tutorial](md:Simple_Rendering).
If you started working on your own previously, you can resume from the code you wrote as a part of
that tutorial. Note, however, that the painter, the pipeline, and the shaders are renamed from
`Simple...` to `Model...` to avoid name clashes in the tutorials package.


The Model Class
---------------

The center piece of this tutorial is the [stormname:ui.s3d.Model] class, and how it interacts with
the rest of the rendering system. At its core, the [stormname:ui.s3d.Model] class is fairly simple.
It is conceptually a class that contains a set of vertices and a set of indices as follows:

```bs
class Model {
    // Vertex buffer.
    VertexBuffer<Vertex> vertices;

    // Index buffer.
    IndexBuffer indices;

    // Additional members...
}
```

What makes this useful is that it defines a standard interface which different systems can use to
exchange geometry between each other. As a part of this, the `Model` class also defines its own
vertex type: [stormname:ui.s3d.Vertex], which can be used in rendering. It has the following members.

```bsfragment:SFile:use=ui.s3d
vertex Vertex {
    // Vertex position.
    Vector pos;

    // Vertex normal. Assumed to be normalized to length 1.
    Vector normal;

    // Vertex color.
    Color color;

    // Texture coordinate. Each coordinate is normally between 0 and 1.
    Point tex;
}
```

Apart from defining a standard data layout, the `Model` class also provides a number of utilities to
manipulate geometry. For example, loading `.obj` files, creating simple geometric shapes, and
manipulating them. We will see a few of them in the remainder of this tutorial.


Adjusting the Pipeline
----------------------

Since we will represent the geometry using the [stormname:ui.s3d.Vertex] type, we will once again
have to adapt our pipeline accordingly. In this case, we will define the pipeline and the
intermediate vertex type like below. Note that `ModelIntermediate` is the same as `SimpleIntermediate`.

```bsfragment:SFile:use=ui.s3d
vertex ModelIntermediate {
    // Position.
    Vector4 pos : position, perspective;

    // Color.
    Color color : perspective;
}

pipeline ModelPipeline {
    // Note that the source type refers to ui:s3d:Vertex.
    data (Vertex vertex) -> ModelIntermediate -> Output;

    // Camera transform.
    Transform camera;
}
```

We will also have to modify our shaders slightly from the shaders used in the first tutorial, mainly
by adjusting the name of the input in the vertex shader.

```bsfragment:SFile:use=ui.s3d
vertex shader for ModelPipeline: ModelIntermediate modelVertexShader(Vertex point) {
    ModelIntermediate(Vector4(point.pos) * camera, point.color);
}

pixel shader for ModelPipeline: Output modelPixelShader(ModelIntermediate point) {
    Output(point.color);
}
```


Rendering Models
----------------

Now that we have adapted the pipeline, we can turn our attention to our painter. We will modify it
to render a `Model`. As such, our first order of business is to replace the vertex buffer and index
buffer with a `Model`. The `Model` class contains a vertex buffer and an index buffer after all. As
a starting point, we will just create a simple cube using the `Model:cube` static member.

At this time, our painter will look like below:

```bs
class ModelPainter extends SamplePainter3D {
    // The pipeline.
    ModelPipeline pipeline;

    // Model to render.
    Model model;

    // Transform for the camera position.
    Transform cameraPos;

    // Create.
    init() {
        init {
            // Create the pipeline and attach shaders.
            pipeline = ModelPipeline:create(modelVertexShader, modelPixelShader);

            // Create the camera transform.
            cameraPos = rotateX(-35 deg) * translate(Vector(0, 0, 8));

            // Create a default model.
            model = Model:cube(2);
        }
    }

    // Render.
    Bool render(Size size, Graphics3D g) : override {
        Float time = animationTime(5 s);

        g.clearDepth();
        g.fill(black);

        pipeline.camera = rotateY(360 deg * time) * cameraPos * perspectiveProject(100 deg, size.aspect, 0.1, 100);
        pipeline.render(g, model.indices, model.vertices);

        true;
    }
}
```

The code above is not too different from what we have seen so far, at least as long as we keep in
mind that the model is just a vertex buffer and an index buffer wrapped in a class to make them
easier to manipulate. However, since the `Model` class is a part of the 3D rendering package, it
does receive some special treatment from other parts of the system. For example, we can simplify the
call to `render` by just passing the `model` as a whole unit:

```bsstmt
pipeline.render(g, model);
```

At this time, you can run the example to see a rotating white cube.



Manipulating Models
-------------------

Now it is time to explore the capabilities provided by the `Model` class. As a starting point, we
can easily set the color of all vertices in a model using the `color` assign function. For example,
we can make the cube red by adding the following line to the end of the constructor:

```bsstmt
model.color = red;
```


Perhaps more interesting is the fact that the 3D library provides a simple loader for `.obj` files,
to make it easier to implement more complex 3D geometry. For example, we can import a version of the
Utah teapot by replacing the initialization of `model` with the following line:

```bsstmt
model = loadOBJ(resUrl("teapot.obj"));
```

Due to the name resolution rules in Basic Storm, this can also be written as follows:

```bsstmt
model = resUrl("teapot.obj").loadOBJ();
```

As in the [texture tutorial](md:Textures), the `resUrl` function is a helper function that finds the
file `teapot.obj` in any of the `res/` directories for us. We can then pass the resulting
[stormname:core.io.Url] to the `loadOBJ` function to load the file data (there is also an overload
that accepts an [stormname:core.io.IStream] if that is more convenient).

If we run the program at this point, we will see that the cube is replaced with a rotating teapot!


The `Model` class also allows concatenating and transforming the geometry contained within. For
example, we can add a translated green cube to the same model as the teapot as follows. Another
option would, of course, be to have two `Model` instances and render them separately. However, GPUs
tend to like working in batches, so few calls to `render` with large amounts of geometry tend to
give better performance compared to many calls with less geometry.

```bsstmt
model.append(Model:cube(2).withColor(green).transform(translate(Vector(5, 0, 0))));
```

As you can see, the API makes it quite easy to nest things nicely. Let's walk through what happens
step by step.

1. First, we create a cube with sides of 2 units using `Model:cube(2)`. As we saw before, this
   creates a `Model` instance.

2. After that, we call `withColor(green)` on the newly created model. As with `model.color = green`,
   this changes the color of all vertices in the model. The difference is that `withColor` returns
   the original model, so that it works a bit nicer in pipelines.

3. After that, we call `.transform(translate(Vector(5, 0, 0)))`. The expression
   `translate(Vector(5, 0, 0))` creates a [stormname:core.geometry.Transform] that represents a
   translation in the X direction. The `transform` member of `Model` then applies the transform
   to all vertices (in particular, to `pos` and `normal`).

4. Finally, the translated model is appended to the teapot model using `.append`.

Similarly, we can also add a sphere to balance the geometry a bit:

```bsstmt
model.append(Model:sphere(2, 30, 30).withColor(blue).transform(translate(Vector(-5, 0, 0))));
```

At this point, we see a red teapot, a green cube, and a blue sphere in a line, all rotating on the
screen.
