Kamujin's picture

OOGL

OOGL - Object Oriented OpenGL - Sourceforge

This project is a set of utilities that can be used with the OpenTK libraries to add object oriented wrappers to commonly used functionality.
It is done completely in C# and can be run on Windows, Linux, and OS X.

Currently implimented features are as follows.

* MilkShape file loader (including animations)
* MilkShape single animation track to multiple animation track splitting.
* Basic skeletal animation controller.
* Shader class (supports shader based skeletal animation)
* FrameBuffer class (used by GUI to render child windows to textures for higher framerates)
* Texture class. (simple OO wrapper)
* System.Windows.Forms like GUI framework.

The GUI framework provides basic implimentation of the follow controls.

* Button
* CheckBox
* ComboBox
* HScrollBar
* Label
* ListBox
* Picture
* ProgressBar
* TextBox
* VScrollBar


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
tetdai's picture

Hello,

I would like to try this out. Got the code.

When I run it, a file referenced in Game.cs is missing:

Track[] tracks = Track.Load(ResourceLocator.GetFullPath("/Models/Beta_Kamujin/Beta_Kamujin.tracks"));

Please post the content of the .tracks file.

Thanks

Kamujin's picture

Thanks for pointing that out. I have added it to the SVN. Please update your working copy and it should be fixed.

Currently F3 and F4 play the 2 tracks listed in the file.

Inertia's picture

Good idea to separate this from OpenTK itself, here's something you might find useful. It's an implementation of the Tipsy vertex cache optimization described by Sander et al. The implementation is not perfect, it assumes that you have only 1 mesh in your model, as in exactly 1 VBO and 1 IBO. It will also work best if your modeling technique produces alot of triangle fans, since the algorithm takes the center of a fan and issues all triangles connected with it.

The algorithm expects a boolean flag HasBeenProcessed for each triangle. A timestamp and an Array of all triangles referencing it, in each vertex. The structs look like this:

public struct Triangle
    {
        private ushort[] Indicies; // pointer to 3 vertices
 
        public ushort First { get { return Indicies[0]; } }
        public ushort Second { get { return Indicies[1]; } }
        public ushort Third { get { return Indicies[2]; } }
 
        public bool HasBeenProcessed; // (vertex cache opt) true if the triangle has been added to the list already.
 
        public Triangle( ushort first, ushort second, ushort third )
        {
            if ( first == second || first == third || second == third )
                throw new ApplicationException( "Degenerate Triangle found. A: " + first + " B: " + second + " C: " + third );
 
            Indicies = new ushort[3];
            Indicies[0] = first;
            Indicies[1] = second;
            Indicies[2] = third;
 
            HasBeenProcessed = false;
        }
    }
 
 public struct Vertex
    {
        public Vector3 Position; 
        public Vector3 Normal; 
        public Vector2 UV; 
        public Bytes4 BoneIndex; // index of joint or 255, if 255 then that weight is ignored
        public Bytes4 BoneWeights; // vertex weight ranging from 0 - 100, last weight is computed by 1.0 - sum(all weights)
 
        public int CacheTimestamp; // (vertex cache opt) used to track how long ago the Vertex has been processed
        public ArrayList ReferencedTriangles; // (vertex cache opt) contains a list of all triangles using this vertex
        public byte TriangleCount; // (vertex cache opt) number of triangles left in ReferencedTriangles
 
        public Vertex( Vector3 position, Vector2 uv, Vector3 normal, byte[] boneids, byte[] weight )
        {
// truncated ....
            CacheTimestamp = -1;
            ReferencedTriangles = null;
            TriangleCount = 0;
        }
    }

The actual function that does the optimization:

