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