1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:			    $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/math/quaternion.d)
6  * Documentation:
7  * Coverage:
8 **/
9 module liberty.math.quaternion;
10 
11 import liberty.math.vector;
12 import liberty.math.matrix;
13 import liberty.math.traits;
14 ///
15 struct Quaternion(T) {
16 	///
17 	alias type = T;
18 	union {
19 		Vector!(T, 4u) v;
20 		struct {
21 			T x, y, z, w;
22 		}
23 	}
24 	/// Constructs a Quaternion from a value.
25 	this(U)(U x)   if (isAssignable!U) {
26 		opAssign!U(x);
27 	}
28 	/// Constructs a Quaternion from coordinates.
29 	this(T qw, T qx, T qy, T qz)   {
30 		x = qx;
31 		y = qy;
32 		z = qz;
33 		w = qw;
34 	}
35 	/// Constructs a Quaternion from axis + angle.
36 	static Quaternion fromAxis(Vector!(T, 3) axis, T angle)   {
37 		import liberty.math.functions : sin, cos;
38 		Quaternion q = void;
39 		axis.normalize();
40 		T cos_a = cos(angle / 2);
41 		T sin_a = sin(angle / 2);
42 		q.x = sin_a * axis.x;
43 		q.y = sin_a * axis.y;
44 		q.z = sin_a * axis.z;
45 		q.w = cos_a;
46 		return q;
47 	}
48 	/// Constructs a Quaternion from Euler angles.
49 	static Quaternion fromEulerAngles(T roll, T pitch, T yaw)   {
50 		import liberty.math.functions : sin, cos;
51 		Quaternion q = void;
52 		const T sinPitch = sin(pitch / 2);
53 		const T cosPitch = cos(pitch / 2);
54 		const T sinYaw = sin(yaw / 2);
55 		const T cosYaw = cos(yaw / 2);
56 		const T sinRoll = sin(roll / 2);
57 		const T cosRoll = cos(roll / 2);
58 		const T cosPitchCosYaw = cosPitch * cosYaw;
59 		const T sinPitchSinYaw = sinPitch * sinYaw;
60 		q.x = sinRoll * cosPitchCosYaw    - cosRoll * sinPitchSinYaw;
61 		q.y = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw;
62 		q.z = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw;
63 		q.w = cosRoll * cosPitchCosYaw    + sinRoll * sinPitchSinYaw;
64 		return q;
65 	}
66 	/// Converts a quaternion to Euler angles.
67 	Vector!(T, 3) toEulerAngles()   const {
68 		import liberty.math.functions : atan2, sqrt, PI;
69 		Matrix!(T, 3) m = cast(Matrix!(T, 3))(this);
70 		T pitch, yaw, roll;
71 		T s = sqrt(m.c[0][0] * m.c[0][0] + m.c[1][0] * m.c[1][0]);
72 		if (s > T.epsilon) {
73 			pitch = - atan2(m.c[2][0], s);
74 			yaw = atan2(m.c[1][0], m.c[0][0]);
75 			roll = atan2(m.c[2][1], m.c[2][2]);
76 		} else  {
77 			pitch = m.c[2][0] < 0.0f ? PI /2 : -PI / 2;
78 			yaw = -atan2(m.c[0][1], m.c[1][1]);
79 			roll = 0.0f;
80 		}
81 		return Vector!(T, 3)(roll, pitch, yaw);
82 	}
83 	/// Assign from another Quaternion.
84 	ref Quaternion opAssign(U)(U u)   if (isQuaternionInstance!U && is(U._T : T)) {
85 		v = u.v;
86 		return this;
87 	}
88 	/// Assign from a vector of 4 elements.
89 	ref Quaternion opAssign(U)(U u)   if (is(U : Vector!(T, 4u))) {
90 		v = u;
91 		return this;
92 	}
93 	/// Converts to a string.
94 	string toString() const  {
95 		import std..string : format;
96 		try {
97 			return format("%s", v);
98 		} catch (Exception e) {
99 			assert(0);
100 		}
101 	}
102 	/// Normalizes a quaternion.
103 	void normalize()   {
104 		v.normalize();
105 	}
106 	/// Returns normalized quaternion.
107 	Quaternion normalized()   const {
108 		Quaternion ret = void;
109 		ret.v = v.normalized();
110 		return ret;
111 	}
112 	/// Inverses a quaternion in-place.
113 	void inverse()   {
114 		x = -x;
115 		y = -y;
116 		z = -z;
117 	}
118 	/// Returns inverse of quaternion.
119 	Quaternion inversed()   const {
120 		Quaternion ret = void;
121 		ret.v = v;
122 		ret.inverse();
123 		return ret;
124 	}
125 	///
126 	ref Quaternion opOpAssign(string op, U)(U q)   if (is(U : Quaternion) && (op == "*")) {
127 		const T nx = w * q.x + x * q.w + y * q.z - z * q.y;
128 		const T ny = w * q.y + y * q.w + z * q.x - x * q.z;
129 		const T nz = w * q.z + z * q.w + x * q.y - y * q.x;
130 		const T nw = w * q.w - x * q.x - y * q.y - z * q.z;
131 		x = nx;
132 		y = ny;
133 		z = nz;
134 		w = nw;
135 		return this;
136 	}
137 	///
138 	ref Quaternion opOpAssign(string op, U)(U operand)   if (isConvertible!U) {
139 		Quaternion conv = operand;
140 		return opOpAssign!op(conv);
141 	}
142 	///
143 	Quaternion opBinary(string op, U)(U operand)   const if (is(U: Quaternion) || (isConvertible!U)) {
144 		Quaternion temp = this;
145 		return temp.opOpAssign!op(operand);
146 	}
147 	/// Compare two Quaternions.
148 	bool opEquals(U)(U other)  const if (is(U : Quaternion)) {
149 		return v == other.v;
150 	}
151 	/// Compare Quaternion and other types.
152 	bool opEquals(U)(U other)   const if (isConvertible!U) {
153 		Quaternion conv = other;
154 		return opEquals(conv);
155 	}
156 	/// Convert to a 3x3 rotation matrix.
157 	U opCast(U)()   const
158   if (isMatrixInstance!U && is(U.type : type) && (U.rowCount == 3) && (U.columnCount == 3))
159   do {
160 		const T norm = x*x + y*y + z*z + w*w;
161 		const T s = (norm > 0) ? 2 / norm : 0;
162 		T xx = x * x * s, xy = x * y * s, xz = x * z * s, xw = x * w * s,
163 		yy = y * y * s, yz = y * z * s, yw = y * w * s,
164 		zz = z * z * s, zw = z * w * s;
165 		return Matrix!(U.type, 3)(
166       1 - (yy + zz), (xy - zw), (xz + yw),
167       (xy + zw), 1 - (xx + zz), (yz - xw),
168       (xz - yw), (yz + xw), 1 - (xx + yy)
169     );
170 	}
171 	/// Converts a to a 4x4 rotation matrix.
172 	U opCast(U)()   const
173   if (isMatrixInstance!U && is(U.type : type) && (U.rowCount == 4) && (U.columnCount == 4))
174   do {
175 		auto m3 = cast(Matrix!(U.type, 3))(this);
176 		return cast(U)(m3);
177 	}
178 	///
179 	static Quaternion identity()   {
180 		Quaternion q = void;
181 		q.x = q.y = q.z = 0;
182 		q.w = 1;
183 		return q;
184 	}
185 
186   static if (is(T == float)) {
187     /**
188      *
189     **/
190     Quaternion mul(Vector3F r) {
191       const float w_ = -x * r.x - y * r.y - z * r.z;
192       const float x_ =  w * r.x + y * r.z - z * r.y;
193       const float y_ =  w * r.y + z * r.x - x * r.z;
194       const float z_ =  w * r.z + x * r.y - y * r.x;
195       
196       return Quaternion(x_, y_, z_, w_);
197     }
198   }
199 
200 	private enum bool isAssignable(T) = is(typeof({ T x; Quaternion v = x; }()));
201 	private enum bool isConvertible(T) = (!is(T : Quaternion)) && isAssignable!T;
202 }
203 ///
204 alias QuaternionF = Quaternion!float;
205 ///
206 alias QuaternionD = Quaternion!double;
207 /// Returns linear interpolation for quaternions.
208 Quaternion!T lerp(T)(Quaternion!T a, Quaternion!T b, float t)   {
209 	Quaternion!T ret;
210 	ret.v = funcs.lerp(a.v, b.v, t);
211 	return ret;
212 }
213 /// Returns nlerp of quaternions.
214 Quaternion!T nlerp(T)(Quaternion!T a, Quaternion!T b, float t)  
215 in {
216 	assert(t >= 0 && t <= 1);
217 } do {
218 	Quaternion!T ret;
219 	ret.v = funcs.lerp(a.v, b.v, t);
220 	ret.v.normalize();
221 	return ret;
222 }
223 /// Returns slerp of quaternions.
224 Quaternion!T slerp(T)(Quaternion!T a, Quaternion!T b, T t)  
225 in {
226 	assert(t >= 0 && t <= 1);
227 } do {
228 	Quaternion!T ret;
229 	T dotProduct = dot(a.v, b.v);
230 	if (dotProduct < 0) {
231 		b.v *= -1;
232 		dotProduct = dot(a.v, b.v);
233 	}
234 	const T threshold = 10 * T.epsilon;
235 	if ((1 - dotProduct) > threshold) {
236 		return lerp(a, b, t);
237 	}
238 	const T theta_0 = funcs.safeAcos(dotProduct);
239 	const T theta = theta_0 * t;
240 	vec3!T v2 = dot(b.v, a.v * dotProduct);
241 	v2.normalize();
242 	ret.v = dot(b.v, a.v * dotProduct);
243 	ret.v.normalize();
244 	ret.v = a.v * cos(theta) + ret.v * sin(theta);
245 	return ret;
246 }