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