1 module polyplex.math.linear.quaternion;
2 import polyplex.math;
3 
4 public struct Quaternion {
5 	private float[4] data;
6 	
7 	/**
8 		Creates a new Quaternion, in which initial values are X, Y, Z and W.
9 	*/
10 	public this(float x, float y, float z, float w) {
11 		data[0] = x;
12 		data[1] = y;
13 		data[2] = z;
14 		data[3] = w;
15 	}	
16 	
17 	/**
18 		Creates a new Quaternion, in which initial values are X, Y, Z and 0.
19 	*/
20 	public this(float x, float y, float z) {
21 		data[0] = x;
22 		data[1] = y;
23 		data[2] = z;
24 		data[3] = 0;
25 	}	
26 
27 	/**
28 		Creates a new Quaternion, in which initial values are X, Y, 0 and 0.
29 	*/
30 	public this(float x, float y) {
31 		data[0] = x;
32 		data[1] = y;
33 		data[2] = 0;
34 		data[3] = 0;
35 	}
36 
37 	/**
38 		Creates a new Quaternion, in which all initial values are X.
39 	*/
40 	public this(float x) {
41 		data[0] = x;
42 		data[1] = x;
43 		data[2] = x;
44 		data[3] = x;
45 	}
46 
47 	/**
48 		The X component.	
49 	*/
50 	public float X() { return data[0]; }
51 	public void X(float value) { data[0] = value; }
52 		
53 	/**
54 		The Y component.	
55 	*/
56 	public float Y() { return data[1]; }
57 	public void Y(float value) { data[1] = value; }
58 
59 	/**
60 		The Z component.	
61 	*/
62 	public float Z() { return data[2]; }
63 	public void Z(float value) { data[2] = value; }
64 
65 	/**
66 		The W component.	
67 	*/
68 	public float W() { return data[3]; }
69 	public void W(float value) { data[3] = value; }
70 
71 	public void Reorient(Vector3 axis, float angle) {
72 		this.X = axis.X;
73 		this.Y = axis.Y;
74 		this.Z = axis.Z;
75 		this.W = angle;
76 	}
77 
78 	public Vector3 ForwardDirection() {
79 		return Vector3(
80 			2 * (X*Z + W*Y), 
81 			2 * (Y*Z - W*X),
82 			1 - 2 * (X*X + Y*Y));
83 	}
84 
85 	public Vector3 UpDirection() {
86 		return Vector3(
87 			2 * (X*Y + W*Z), 
88 			1 - 2 * (X*X + Z*Z),
89 			2 * (Y*Z - W*X));
90 	}
91 
92 	public Vector3 LeftDirection() {
93 		return Vector3(
94 			1 - 2 * (Y*Y + Z*Z), 
95 			2 * (X*Y + W*Z),
96 			2 * (X*Z - W*Y));
97 	}
98 
99 	public void Rotate(T)(Vector3 axis, T theta) {
100 		X = Mathf.Sin(cast(real)theta/2) * axis.X;
101 		Y = Mathf.Sin(cast(real)theta/2) * axis.Y;
102 		Z = Mathf.Sin(cast(real)theta/2) * axis.Z;
103 		W = Mathf.Cos(cast(real)theta/2);
104 	}
105 
106 	public static Quaternion Rotated(T)(Vector3 axis, T theta) {
107 		Quaternion q;
108 		q.Rotate(axis, theta);
109 		return q;
110 	}
111 
112 	/*public Vector3 Rotate(T)(Vector3 axis, T angle) {
113 		Vector3 vec;
114 		vec.X = X;
115 		vec.Y = Y;
116 		vec.Z = Z;
117 
118 		float vd = 2.0f * vec.Dot(axis);
119 		Vector3 vdv = Vector3(vec.X * vd, vec.Y * vd, vec.Z * vd);
120 
121 		float ad = W*W - vec.Dot(vec);
122 		Vector3 adv = Vector3(axis.X * ad, axis.Y * ad, axis.Z * ad);
123 
124 		float vc = 2.0f * W;
125 		Vector3 vcv = vec.Cross(axis);
126 		vcv.X *= vc;
127 		vcv.Y *= vc;
128 		vcv.Z *= vc;
129 
130 		return vdv * adv + vcv ;
131 	}*/
132 
133 	public static Quaternion EulerToQuaternion(Vector3 euler) {
134 		Quaternion quat;
135 
136 		// pitch
137 		double cp = Mathf.Cos(euler.X * 0.5);
138 		double sp = Mathf.Cos(euler.X * 0.5);
139 
140 		// roll
141 		double cr = Mathf.Cos(euler.Y * 0.5);
142 		double sr = Mathf.Cos(euler.Y * 0.5);
143 
144 		// yaw
145 		double cy = Mathf.Cos(euler.Z * 0.5);
146 		double sy = Mathf.Cos(euler.Z * 0.5);
147 
148 
149 		// convert to quaternion.
150 		quat.X = cy * sr * cp - sy * cr * sp;
151 		quat.Y = cy * cr * sp + sy * sr * cp;
152 		quat.Z = sy * cr * cp - cy * sr * sp;
153 		quat.W = cy * cr * cp + sy * sr * sp;
154 		return quat;
155 	}
156 
157 	public static Quaternion FromMatrix(Matrix4x4 mat) {
158 		Quaternion qt = Quaternion.Identity;
159 		float tr = mat[0,0] + mat[1,1] + mat[2,2];
160 
161 		if (tr > 0) {
162 			float S =  Mathf.Sqrt(tr+1.0) * 2;
163 			qt.X = (mat[2,1] - mat[1,2]) / S;
164 			qt.Y = (mat[0,2] - mat[2,0]) / S;
165 			qt.Z = (mat[1,0] - mat[0,1]) / S;
166 			qt.W = 0.25 * S;
167 			return qt;
168 		}
169 
170 		if ((mat[0,0] > mat[1,1])&(mat[0,0] > mat[2,2])) {
171 			float S = Mathf.Sqrt(1.0 + mat[0,0] - mat[1,1] - mat[2,2]) * 2;
172 			qt.X = 0.25 * S;
173 			qt.Y = (mat[0,1] + mat[1,0]) / S;
174 			qt.Z = (mat[0,2] + mat[2,0]) / S;
175 			qt.W = (mat[2,1] - mat[1,2]) / S;
176 			return qt;
177 		}
178 
179 		if (mat[1,1] > mat[2,2]) {
180 			float S = Mathf.Sqrt(1.0 + mat[1,1] - mat[0,0] - mat[2,2]) * 2;
181 			qt.X = (mat[0,1] + mat[1,0]) / S;
182 			qt.Y = 0.25 * S;
183 			qt.Z = (mat[1,2] - mat[2,1]) / S;
184 			qt.W = (mat[0,2] + mat[2,0]) / S;
185 			return qt;
186 		}
187 
188 		float S = Mathf.Sqrt(1.0 + mat[2,2] - mat[0,0] - mat[1,1]) * 2;
189 		qt.X = (mat[0,2] + mat[2,0]) / S;
190 		qt.Y = (mat[1,2] - mat[2,1]) / S;
191 		qt.Z = 0.25 * S;
192 		qt.W = (mat[1,0] + mat[0,1]) / S;
193 		return qt;
194 	}
195 
196 	// Binary actions.
197 	public @trusted nothrow Quaternion opBinary(string op, T2)(T2 other) if (is (T2 : Quaternion)) { 
198 		// Operation on these, (due to being smallest size) can be done faster this way.
199 		mixin(q{
200 			return Quaternion(
201 				this.X * other.W + this.Y * other.Z + this.Z * other.Y - this.W * other.X,
202 				-this.X * this.Z - this.Y * other.W + this.Z * other.X + this.W * other.Y,
203 				this.X * this.Y - this.Y * other.X + this.Z * other.W + this.W * other.Z,
204 				-this.X * this.X - this.Y * other.Y + this.Z * other.Z + this.W * other.W);
205 			}.Format(op)
206 		); 
207 	}
208 
209 	public @trusted Quaternion Normalized() {
210 		import polyplex.math;
211 		double n = Mathf.Sqrt(X*X+Y*Y+Z*Z+W*W);
212 		return Quaternion(X/n, Y/n, Z/n, W/n);
213 	}
214 
215 	public static Quaternion Identity() {
216 		return Quaternion(0f, 0f, 0f, 0f);
217 	}
218 
219 	public string toString() {
220 		import polyplex.utils.strutils;
221 		return "<{0}, {1}, {2}, {3}>".Format(X, Y, Z, W);
222 	}
223 }