# 2D Vertex Skinning

This example is an extension to this tutorial, supporting vertex skinning on the CPU.
http://www.opentk.com/node/3597

It took lot of time to figure out how it works, but I am happy that it did. :) I have started learning more about character animation and vertex skinning, so until I figure out how things work and gain knowledge, I will have to implement everything -almost- from scratch. It will take some more weeks, but I have plans to make a fully featured character animation example.

```using System;
using OpenTK;
using OpenTK.Graphics.OpenGL;

namespace OpenTKCPUVertexSkinning
{
class Bone
{
public string Name;
public Vector3 LocalPosition;
public Vector3 Position;
public float Angle;
public Bone Parent;
public Matrix4 Matrix;
}

class MainClass
{
public static void Main(string[] args)
{
#region Set the data of the skeleton
// Bones - coordinates set in local space
var bones = new Bone[4];
bones[0] = new Bone { Name = "root", LocalPosition = new Vector3(0, 0, 0) };
bones[1] = new Bone { Name = "body", LocalPosition = new Vector3(0, 2, 0) };
bones[2] = new Bone { Name = "neck", LocalPosition = new Vector3(0, 1, 0) };
bones[3] = new Bone { Name = "head", LocalPosition = new Vector3(0, 1, 0) };
bones[1].Parent = bones[0]; // "body" has "root" parent
bones[2].Parent = bones[1]; // "neck" has "body" parent
bones[3].Parent = bones[2]; // "head" has "neck" parent

// Vertices - coordinates are relative to the associated bone
var vertices = new Vector3[8];
vertices[0] = new Vector3(-0.2f, -2f, 0f); // body - bottom left
vertices[1] = new Vector3(0.2f, -2f, 0f); // body - bottom right
vertices[2] = new Vector3(-0.2f, -1f, 0f); // body - top left
vertices[3] = new Vector3(0.2f, -1f, 0f); // body - top right
vertices[4] = new Vector3(-0.2f, 0f, 0f); // neck - left
vertices[5] = new Vector3(0.2f, 0f, 0f); // neck - right
vertices[6] = new Vector3(-0.2f, 0f, 0f); // head - left
vertices[7] = new Vector3(0.2f, 0f, 0f); // head - right

// Array for storing the transformed vertices
var verticesTransformed = new Vector3[vertices.Length];

// Vertex and bone association (boneIndices[vertexIndex] = boneIndex)
var boneIndices = new int[vertices.Length];
boneIndices[0] = 1;
boneIndices[1] = 1; // body
boneIndices[2] = 2;
boneIndices[3] = 2; // neck
boneIndices[4] = 2;
boneIndices[5] = 2; // neck
boneIndices[6] = 3;

// Create vertex indices for line loop drawing
var drawIndices = new int[]
{
0,			// start
1, 3, 2, 	// right, up, left
3, 5, 4, 	// right, up, left
5, 7, 6, 	// right, up, left
6, 4, 2,	// down, down, down
0			// start
};
#endregion

// OpenTK application
var win = new GameWindow(800, 600);
Matrix4 modelview, projection;
var mvp = Matrix4.Identity;

// Temp variables for moving the skeleton
var skeletonSpeed = 0f;
var skeletonAngle = 0f;
var skeletonPosition = new Vector3();
var skeletonPosTarget = new Vector3();

win.Load += (object sender, EventArgs e) =>
{
modelview = Matrix4.LookAt(
new Vector3(0, 2, 12), new Vector3(0, 2, 0), Vector3.UnitY);
projection = Matrix4.CreatePerspectiveFieldOfView(
(float)Math.PI / 4, (float)win.Width/win.Height, 0.1f, 100f);
mvp = modelview * projection;
};

#region Build tuple array (Key, BoneIndex, ValueSign) bone angle input logic
var inputBones = new Tuple<OpenTK.Input.Key, int, float>[]
{
Tuple.Create(OpenTK.Input.Key.Z, 1, 1f), 	// body
Tuple.Create(OpenTK.Input.Key.C, 1, -1f),
Tuple.Create(OpenTK.Input.Key.A, 2, 1f), 	// neck
Tuple.Create(OpenTK.Input.Key.D, 2, -1f),
Tuple.Create(OpenTK.Input.Key.E, 3, -1f)
};
#endregion

#region Build tuple array (Key, Rot, Move) for skeleton movement input logic
var inputSkeleton = new Tuple<OpenTK.Input.Key, float, float>[]
{
Tuple.Create(OpenTK.Input.Key.Left, 1f, 0f),
Tuple.Create(OpenTK.Input.Key.Right, -1f, 0f),
Tuple.Create(OpenTK.Input.Key.Up, 0f, 1f),
Tuple.Create(OpenTK.Input.Key.Down, 0f, -1f)
};
#endregion

win.UpdateFrame += (object sender, FrameEventArgs e) =>
{
#region Check input for setting bone angles
foreach (var i in inputBones)
{
if (win.Keyboard[i.Item1])
bones[i.Item2].Angle += i.Item3 * (float)e.Time;
}
#endregion

#region Check input for setting the movement of the skeleton
skeletonSpeed = 0f;
foreach (var i in inputSkeleton)
{
if (win.Keyboard[i.Item1])
{
if (i.Item2 != 0) skeletonAngle += i.Item2 * (float)e.Time;
if (i.Item3 != 0) skeletonSpeed += i.Item3 * (float)e.Time;
}
}
#endregion

#region Calculate transformation of the skeleton
var quat = Quaternion.FromAxisAngle(Vector3.UnitZ, skeletonAngle);
skeletonPosition += Vector3.Transform(Vector3.UnitY * skeletonSpeed, quat);
skeletonPosTarget = skeletonPosition + Vector3.Transform((Vector3.UnitY * 1f), quat);
#endregion

#region Calculate bone transformations
for (var i = 0; i < bones.Length; i++)
{
var bone = bones[i];
float angle = 0f;

// Hack to affect the position of the root bone
if (i == 0)
{
bone.LocalPosition = skeletonPosition;
bone.Angle = skeletonAngle;
}

// Hack to affect the last bone based on the root bone
if (i == 3)
angle += skeletonAngle;

// Translate bone
var matrix = Matrix4.CreateTranslation(bone.LocalPosition);
if (bone.Parent != null)
{
angle += bone.Angle + bone.Parent.Angle;
if (bone.Parent.Parent != null)
angle += bone.Parent.Parent.Angle;

matrix *= Matrix4.CreateRotationZ(angle);
matrix *= Matrix4.CreateTranslation(bone.Parent.Position);
}

bone.Matrix = matrix;
bone.Position = matrix.Row3.Xyz;
}
#endregion
};

win.RenderFrame += (object sender, FrameEventArgs e) => {

// Transform the vertices
for (var i = 0; i < vertices.Length; i++)
{
var matrix2 = bones[boneIndices[i]].Matrix;
var vertex = vertices[i];
verticesTransformed[i] = Vector3.Transform(vertex, matrix2);
}

// Draw bones
GL.Color3(1f, 1f, 1f);
GL.Begin(PrimitiveType.Lines);
foreach (var i in bones)
{
if (i.Parent != null)
{
GL.Vertex3(i.Parent.Position);
GL.Vertex3(i.Position);
}
}
GL.End();

// Draw joints
GL.Color3(1f, 0f, 0f);
GL.PointSize(10f);
GL.Begin(PrimitiveType.Points);
foreach (var i in bones)
GL.Vertex3(i.Position);
GL.Vertex3(bones[1].Position);
GL.End();
GL.PointSize(1f);

// Draw edges of vertices
GL.Color3(0f, 1f, 0f);
GL.Begin(PrimitiveType.LineLoop);
for (var i = 0; i < drawIndices.Length; i++)
GL.Vertex3(verticesTransformed[drawIndices[i]]);
GL.End();

GL.Color3(0f, 0f, 1f);
GL.Begin(PrimitiveType.Lines);
GL.Vertex3(skeletonPosition);
GL.Vertex3(skeletonPosTarget);
GL.End();

win.SwapBuffers();
};

win.Run();
}
}
}```

Next steps for the tutorial will be:
a. Calculate vertex skinning on the GPU
b. Support for 4 weights per vertex which is very standard in game engines.
c. Convert example to 3D (e.g. human skeletal shape)
d. Support for animations.

P.S. If any of you happens to be experienced in such projects and can hack something, please respond so you can save me a dozen of time. :)

Inline Images