/ecere/src/gfx/3D/Quaternion.ec

https://github.com/redj/ecere-sdk · C · 400 lines · 333 code · 55 blank · 12 comment · 9 complexity · acde100d98c0498badc8a1907ba2477a MD5 · raw file

  1. namespace gfx3D;
  2. import "Display"
  3. public enum EulerRotationOrder { xyz, xzy, yxz, yzx, zxy, zyx };
  4. public struct Quaternion
  5. {
  6. double w, x, y, z;
  7. void Identity(void)
  8. {
  9. x = y = z = 0;
  10. w = 1;
  11. }
  12. void Normalize(const Quaternion source)
  13. {
  14. double m = sqrt(source.x * source.x +
  15. source.y * source.y +
  16. source.z * source.z +
  17. source.w * source.w);
  18. if(m)
  19. {
  20. x = (double)(source.x/m);
  21. y = (double)(source.y/m);
  22. z = (double)(source.z/m);
  23. w = (double)(source.w/m);
  24. }
  25. else
  26. w = x = y = z = 0;
  27. }
  28. void Multiply(const Quaternion q1, const Quaternion q2)
  29. {
  30. w = q1.w * q2.w - q2.x * q1.x - q1.y * q2.y - q1.z * q2.z;
  31. x = q1.w * q2.x + q2.w * q1.x + q1.y * q2.z - q1.z * q2.y;
  32. y = q1.w * q2.y + q2.w * q1.y + q1.z * q2.x - q1.x * q2.z;
  33. z = q1.w * q2.z + q2.w * q1.z + q1.x * q2.y - q1.y * q2.x;
  34. }
  35. void Divide(const Quaternion q1, const Quaternion q2)
  36. {
  37. w = q2.w * q1.w + q2.x * q1.x + q2.y * q1.y + q2.z * q1.z;
  38. x = q2.w * q1.x - q2.x * q1.w + q2.y * q1.z - q2.z * q1.y;
  39. y = q2.w * q1.y - q2.x * q1.z - q2.y * q1.w + q2.z * q1.x;
  40. z = q2.w * q1.z + q2.x * q1.y - q2.y * q1.x - q2.z * q1.w;
  41. }
  42. void RotationAxis(const Vector3D axis, Degrees angle)
  43. {
  44. double sa = sin( angle / 2 );
  45. double ca = cos( angle / 2 );
  46. x = axis.x * sa;
  47. y = axis.y * sa;
  48. z = axis.z * sa;
  49. w = ca;
  50. }
  51. void RotationYawPitchRoll(const Euler euler)
  52. {
  53. Quaternion rotation, result;
  54. result.Yaw(euler.yaw);
  55. rotation.Pitch(euler.pitch);
  56. Multiply(rotation, result);
  57. rotation.Roll(euler.roll);
  58. result.Multiply(rotation, this);
  59. Normalize(result);
  60. }
  61. void RotationEuler(Euler euler, EulerRotationOrder rotationOrder)
  62. {
  63. Quaternion qPitch, qYaw, qRoll;
  64. qPitch.RotationAxis({ 1,0,0 }, euler.pitch);
  65. qYaw.RotationAxis ({ 0,1,0 }, euler.yaw);
  66. qRoll.RotationAxis ({ 0,0,1 }, euler.roll);
  67. rotateQuats(qPitch, qYaw, qRoll, rotationOrder);
  68. }
  69. static void rotateQuats(Quaternion qPitch, Quaternion qYaw, Quaternion qRoll, EulerRotationOrder rotationOrder)
  70. {
  71. Quaternion q, a;
  72. switch(rotationOrder)
  73. {
  74. case xyz: a.Multiply(qYaw, qPitch); q.Multiply(qRoll, a); break;
  75. case xzy: a.Multiply(qRoll, qPitch); q.Multiply(qYaw, a); break;
  76. case yxz: a.Multiply(qPitch, qYaw); q.Multiply(qRoll, a); break;
  77. case yzx: a.Multiply(qRoll, qYaw); q.Multiply(qPitch, a); break;
  78. case zyx: a.Multiply(qYaw, qRoll); q.Multiply(qPitch, a); break;
  79. default:
  80. case zxy: a.Multiply(qPitch, qRoll); q.Multiply(qYaw, a); break;
  81. }
  82. }
  83. void RotationDirection(const Vector3D direction)
  84. {
  85. Angle yaw = -atan2(direction.x, direction.z);
  86. Angle pitch = atan2(direction.y, sqrt(direction.x * direction.x + direction.z * direction.z));
  87. YawPitch(yaw, pitch);
  88. }
  89. void RotationMatrix(const Matrix m)
  90. {
  91. double t = m.m[0][0] + m.m[1][1] + m.m[2][2];
  92. if(t > 0)
  93. {
  94. double s = sqrt(t + 1.0) * 2;
  95. w = 0.25 * s;
  96. x = ( m.m[2][1] - m.m[1][2] ) / s;
  97. y = ( m.m[0][2] - m.m[2][0] ) / s;
  98. z = ( m.m[1][0] - m.m[0][1] ) / s;
  99. }
  100. else
  101. {
  102. double q[3];
  103. double s;
  104. int i = 0,j,k;
  105. int nxt[3] = {1,2,0};
  106. if(m.m[1][1] > m.m[0][0]) i = 1;
  107. if(m.m[2][2] > m.m[i][i]) i = 2;
  108. j = nxt[i];
  109. k = nxt[j];
  110. s = sqrt(m.m[i][i] - (m.m[j][j] + m.m[k][k]) + 1.0) * 2;
  111. w = (m.m[k][j] - m.m[j][k]) / s;
  112. q[i] = 0.25 * s;
  113. q[j] = (m.m[j][i] + m.m[i][j]) / s;
  114. q[k] = (m.m[k][i] + m.m[i][k]) / s;
  115. x = q[0];
  116. y = q[1];
  117. z = q[2];
  118. }
  119. }
  120. #define DELTA 0
  121. void Slerp(const Quaternion from, const Quaternion to, float t)
  122. {
  123. double to1[4];
  124. double omega, cosom, sinom, scale0, scale1;
  125. cosom = from.x * to.x + from.y * to.y + from.z * to.z + from.w * to.w;
  126. if ( cosom < 0.0 )
  127. {
  128. cosom = -cosom;
  129. to1[0] = -to.x;
  130. to1[1] = -to.y;
  131. to1[2] = -to.z;
  132. to1[3] = -to.w;
  133. }
  134. else
  135. {
  136. to1[0] = to.x;
  137. to1[1] = to.y;
  138. to1[2] = to.z;
  139. to1[3] = to.w;
  140. }
  141. if ( (1.0 - cosom) > DELTA )
  142. {
  143. omega = acos(cosom);
  144. sinom = sin(omega);
  145. scale0 = sin((1.0 - t) * omega) / sinom;
  146. scale1 = sin(t * omega) / sinom;
  147. }
  148. else
  149. {
  150. scale0 = 1.0 - t;
  151. scale1 = t;
  152. }
  153. x = (double)(scale0 * from.x + scale1 * to1[0]);
  154. y = (double)(scale0 * from.y + scale1 * to1[1]);
  155. z = (double)(scale0 * from.z + scale1 * to1[2]);
  156. w = (double)(scale0 * from.w + scale1 * to1[3]);
  157. }
  158. void Yaw(Degrees angle)
  159. {
  160. double sa = sin( angle / 2 );
  161. double ca = cos( angle / 2 );
  162. x = 0;
  163. y = (double)sa;
  164. z = 0;
  165. w = (double)ca;
  166. }
  167. void YawPitch(Degrees yaw, Degrees pitch)
  168. {
  169. double sYaw = sin( yaw / 2 );
  170. double cYaw = cos( yaw / 2 );
  171. double sPitch = sin( pitch / 2 );
  172. double cPitch = cos( pitch / 2 );
  173. w = cPitch * cYaw;
  174. x = sPitch * cYaw;
  175. y = cPitch * sYaw;
  176. z = sPitch * sYaw;
  177. }
  178. void Pitch(Degrees angle)
  179. {
  180. double sa = sin( angle / 2 );
  181. double ca = cos( angle / 2 );
  182. x = (double)sa;
  183. y = 0;
  184. z = 0;
  185. w = (double)ca;
  186. }
  187. void Roll(Degrees angle)
  188. {
  189. double sa = sin( angle / 2 );
  190. double ca = cos( angle / 2 );
  191. x = 0;
  192. y = 0;
  193. z = (double)sa;
  194. w = (double)ca;
  195. }
  196. void RotatePitch(Degrees pitch)
  197. {
  198. Quaternion rotation, result;
  199. rotation.Pitch(pitch);
  200. result.Multiply(rotation, this);
  201. this = result;
  202. }
  203. void RotateYaw(Degrees yaw)
  204. {
  205. Quaternion rotation, result;
  206. rotation.Yaw(yaw);
  207. result.Multiply(rotation, this);
  208. this = result;
  209. }
  210. void RotateRoll(Degrees roll)
  211. {
  212. Quaternion rotation, result;
  213. rotation.Roll(roll);
  214. result.Multiply(rotation, this);
  215. this = result;
  216. }
  217. void RotateYawPitch(Degrees yaw, Degrees pitch)
  218. {
  219. Quaternion rotation, result;
  220. rotation.YawPitch(yaw, pitch);
  221. result.Multiply(rotation, this);
  222. this = result;
  223. }
  224. void ToDirection(Vector3D direction)
  225. {
  226. /*
  227. Vector3D vector { 0,0,1 };
  228. Matrix mat;
  229. mat.RotationQuaternion(this);
  230. direction.MultMatrix(vector, mat);
  231. */
  232. direction.x = (double)( 2 * ( x*z - w*y ));
  233. direction.y = (double)( 2 * ( y*z + w*x ));
  234. direction.z = (double)(1 - 2 * ( x*x + y*y ));
  235. }
  236. void Inverse(const Quaternion source)
  237. {
  238. this = { -source.w, source.x, source.y, source.z };
  239. }
  240. };
  241. static struct RotationOrderProperties
  242. {
  243. int yaw, pitch, roll; // order of yaw, pitch & roll in the rotation order
  244. int t0y, t0x, s0y, s0x; // atan2() terms and signs for 1st component
  245. int t1, s1; // term for determining Gimbal lock and resolving 2nd component with asin()
  246. int t2y, t2x, s2y, s2x; // atan2() terms and signs for 3rd component
  247. int fy, fx, sfy, sfx; // Gimbal lock fallback terms & signs for atan2()
  248. };
  249. static RotationOrderProperties rotProps[EulerRotationOrder] =
  250. {
  251. // YAW PITCH ROLL | t0y t0x s0y s0x | t1 s1 | t2y t2x s2y s2x | fy fx sfy sfx
  252. { 1, 0, 2, 2*4+1, 2*4+2, 1, 1, 2*4+0, -1, 1*4+0, 0*4+0, 1, 1, 0*4+1, 0*4+2, 1, 1 }, // xyz
  253. { 2, 0, 1, 1*4+2, 1*4+1, -1, 1, 1*4+0, 1, 2*4+0, 0*4+0, -1, 1, 0*4+2, 0*4+1, 1, -1 }, // xzy
  254. { 0, 1, 2, 2*4+0, 2*4+2, -1, 1, 2*4+1, 1, 0*4+1, 1*4+1, -1, 1, 0*4+2, 0*4+0, -1, -1 }, // yxz
  255. { 0, 2, 1, 0*4+2, 0*4+0, 1, 1, 0*4+1, -1, 2*4+1, 1*4+1, 1, 1, 1*4+2, 1*4+0, 1, 1 }, // yzx
  256. { 2, 1, 0, 1*4+0, 1*4+1, 1, 1, 1*4+2, -1, 0*4+2, 2*4+2, 1, 1, 0*4+1, 0*4+0, 1, -1 }, // zxy
  257. { 1, 2, 0, 0*4+1, 0*4+0, -1, 1, 0*4+2, 1, 1*4+2, 2*4+2, -1, 1, 1*4+0, 1*4+1, -1, -1 } // zyx
  258. };
  259. // FIXME: Having this before Quaternion class now causes problems!!! (Since adding FromQuaternion() ?)
  260. public struct Euler
  261. {
  262. Degrees yaw, pitch, roll;
  263. void FromMatrix(const Matrix m, EulerRotationOrder order)
  264. {
  265. Degrees values[3];
  266. RotationOrderProperties p = rotProps[order];
  267. double v = m.array[p.t1];
  268. if(fabs(v) <= 1.0 - 0.000005)
  269. {
  270. values[0] = atan2(p.s0y * m.array[p.t0y], p.s0x * m.array[p.t0x]);
  271. //values[1] = atan2(p.s1 * v, sqrt(1.0 - v * v));
  272. //values[1] = atan2(p.s1 * m.array[t1], sqrt(m.array[p.t0y]*m.array[p.t0y] + m.array[p.t0x]*m.array[p.t0x]));
  273. values[1] = asin(p.s1 * v);
  274. values[2] = atan2(p.s2y * m.array[p.t2y], p.s2x * m.array[p.t2x]);
  275. }
  276. else
  277. {
  278. values[0] = Pi + atan2(p.sfy * m.array[p.fy], p.sfx * m.array[p.fx]);
  279. values[1] = (v * p.s1) < 0 ? -Pi/2 : Pi/2;
  280. values[2] = 0;
  281. }
  282. this = { values[p.yaw], values[p.pitch], values[p.roll] };
  283. }
  284. void FromQuaternion(const Quaternion q, EulerRotationOrder order)
  285. {
  286. Matrix m;
  287. m.RotationQuaternion(q);
  288. FromMatrix(m, order);
  289. }
  290. property Quaternion
  291. {
  292. // NOTE: This assumes yaw, pitch, roll (yxz) order
  293. set
  294. {
  295. double y = 2 * ( value.y*value.z + value.w*value.x );
  296. if(fabs(y) <= 1.0 - 0.000005)
  297. {
  298. double x = 2 * ( value.x*value.z - value.w*value.y );
  299. double z = 1 - 2 * ( value.x*value.x + value.y*value.y );
  300. Angle yaw = -atan2(x, z);
  301. Angle pitch = atan2(y, sqrt(x * x + z * z));
  302. double sYaw = sin( yaw / 2 );
  303. double cYaw = cos( yaw / 2 );
  304. double sPitch = sin( pitch / 2 );
  305. double cPitch = cos( pitch / 2 );
  306. Quaternion yp = { cPitch * cYaw, sPitch * cYaw, cPitch * sYaw, sPitch * sYaw };
  307. double rollW = yp.w * value.w + yp.x * value.x + yp.y * value.y + yp.z * value.z;
  308. double rollZ = yp.w * value.z + yp.x * value.y - yp.y * value.x - yp.z * value.w;
  309. this.yaw = yaw;
  310. this.pitch = pitch;
  311. this.roll = atan2(rollZ, rollW) * 2;
  312. }
  313. else
  314. {
  315. // 90 degrees pitch case
  316. double sin45 = sin(Pi/4);
  317. double yawW = sin45 * value.w + sin45 * value.x;
  318. double yawY = sin45 * value.y + sin45 * value.z;
  319. this.yaw = atan2(yawY, yawW) * 2;
  320. this.pitch = Pi/2;
  321. this.roll = 0;
  322. }
  323. }
  324. get
  325. {
  326. double sYaw = sin( yaw / 2 );
  327. double cYaw = cos( yaw / 2 );
  328. double sPitch = sin( pitch / 2 );
  329. double cPitch = cos( pitch / 2 );
  330. double sRoll = sin( roll / 2 );
  331. double cRoll = cos( roll / 2 );
  332. Quaternion yp = { cPitch * cYaw, sPitch * cYaw, cPitch * sYaw, sPitch * sYaw };
  333. value.w = yp.w * cRoll - yp.z * sRoll;
  334. value.x = yp.x * cRoll - yp.y * sRoll;
  335. value.y = yp.y * cRoll + yp.x * sRoll;
  336. value.z = yp.z * cRoll + yp.w * sRoll;
  337. }
  338. };
  339. void Add(Euler e1, Euler e2)
  340. {
  341. yaw = e1.yaw + e2.yaw;
  342. pitch = e1.pitch + e2.pitch;
  343. roll = e1.roll + e2.roll;
  344. }
  345. };