1 module ws.math.quaternion;
2 
3 import std.math, ws.math.matrix, ws.math.vector, ws.math.angle;
4 
5 
6 private alias Type = float;
7 
8 
9 struct Quaternion {
10 	
11 	Type w = 1;
12 	Type x = 0;
13 	Type y = 0;
14 	Type z = 0;
15 	
16 	this(Type iw, Type ix, Type iy, Type iz){
17 		w = iw; x = ix; y = iy; z = iz;
18 	}
19 	
20 	this(Type iw, Vector!3 v){
21 		fromAngleAxis(iw, v);
22 	}
23 	
24 	this(Angle a){
25 		this = euler(a);
26 	}
27 	
28 	@property Quaternion normal(){
29 		Type len = length;
30 		return Quaternion(w/len, x/len, y/len, z/len);
31 	}
32 	
33 	void normalize(){
34 		Type len = length;
35 		w /= len;
36 		x /= len;
37 		y /= len;
38 		z /= len;
39 	}
40 	
41 	@property Type length(){
42 		return sqrt(x*x + y*y + z*z + w*w);
43 	}
44 	
45 	@property Vector!3 forward(){
46 		return this*Vector!3(0,0,-1);
47 	}
48 	
49 	@property Vector!3 up(){
50 		return this*Vector!3(0,1,0);
51 	}
52  
53 	@property Vector!3 right(){
54 		return this*Vector!3(1,0,0);
55     }
56 
57 	@property Quaternion inverse(){
58 		return Quaternion(w, -x, -y, -z);
59 	}
60 	
61 	static Quaternion euler(Angle a){
62 		Quaternion q;
63 		Type sinp = sin(a.p*PI/360);
64 		Type siny = sin(a.y*PI/360);
65 		Type sinr = sin(a.r*PI/360);
66 		Type cosp = cos(a.p*PI/360);
67 		Type cosy = cos(a.y*PI/360);
68 		Type cosr = cos(a.r*PI/360);
69 		q.x = sinr*cosp*cosy - cosr*sinp*siny;
70 		q.y = cosr*sinp*cosy + sinr*cosp*siny;
71 		q.z = cosr*cosp*siny - sinr*sinp*cosy;
72 		q.w = cosr*cosp*cosy + sinr*sinp*siny;
73 		q.normalize();
74 		return q;
75 	}
76 
77 
78 	static Quaternion euler(double p, double y, double r){
79 		return euler(Angle(p, y, r));
80 	}
81 
82 
83 	Angle euler(){
84 		auto sqw = w*w;
85 		auto sqx = x*x;
86 		auto sqy = y*y;
87 		auto sqz = z*z;
88 		auto test = 2.0 * (y*w - x*z);
89 		Angle ang;
90 		if(test < 1.0001 && test > 0.9999){
91 			ang.y = -2.0*atan2(x, w);
92 			ang.r = 0;
93 			ang.p = PI/2;
94 		}else if(test > -1.0001 && test < -0.9999){
95 			ang.y = 2.0*atan2(x, w);
96 			ang.r = 0;
97 			ang.p = PI/-2;
98 		}else{
99 			ang.y = atan2(2*(x*y +z*w), (sqx - sqy - sqz + sqw));
100 			ang.r = atan2(2*(y*z +x*w),(-sqx - sqy + sqz + sqw));
101 			ang.p = asin(clamp(test, -1, 1));
102 		}
103 		ang.p = ang.p * 180/PI;
104 		ang.y = ang.y * 180/PI;
105 		ang.r = ang.r * 180/PI;
106 		return ang;
107 	}
108 
109 	Quaternion fromAngleAxis(Type angle, Vector!3 dir){
110 		auto v = dir.normal;
111 		auto s = sin(angle*PI/360);
112 		this = Quaternion(
113 			cos(angle/PI/360),
114 			v.x*s,
115 			v.y*s,
116 			v.z*s
117 		).normal;
118 		return this;
119 	}
120 
121 
122 	void rotate(Type angle, Type x, Type y, Type z){
123 		rotate(angle, Vector!3(x,y,z));
124 	}
125 
126 
127 	void rotate(Type angle, Vector!3 v){
128 		Quaternion t;
129 		t.fromAngleAxis(angle, v);
130 		this = t*this;
131 	} 
132 
133 
134 	Matrix!(4,4) matrix(){
135 		Type x2 = x * x;
136 		Type y2 = y * y;
137 		Type z2 = z * z;
138 		Type xy = x * y;
139 		Type xz = x * z;
140 		Type yz = y * z;
141 		Type wx = w * x;
142 		Type wy = w * y;
143 		Type wz = w * z;
144 
145 		return new Matrix!(4,4)([
146 			1-2*(y2 + z2), 2*(xy - wz), 2*(xz + wy), 0,
147 			2*(xy + wz), 1-2*(x2 + z2), 2*(yz - wx), 0,
148 			2*(xz - wy), 2*(yz + wx), 1-2*(x2 + y2), 0,
149 			0, 0, 0, 1
150 		]);
151 	}
152 
153 	Quaternion opBinary(string op)(Quaternion other) if(op=="*"){
154 		return Quaternion(
155 			w * other.w - x * other.x - y * other.y - z * other.z,
156 			w * other.x + x * other.w + y * other.z - z * other.y,
157 			w * other.y + y * other.w + z * other.x - x * other.z,
158 			w * other.z + z * other.w + x * other.y - y * other.x
159 		);
160 	}
161 
162 	Vector!3 opBinary(string op)(Vector!3 v) if(op=="*"){
163 		auto o = this*(Quaternion(0, v.x, v.y, v.z)*inverse);
164 		return Vector!3(o.x, o.y, o.z);
165 	}
166 
167 	void opOpAssign(string op, T)(T other){
168 		mixin("this = this " ~ op ~ " other;");
169 	}
170 
171 }
172 
173 private double clamp(double n, double min, double max){
174 	return n < min ? min : (n > max ? max : n);
175 }
176 
177 unittest {
178 
179 	import std.string;
180 
181 
182 	void assertEqualA(Angle left, Angle right){
183 		assert((left.p - right.p).abs < 0.00001, "%s not equal %s".format(left, right));
184 		assert((left.y - right.y).abs < 0.00001, "%s not equal %s".format(left, right));
185 		assert((left.r - right.r).abs < 0.00001, "%s not equal %s".format(left, right));
186 	}
187 
188 	void assertEqualQ(Quaternion left, Quaternion right){
189 		assert((left.w - right.w).abs < 0.00001, "%s not equal %s".format(left, right));
190 		assert((left.x - right.x).abs < 0.00001, "%s not equal %s".format(left, right));
191 		assert((left.y - right.y).abs < 0.00001, "%s not equal %s".format(left, right));
192 		assert((left.z - right.z).abs < 0.00001, "%s not equal %s".format(left, right));
193 	}
194 
195 
196 	void assertEqual(T1, T2)(T1 o1, T2 o2){
197 		assert(o1 == o2, "%s not equal %s".format(o1, o2));
198 	}
199 
200 	assertEqual((Quaternion(Angle(0,0,90))*Vector!3(0,1,0)).normal, Vector!3(0,0,1));
201 	assertEqual((Quaternion(Angle(-90,0,0))*Vector!3(1,0,0)).normal, Vector!3(0,0,1));
202 	assertEqual((Quaternion(Angle(0,90,0))*Vector!3(1,0,0)).normal, Vector!3(0,1,0));
203 	Quaternion f = Angle(0,95,90);
204 	assertEqualA(f.euler(), Angle(0,95,90));
205 	Quaternion conv = Quaternion.euler(f.euler());
206 	assertEqualQ(f, conv);
207 }
208 
209 
210