flopoloco's picture

Any Transform components for OpenTK?

Is there any good Transform components for OpenTK?
I try to create one of my own.

Currently I am testing things out and later they might be streamlined.

In this example I try to turn the turret but I come up with gimbal lock problem.

I don't know if I have to do something with the Quaternions or the calculation of the parent's matrix.

using System;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using Debug = System.Diagnostics.Debug;
 
namespace OpenTKParent
{
	class MainClass
	{
		class Transform
		{
			public Vector3 Position;
			public Quaternion Orientation;
 
			public Matrix4 Matrix;
			public Transform Parent;
 
			public Transform()
			{
				Orientation = Quaternion.Identity;
				Matrix = Matrix4.Identity;
			}
 
			public void CalculateMatrix()
			{
				if (Parent == null)
				{
					Matrix = 
						Matrix4.CreateFromQuaternion(Orientation) *
						Matrix4.CreateTranslation(Position);
				}
				else
				{
					// ???
//					var pos = Vector3.Transform(Position, Parent.Matrix);
//					var ori = Orientation * Parent.Orientation;
//					Matrix =
//						Matrix4.CreateFromQuaternion(ori) *
//						Matrix4.CreateTranslation(pos);
 
					// Gimbal lock occurs
					Matrix =
						Matrix4.CreateFromQuaternion(Orientation) *
						Matrix4.CreateTranslation(Position) *
						Parent.Matrix;
				}
 
			}
		}
 
		public static void Main(string[] args)
		{
			var window = new GameWindow();
			Matrix4 matrixModelview, matrixProjection, matrixMVP, matrixPush;
			matrixModelview = matrixProjection = matrixMVP = matrixPush = Matrix4.Identity;
 
			Transform partBody, partTurret, partCannon;
			partBody = new Transform();
			partTurret = new Transform();
			partCannon = new Transform();
 
			window.Load += (object sender, EventArgs e) => 
			{
				GL.Enable(EnableCap.DepthTest);
 
				// Setup transforms
				partBody.Position = Vector3.Zero;
				partTurret.Position = new Vector3(0, 1, 0); // Turret is one unit above
				partCannon.Position = new Vector3(0, 0, -2); // Cannon is two units front of turret
				partTurret.Parent = partBody;
				partCannon.Parent = partTurret;
			};
 
			window.UpdateFrame += (object sender, FrameEventArgs e) =>
			{
				var moveSpeed = 5 * (float)e.Time;
				var turnSpeed = 100 * (float)e.Time;
				var moveZ = 0f;
				var turnY = 0f;
				var turretX = 0f;
				var turretY = 0f;
 
				// Update partBody
				if (window.Keyboard[Key.W]) moveZ = moveSpeed;
				if (window.Keyboard[Key.S]) moveZ = -moveSpeed;
				if (window.Keyboard[Key.A]) turnY = turnSpeed;
				if (window.Keyboard[Key.D]) turnY = -turnSpeed;
				// ----
				partBody.Orientation *= Quaternion.FromAxisAngle(Vector3.UnitY, MathHelper.DegreesToRadians(turnY));
				var moveVector = Vector3.Transform(-Vector3.UnitZ, partBody.Orientation);
				partBody.Position += moveVector * moveZ;
				partBody.CalculateMatrix();
 
				// Update partTurret
				if (window.Keyboard[Key.Left]) turretY = turnSpeed;
				if (window.Keyboard[Key.Right]) turretY = -turnSpeed;
				if (window.Keyboard[Key.Up]) turretX = turnSpeed;
				if (window.Keyboard[Key.Down]) turretX = -turnSpeed;
				// ----
				partTurret.Orientation *= 
					Quaternion.FromAxisAngle(Vector3.UnitY, MathHelper.DegreesToRadians(turretY)) *
					Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(turretX));
				partTurret.CalculateMatrix();
 
				// Update partCannon
				// ----
				partCannon.CalculateMatrix();
			};
 
			window.Resize += (object sender, EventArgs e) => 
			{
				var aspect = 4f / 3f;
				var viewWidth = (float)window.Width;
				var viewHeight = (float)window.Width / aspect;
				if (viewHeight > window.Height)
				{
					viewWidth = window.Height * aspect;
					viewHeight = window.Height;
				}
				var viewX = (window.Width - viewWidth) / 2f;
				var viewY = (window.Height - viewHeight) / 2f;
				GL.Viewport((int)viewX, (int)viewY, (int)viewWidth, (int)viewHeight);
 
				matrixProjection = Matrix4.CreatePerspectiveFieldOfView(
					MathHelper.PiOver4, (float)window.Width/window.Height, 0.1f, 1000f);
			};
 
			Action<Matrix4, Matrix4> drawAxisAction = (_matrixModel, _matrixViewProj) =>
			{
				// Template of: matrixPush = partBody.Matrix * matrixMVP; GL.LoadMatrix(ref matrixPush); DrawAxis();
				matrixPush = _matrixModel * _matrixViewProj;
				GL.LoadMatrix(ref matrixPush);
				DrawAxis();
			};
 
			window.RenderFrame += (object sender, FrameEventArgs e) =>
			{
				GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
				matrixModelview = Matrix4.LookAt(new Vector3(10f, 10f, 10f), Vector3.Zero, Vector3.UnitY);
				matrixMVP = matrixModelview * matrixProjection;
				GL.LoadMatrix(ref matrixMVP);
 
				// Draw world axis
				GL.Color3(1f, 1f, 1f);
				DrawAxis();
 
				// Draw the body
				GL.Color3(1f, 0f, 0f);
				drawAxisAction(partBody.Matrix, matrixMVP);
 
				// Draw the turret
				GL.Color3(1f, 1f, 0f);
				drawAxisAction(partTurret.Matrix, matrixMVP);
 
				// Draw the cannon
				GL.Color3(0f, 1f, 0f);
				drawAxisAction(partCannon.Matrix, matrixMVP);
 
				window.SwapBuffers();
			};
 
			window.Run();
		}
 
		public static void DrawAxis()
		{
			GL.Begin(PrimitiveType.Lines);
			GL.Vertex3(-1f, 0f, 0f);
			GL.Vertex3( 1f, 0f, 0f);
			GL.Vertex3(0f, 1f, 0f);
			GL.Vertex3(0f, -1f, 0f);
			GL.Vertex3(0f, 0f, 1f);
			GL.Vertex3(0f, 0f, -1f);
			GL.End();
		}
	}
}