public static void Optimize( ref Vertex[] VertexList, ref Triangle[] TriList, int CacheSize )
        {
            // Iterate through all Vertices, build a list of triangles using it
            for ( int currentVertex = 0; currentVertex < VertexList.Length; currentVertex++ )
            {
                VertexList[currentVertex].ReferencedTriangles = new ArrayList( );
                // iterate through the mesh and check if any triangle uses the currently examined vertex
                for ( int currentTriangle = 0; currentTriangle < TriList.Length; currentTriangle++ )
                {
                    if ( TriList[currentTriangle].First == currentVertex ||
                         TriList[currentTriangle].Second == currentVertex ||
                         TriList[currentTriangle].Third == currentVertex )
                    {  // if any of the indices is the currently examined vertex, remember this triangle 
                        VertexList[currentVertex].ReferencedTriangles.Add( currentTriangle );
                        VertexList[currentVertex].TriangleCount++;
                    }
                }
            }
 
            // implementation of the Tipsy algorythm
            Stack DeadEndStack = new Stack( ); // Dead-end vertex stack
            ArrayList NextCandidates = new ArrayList( ); // 1-ring of next candidates
            Queue Output = new Queue( ); // Empty output buffer
            int currentIndex = 0; // Arbitrary starting vertex
            int Stamp = CacheSize + 1; // time stamp
            int Cursor = 1; // cursor
            while ( currentIndex >= 0 ) // For all valid fanning vertices
            {
                NextCandidates.Clear( );
                foreach ( int tri in VertexList[currentIndex].ReferencedTriangles )
                {
                    if ( !TriList[tri].HasBeenProcessed ) // make sure no triangle is queued more than once
                        foreach ( ushort Index in TriList[tri].Indicies )
                        {
                            Output.Enqueue( Index );// Output vertex 
                            DeadEndStack.Push( Index ); // Add to dead-end stack
                            NextCandidates.Add( Index ); // Register as candidate
                            VertexList[Index].TriangleCount--; // Decrease live triangle count
                            if ( Stamp - VertexList[Index].CacheTimestamp > CacheSize ) // If not in cache
                            {
                                VertexList[Index].CacheTimestamp = Stamp; // Set time stamp
                                Stamp++; // Increment time stamp
                            }
                        }
                    TriList[tri].HasBeenProcessed = true;// Flag triangle as emitted
 
                }
                //Select next fanning vertex
                int m = 0;
                ;
                int n = -1;
                int p = -1; // Best candidate and priority
                foreach ( ushort Index in NextCandidates )
                {
                    if ( VertexList[Index].TriangleCount > 0 ) // Must have live triangles
                    {
                        p = 0; // Initial priority
                        if ( Stamp - VertexList[Index].CacheTimestamp + 2 * VertexList[Index].TriangleCount <= CacheSize ) // In cache even after fanning?
                        {
                            p = Stamp - VertexList[Index].CacheTimestamp; // Priority is position in cache
                        }
                        if ( p > m ) // Keep best candidate
                        {
                            m = p;
                            n = Index;
                        }
                    }
                }
                if ( n == -1 ) // Reached a dead-end?
                {
                    n = SkipDeadEnd( ref VertexList, DeadEndStack, Cursor ); // Get non-local vertex
                }
                currentIndex = n;
            }
 
            Debug.Assert( Output.Count % 3 == 0 ); // must be a multiple of 3, a triangle has 3 verts
            Triangle[] temptrilist = new Triangle[(int) ( Output.Count / 3 )];
            for ( ushort i = 0; i < temptrilist.Length; i++ )
            {
                ushort first = (ushort) Output.Dequeue( );
                ushort second = (ushort) Output.Dequeue( );
                ushort third = (ushort) Output.Dequeue( );
                temptrilist[i] = new Triangle( first, second, third );
            }
 
            TriList = temptrilist;
 
            // check if the optimizer "forgot" any triangles and clear the reference lists for each vertex
            for ( ushort i = 0; i < VertexList.Length; i++ )
                if ( VertexList[i].TriangleCount != 0 )
                {
                    if ( Verbose != eVerbose.Silence )
                        Log( "Warning: Not all triangles referencing Vertex (" + i + ") have been processed." );
                    VertexList[i].ReferencedTriangles.Clear( );
                }
 
            // clean up
            Output.Clear( );
            DeadEndStack.Clear( );
            NextCandidates.Clear( );
        }
 
        private static int SkipDeadEnd( ref Vertex[] VertexList, Stack MyStack, int Index )
        {
            while ( MyStack.Count > 0 ) // Next in dead-end stack
            {
                ushort d = (ushort) MyStack.Pop( );
                if ( VertexList[d].TriangleCount > 0 ) // Check for live triangles
                    return d;
            }
            while ( Index < VertexList.Length ) // Next in input order
            {
                Index++; // Cursor sweeps list only once
                if ( Index >= VertexList.Length )
                    return -1;
                if ( VertexList[Index].TriangleCount > 0 ) // Check for live triangles
                    return Index;
            }
            return -1; // We are done!
        }
    }

