Dynamic Model Creation and Animation tutorial

You can dynamically create new models inside Luxinia and even animate them. This tutorial shows with a simple example how to create such a model and how to use it. Another possibility is to use the new rendermesh class, or "usermesh" in l3dprimitive/l2dimage, which also create rendermeshes.

Models / L3dprimitives

Models can be either loaded or created. As we want to create a model in this case by ourselves, we create such a model:

-- all resource create functions behave similar as to load:
-- they first look for the name identifier being loaded already,
-- and return it when found, or they execute the creation process

-- modelname and number of quads
name,n = "test",32 -- the modelname must be unique!
-- we want to have colors and normals
mdl = model.create(name,vertextype.vertex32normals(),1,0)

However, note that you could also create a usermesh, using |l3dprimitive.usermesh. It can be used in a very similar way like creating the model. The difference is, that a usermesh should be used for unique purposes, while models are added to the resource chunk and adds some overhead. However, models can be used for more than just one l3dmodel.

As the comment says, the model name should be unique, as it is stored in the resource chunk and can be found again by using the same name again.

We also initialize a variable named n, which is simply the resolution of our mesh. The higher the number, the more vertices and vice-versa.

Each model can contain a number of meshes. Meshes are actually containing the information how things are drawn, while models do only tell which meshes form one model.

mesh = mdl:meshid(0) -- retrieve the mesh we work with

-- initialize the mesh with the number of required vertices
mesh:indexPrimitivetype(8) -- make it a quadstrip
-- in v0.98 instead of 8 use primitivetype.quadstrip()

mesh:vertexCount(n*2) -- set the vertexcount
mesh:indexCount(n*2) -- set the indexcount

The initialization of the mesh is important as it allocates all the date we need. There are lot's of different ways how the final triangles are drawn on the screen. The less data is required, the more efficient is the drawing process.

To understand this, let's explain the basics of how the data is stored.

Each mesh consists of indices and vertices. The vertices are vectors in space that also may describe additional parameters like a normal or a color value for that vertice. The indices tell us, in which order the vertices are connected and how to form either triangles, quads or polygons:

The moon picture above shows such a combination of indices and vertices (yes, it is just like these drawing pictures by connecting numbered dots as we did it as kids).

We can describe that moon either as a polygon (which would not be working well as it is not convex) or, as the blue line shows, as a triangle strip.

The figure shows 3 different meshtypes: Triangle strips (a), plain triangles (b) or triangle fans (c).

The indexlists would look like this (I am counting here from 1-n, the meshdata is adressing the data with 0-(n-1)):

9 5*9*
10 410*
11 611*
12 5*12*
13 513*
14 614*
14 7*15*
15 616*
16 8 
17 7* 
* denotes when a triangle was created

The value in the index is pointing to the index of the vertice that is used for the index. So the index values 1-2-3 define that vertice 1, vertice 2 and vertice 3 should be used to form a triangle. As the table shows, the trianglestrip requires much less indexes to form a line of triangles, because with each new index value, a new triangle is created with the former 2 indexes, while if the indexlist describes plain triangles, 3 indexes are always required to describe a new triangle. Triangle fans are similiar like the triangle strip.

So choosing the right type for your model is important if you want to draw it more efficient.

In our case, we are using a quadstrip, which is just working the same way as the tristrip.

Backfaces and frontfaces

In order to avoid drawing triangles that are not important at all, like triangles that are facing away (statistically, we only see about 50% of all triangles because we can see only 50% of an object while the backside is hidden), the graphics cards are culling the triangles.

This is done in a very simple way: We simply define that if we draw the triangle clockwise, it is visible, if it is drawn anticlockwise, it is culled. If we turn a triangle, it is automatically changing the way it is drawn, so by looking at the order of the drawing (which can be done very quickly), we can see if the triangle is to be drawn or not.

So it is important to define the triangle orientation in order to see the object culled correctly. Drawing a mesh that is not culled adds significant overhead, since twice as much pixels are to be drawn, where only half of it are visible.

The tube

Let's initialize now the mesh data for our tube:

for i=0,n-1 do -- initial tube mesh
    local angle = math.pi/(n-1)*i*2 -- current angle
    local y,z = math.sin(angle),math.cos(angle) 
    mesh:vertexPos(i*2, -1,y,z) -- setting the position
    mesh:vertexPos(i*2+1,1,y,z) -- ... for the front point

    mesh:vertexColor(i*2,1,0,0,1) -- set the colors
    mesh:vertexColor(i*2+1,1,1,0,1) -- ...

    mesh:vertexNormal(i*2,0,y,z) -- and now the normal very
    mesh:vertexNormal(i*2+1,0,y,z) -- easy for a unitsized tube

We just calculate a circle and set the vertice positions, normals and colors.

As it is a quadstrip and our tube is just going round the clock, we can initialize the indexlist in a simple for loop:

for i=0,n*2-1 do
    mesh:indexValue(i,i) -- tell how the quadstrip runs
     -- over our vertices (really easy for such a quadstrip

Finally, we have to update some information, telling Luxinia that the model is ready to be drawn:

mesh:indexMinmax() -- let the application determine the 
 -- used vertices

mdl:createfinish (false,true,true) -- let's finalize the model
 -- the additional parameters allow us to modify it dynamically
mdl:updatebbox () -- updating the axisaligned boundingbox

Now we only have to make it visible:

actor = actornode.new("",0,0,0) -- create an actor so we 
l3d = l3dmodel.new("",mdl) -- ... can attach an model to it
l3d:rfLitSun(true) -- let it be lit 

So we just use it like any other model.

In order to animate it as shown in the youtube video, we simply create a timer that modifies the vertexpositions:

local t=0
        t = t + .01
        for i=0,n-1 do
            local angle = math.pi/(n-1)*i*2+t
            local y,z = math.sin(angle),math.cos(angle)
            mesh:vertexPos(i*2, -1,y,z)
            mesh:vertexNormal(i*2, -1,y,z)

So the timer variable t is increasing and turns our vertice position so that it runs round the clock.

In order to add some interactive elements like switching the light on or off, I have added a GUI element and a few keybindings, which are described in the tutorial package files.

Problems and Issues

  1. Call "indexMinmax()" or set minimum and maximum index used manually, at the end of your mesh initialization. Having correct min and max values is crucial to drawing. You might see nothing at all, if you forgot this.

  2. Don't forget to set "indexCount", "vertexCount". When creating meshes you pass counts that define total storage space, not the counts that are used.

  3. Set "indexPrimitiveType", at best at beginning, else your mesh might be drawn as points or not at all.

  4. When dealing with usermeshes (l3dprimitive) be aware that the visibility culling box will use the dimension of the original primitive. So if you know your geometry fits into certain dimensions pass those at beginning. However for the original mesh this also serves as "scaling" value. So once you set a usermesh make sure to set ":renderscale(1,1,1)", if you dont want your vertices to be scaled. That will cause no scaling done to the usermesh, but keep the original size for visual bounding box. From 0.98 on you can change bbox size with ":size" afterwards again, in older versions this will not affect the bbox.

  5. Geometry becoming invisible, eventhough l3dprimitive.size is correct. This can happen when you change the bbox size after ":linkinterface(..)" was done. As linkinterface merges the current bbox with its own, it will not detect changes to l3d's bbox afterwards. If you require this change the spatialnodes bbox directly with ":vistestbbox(...)"

  6. Wrong face winding, primitives must be specified in certain order, that defines what is front and backfacing. If you see no geometry try ":rfNocull(true)" to check whether you accidently used the wrong winding.