Sunday, October 17, 2004

How to render the Flag

Croquet uses OpenGL so rendering the TTTapestry is just a series of OpenGL calls. You can find this code in the recent Croquet updates as well.

The basic idea is the masses are objects that include their position in space, the normal that is computed from their neighbors, the texture u,v location which are all used in rendering, and the summation of the forces from the springs, gravity, and wind. The mass position is an obviously useful thing to have around. This is used both to render it here and to determine how much the TSpring is stretched to determine it's restoring force.

The normal is used by both the wind vector to determine how much of the face of the flag is facing the wind. That is, a piece of cloth that is perpendicular to the wind feels a strong force, where one that is parallel doesn't feel much at all. It is also used in lighting the flag, as the amount of light at this location is determined in much the same as the amount of force from the wind is.

The texture u,v coordinates are usually a number between 0.0 and 1.0 and determine what part of the flag's texture corresponds to this mass location. The texture is then stretched between the other mass locations and this one.

This method uses a triangle strip method. It is actually kind of a zig-zag like this:

1--------2
--------
------
---
3--------4
--------
------
---
5--------6

...


We have to make a number of top to bottom strips like this to complete the flag, so it looks a bit more like this:


1--------2--------n
-------- --------
------ ------
--- ---
3--------4--------n+1
-------- --------
------ ------
--- ---
5--------6--------n+2


TTapestry>>#render: ogl
"We always pass in the OpenGL object when we render"
| index m |

ogl glDisable: GLCullFace. "we want to render both sides"
texture ifNotNil: [ texture enable: ogl.]."activate the texture"
1 to: xsize-1 do:[:i | "this is left to right"
wire ifFalse:[ ogl glBegin: GLTriangleStrip.] " if this is wire frame"
ifTrue:[ ogl glBegin: GLLineStrip.]. "or if it isn't"
1 to: ysize do:[:j | "draw the strips vertically"
index _ (i-1*ysize)+j. "location of the mass in the big array"
m _ masses at: index.
ogl glNormal3fv: m normal. "computed from nearby masses"
ogl glTexCoord2f: m uv x with: m uv y. "texture coordinates"
ogl glVertex3fv: m location. "continue line at 3D location of mass"
index _ index +ysize. "index of the mass to our right"
m _ masses at: index.
ogl glNormal3fv: m normal.
ogl glTexCoord2f: m uv x with: m uv y.
ogl glVertex3fv: m location.

].
ogl glEnd. "stop rendering the triangle strip"
].

"If we are rendering in wire frame, clean up the edges, otherwise we have
a zig-zag like shape."
wire ifTrue:[
1 to: xsize do:[:j |
ogl glBegin: GLLineStrip.
1 to: ysize do:[:i |
ogl glVertex3fv: (masses at:(j-1*ysize)+i) location.].
ogl glEnd.
].
].
texture ifNotNil:[texture disable: ogl.].
"
This was test code to render the springs to see if these make any sense...
they do now.

springs do:[:s |
ogl glBegin: GLLineStrip.
ogl glVertex3fv: s mass1 location+(0@2@0.3).
ogl glVertex3fv: s mass2 location+(0@2@0.3).
ogl glEnd.
].
"
"
This was test code to display all of the normals on the flag.

masses do:[:ms |
ogl glBegin: GLLineStrip.
ogl glVertex3fv: ms location.
ogl glVertex3fv: ms location - ms normal.
ogl glEnd.
].

"
"return OpenGL back to it's standard approach of culling back faces"
ogl setCull.

2 comments:

Matthew Corey Brown said...

It ocurred to me that you recreate the mesh on each update, instead of just moving the vertices. I realize there is probly little optimzation to be had here, but thats not what concerns me.

Is it possbile to have the masses check to see if it passes through another part of the mesh and to prevent it? To prevent the TTapestry from having a piece if it go through itslef, and later other objects?

I changed the mass of elements 1 and 150 so it looks like its coming off a flag pole and it does, with the ocasional self intersection.

I'm not familar with OpenGL, my 3D programing experience is with non realtime systems like Povray.However in you render code you tell it not to cull backsides, so the OGL has a concept of front side and backsides. So could we some how get the masses to reference that mesh and not move through the mesh.

This could open up easier collision detection within the world itself.

Of course, failing that we could create a triagle object that interfaces with physics SDK.

(So much to play with, so little time to do everything)

Croqueteer said...

Computing self intersection like this is a non-trivial task. Each point mass needs to be checked against the surface defined by the rest of the point masses. In particular, one needs to test if the edges defined by the masses ever intersect with the triangles. So if we have n masses and about 2*n triangles we need to test each of the masses edges, which are about 3, because they are shared, so a worst case is:
3*n*2*n tests. Of course, a few of these can be immediately thrown away - the triangles that are next to the mass, which is about 8 of them, hence:

3*n*(2*n - 8)

So if n is 100, we have somewhat fewere than 60000 tests to perform.

This is a worst case. It is a very interesting problem to work on.