This code was written a while ago for XNA, ported to Tao and then to OpenTK, and obviously has some flaws that could be improved e.g. ArrayList{Triangle} and passing more by ref. Feel free to use/alter it in any way you like, but keep in mind that the approach was taken from http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf and you will most likely not be able to apply licenses/fees to it without checking with the authors first.

Hope this helps.

Kamujin's picture

Thanks, I will certainly consider this method when I get to the optimization phase.

Entropy's picture

Does OOGL use the latest release (0.9.1) or OpenTK?

I can't build the source code, as there's no Font.MeasureText method in the OpenTK or OpenTK.Utilities dlls.

I actually modified the code to create a RectangleF using the MeasureString method, but it seemed I was missing a GameWindow.RunSumple method as well.

I'd like to build this, as I'm very interesed in the MS3D loader, and seeing how it works.

Kamujin's picture

Oh, well that's just plain clumsy of me. I'll square this away asap.

I am using the OpenTK from the SVN, I'll make sure to post the revision in the README from now on.

Until then, you can safely replace the GameWindow.RunSimple with GameWindow.Run method.

For what its worth, my code for RunSimple can be found here http://www.opentk.com/node/488.
It's not required though.

Can you give me some feedback on how it works for you?

Entropy's picture

Yeah I had a go at replacing the SimpleRun(50) call with Run(50) after posting, but now I'm getting a shader compiling error in the AbstractShader class when loading skeletalAnimation.vs:

Shader error: Vertex shader failed to compile with the following errors:
ERROR: 0:15: ')' : syntax error parse error
ERROR: 1 compilation errors. No code generated.

Shaders are still a little mysterious to me, so I don't have much hope of ironing that one out. :-)

Once I've got it running, I'll have a play around with it, but it looks like I could really use some of the stuff you've done here in my current project (which is perhaps still a little young to advertise on this board).

Kamujin's picture

That looks like its complaining about this line.

int bone = (int) boneAttribute;

I assume its complaining about casting the float to an int.

What card are you using? Maybe I am leaning on something nVidia specific in my shader.

Entropy's picture

It's a Radeon X1950.

I changed the typecasting syntax to:

int bone = int(boneAttribute)

The compiler seems to accept this, as I now get an error message for line 17:

mat3 normalAnimation = mat3(vertexAnimation);

Shader error: Vertex shader failed to compile with the following errors:
ERROR: 0:17: '' : Matrix construction from matrix is not allowed in GLSL1.10
ERROR: compilation errors. No code generated.

I've no idea what version of GLSL I'm lacking, but I updated my graphic drivers after getting this and it still won't compile. It's tempting to fiddle around and find another way of implementing the same line in a way which the compiler will accept, but there's clearly an underlying compatibility issue at stake here.

Kamujin's picture

Yeah, I wrote those shaders to GLSL 1.2.

Can you try?

// Skeletal Animation Vertex Shader
 
const int maxBones = 40;
 
attribute float boneAttribute;
 
uniform vec3 lightPos0;
uniform mat4 vertexTransformations[maxBones];
 
varying vec3 normal, light, halfVector;
 
void main(void)
{
	// Animation
	int bone = int(boneAttribute);
	mat4 vertexAnimation = (bone >= 0 && bone < maxBones) ? vertexTransformations[bone] : mat4(1.0);
//	mat3 normalAnimation = mat3(vertexAnimation);
 
	mat3 normalAnimation;
	normalAnimation[0][0] = vertexAnimation[0][0];
	normalAnimation[1][0] = vertexAnimation[1][0];
	normalAnimation[2][0] = vertexAnimation[2][0];
 
	normalAnimation[0][1] = vertexAnimation[0][1];
	normalAnimation[1][1] = vertexAnimation[1][1];
	normalAnimation[2][1] = vertexAnimation[2][1];
 
	normalAnimation[0][2] = vertexAnimation[0][2];
	normalAnimation[1][2] = vertexAnimation[1][2];
	normalAnimation[2][2] = vertexAnimation[2][2];
 
	// lighting
	vec4 viewVertex = gl_ModelViewMatrix * vertexAnimation * gl_Vertex;
 
	light = normalize(gl_LightSource[0].position.xyz - viewVertex.xyz);
	normal = normalize(gl_NormalMatrix * normalAnimation * gl_Normal);
	halfVector = normalize(gl_LightSource[0].halfVector.xyz);
 
	// position	
	gl_Position = gl_ModelViewProjectionMatrix * vertexAnimation * gl_Vertex;
	gl_TexCoord[0] = gl_MultiTexCoord0;	
}