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