1 module polyplex.math.quaternion;
2 import polyplex.math.vector;
3 static import Mathf = polyplex.math.mathf;
4 
5 import std.traits;
6 
7 alias Quaternion = QuaternionT!float;
8 /**
9   A quaternion is a way of representing a rotation in three dimensions by
10     storing a rotation and an angle. It properly forms a topological 3-sphere,
11     which allows it to avoid the gimbal lock that plagues the more
12     human-readable euler angle.
13   Read more here:
14   https://euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/geometric/orthogonal
15 **/
16 struct QuaternionT(T) if (__traits(isFloating, T)) {
17 	/// Holds rotation (xyz) and angle (w)
18 	public Vector4T!T data;
19 
20   public immutable alias Type = T;
21 
22 	// convert to string Q<x, y, z, w>
23 	public alias ToString = toString;
24 	public string toString() {
25 		return "Q" ~ data.toString();
26 	}
27 
28 	// -- Accessors, just call data vector
29 	public @property auto opDispatch(string swizzle_list, U=void)() pure const nothrow {
30 		return data.opDispatch!(swizzle_list);
31 	}
32 	public @property auto opDispatch(string swizzle_list, U)(U x) pure nothrow {
33 		return data.opDispatch!(swizzle_list, U)(x);
34 	}
35 	unittest {
36 		assert(__traits(compiles, QuaternionT!T(float3(0.2f), 0.2f).xy));
37 		assert(QuaternionT!T(Vector4T!T(4)).xwz == Vector3T!T(4));
38 	}
39 
40 	/// Constructs a quaternion from a vector
41 	public this(Vector4T!T vec) pure nothrow { data = vec; }
42 
43 	/// Constructs a quaternion from another quaternion
44 	public this(QuaternionT!T quat) pure nothrow { data = quat.data; }
45 
46 	/// Constructs a quaternion from an axis and an angle theta
47 	public this(Vector3T!T axis, T theta) pure nothrow {
48 		// adjust axis and theta
49 		axis = axis.Normalize();
50 		theta = theta/cast(T)2;
51 		T sin_theta = Mathf.Sin(theta);
52 		// Set data
53 		data.w = Mathf.Cos(theta);
54 		data.x = sin_theta*axis.x;
55 		data.y = sin_theta*axis.x;
56 		data.z = sin_theta*axis.x;
57 	}
58 	unittest {
59 		assert(__traits(compiles, QuaternionT!T(Vector3T!T(0.3f), 0.5f)));
60 	}
61 
62 	/// Constructs identity of a quaternion
63 	public static QuaternionT!T Identity() pure nothrow {
64 		QuaternionT!T q;
65 		q.x = q.y = q.z = 0;
66 		q.w = 1;
67 		return q;
68 	}
69 
70 	/// Constructs a quaternion from euler angle
71 	public static QuaternionT!T ToQuaternion(T pitch, T roll, T yaw) pure nothrow {
72 		QuaternionT!T q;
73 		// adjust angles
74 		pitch *= cast(T)0.5f;
75 		roll *= cast(T)0.5f;
76 		yaw *= cast(T)0.5f;
77 
78 		T cy = Mathf.Cos(yaw), sy = Mathf.Sin(yaw);
79 		T cr = Mathf.Cos(roll), sr = Mathf.Sin(roll);
80 		T cp = Mathf.Cos(pitch), sp = Mathf.Sin(pitch);
81 
82 		q.w = cy*cr*cp + sy*sr*sp;
83 		q.x = cy*sr*cp - sy*cr*sp;
84 		q.y = cy*cr*sp + sy*sr*cp;
85 		q.z = sy*cr*cp - cy*sr*sp;
86 
87 		return q;
88 	}
89 
90 	/// Constructs a quaternion from euler angle vector
91 	public static QuaternionT!T ToQuaternion(Vector3T!T vec) pure nothrow {
92 		return ToQuaternion(vec.x, vec.y, vec.z);
93 	}
94 	
95 	unittest {
96 		assert(__traits(compiles, ToQuaternion(Vector3T!T(0.2f))));
97 		assert(__traits(compiles, ToQuaternion(0.2f, 0.5f, 0.6f)));
98 	}
99 
100 	/// Returns the euler angle equivalent of this quaternion, in the form of
101 	///   <roll, pitch, yaw>
102 	public Vector3T!T ToEulerAngle() pure const nothrow {
103 		Vector3T!T euler;
104 		QuaternionT!T q = this;
105 
106 		// calculate roll
107 		T sinr = 2 * (q.w*q.x + q.y*q.z);
108 		T cosr = 1 - 2*(q.x*q.x + q.y*q.y);
109 		euler.X = Mathf.ATan2(sinr, cosr);
110 
111 		// calculate pitch
112 		T sinp = Mathf.Clamp(2*(q.w*q.y - q.z*q.x), cast(T)-1, cast(T)1);
113 		euler.Y = Mathf.ASin(sinp);
114 
115 		// calculate yaw
116 		T siny = 2 * (q.w*q.z + q.x*q.y);
117 		T cosy = 1 - 2*(q.y*q.y + q.z*q.z);
118 		euler.Z = Mathf.ATan2(siny, cosy);
119 
120 		return euler;
121 	}
122 
123 	/// Sets euler angle as member parameters, equivalent of this quaternion
124 	public void ToEulerAngle(ref T roll, ref T pitch, ref T yaw) pure const nothrow {
125 		Vector3T!T euler = ToEulerAngle();
126 		roll = euler.x;
127 		pitch = euler.y;
128 		yaw = euler.z;
129 	}
130 	unittest {
131 		auto q = QuaternionT!T(Vector3T!T(0.3f), 0.5f);
132 		T r, p, y;
133 		assert(__traits(compiles, q.ToEulerAngle));
134 		assert(__traits(compiles, q.ToEulerAngle(r, p, y)));
135 	}
136 
137 	/// Returns a normalized quaternion
138 	public QuaternionT!T Normalize ( ) pure const nothrow {
139 		return QuaternionT!T(data.Normalize);
140 	}
141 	unittest {
142 		assert(__traits(compiles, QuaternionT!T.ToQuaternion(Vector3T!T(0.3)).Normalize));
143 	}
144 
145 	/// Returns a conjugated quaternion
146 	public QuaternionT!T Conjugate ( ) pure const nothrow {
147 		return QuaternionT!T(float4(-data.x, -data.y, -data.z, data.w));
148 	}
149 	unittest {
150 		assert(__traits(compiles, QuaternionT!T(Vector3T!T(0.2f), 0.4f).Normalize.Conjugate));
151 	}
152 
153 	// Returns a scaled quaternion
154 	public QuaternionT!T Scale(T scale) pure const nothrow {
155 		return QuaternionT!T(data*scale);
156 	}
157 	unittest {
158 		assert(__traits(compiles, QuaternionT!T(Vector3T!T(0.2f), 0.4f).Scale(0.5)));
159 	}
160 
161 	/// quaternion * quaternion
162 	public QuaternionT!T opBinary(string op, U)(U rhs) pure const nothrow
163 	                      if (is(U : QuaternionT!T) && op == "*") {
164 		QuaternionT!T v;
165 		QuaternionT!T lhs = this;
166 		v.x =  lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y + lhs.w * rhs.x;
167 		v.y = -lhs.x * rhs.z + lhs.y * rhs.w + lhs.z * rhs.x + lhs.w * rhs.y;
168 		v.z =  lhs.x * rhs.y - lhs.y * rhs.x + lhs.z * rhs.w + lhs.w * rhs.z;
169 		v.w = -lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z + lhs.w * rhs.w;
170 		return v;
171 	}
172 	/// quaternion *= quaternion
173 	public void opOpAssign(string op, U)(U rhs) pure nothrow
174 	                       if (is(U : QuaternionT!T) && op == "*") {
175 		this = this * rhs;
176 	}
177 
178 	/// quaternion + quaternion
179 	public QuaternionT!T opBinary(string op, U)(U rhs) pure const nothrow
180 	                       if(is(U : QuaternionT!T) && op == "+") {
181 		return QuaternionT!T(this.data + rhs.data);
182 	}
183 	/// quaternion += quaternion
184 	public void opOpAssign(string op, U)(U rhs) pure nothrow
185 	                       if (is(U : QuaternionT!T) && op == "+") {
186 		this = this + rhs;
187 	}
188 
189 	unittest {
190 		QuaternionT!T q = QuaternionT!T(float4(0.4f));
191 		assert(__traits(compiles, q*q+q));
192 		q *= q;
193 		q += q;
194 	}
195 }