Quat.hx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package h3d;
  2. using hxd.Math;
  3. @:noDebug
  4. class Quat {
  5. public var x : Float;
  6. public var y : Float;
  7. public var z : Float;
  8. public var w : Float;
  9. public inline function new( x = 0., y = 0., z = 0., w = 1. ) {
  10. this.x = x;
  11. this.y = y;
  12. this.z = z;
  13. this.w = w;
  14. }
  15. public inline function set(x, y, z, w) {
  16. this.x = x;
  17. this.y = y;
  18. this.z = z;
  19. this.w = w;
  20. }
  21. public inline function identity() {
  22. x = y = z = 0;
  23. w = 1;
  24. }
  25. public inline function lengthSq() {
  26. return x * x + y * y + z * z + w * w;
  27. }
  28. public inline function length() {
  29. return lengthSq().sqrt();
  30. }
  31. public inline function load( q : Quat ) {
  32. this.x = q.x;
  33. this.y = q.y;
  34. this.z = q.z;
  35. this.w = q.w;
  36. }
  37. public function clone() {
  38. return new Quat(x, y, z, w);
  39. }
  40. public function initMoveTo( from : Vector, to : Vector ) {
  41. // H = Normalize(From + To)
  42. // Q = (From ^ H, From . H)
  43. //
  44. // We have an issue when From.To moves towards -1, a small tilt on From ^ To will result in big tilt.
  45. var hx = from.x + to.x;
  46. var hy = from.y + to.y;
  47. var hz = from.z + to.z;
  48. x = from.y * hz - from.z * hy;
  49. y = from.z * hx - from.x * hz;
  50. z = from.x * hy - from.y * hx;
  51. w = from.x * hx + from.y * hy + from.z * hz;
  52. normalize();
  53. }
  54. public function initNormal( dir : h3d.col.Point, rotate : Float = 0.0 ) {
  55. var dir = dir.normalized();
  56. if( dir.x*dir.x+dir.y*dir.y < Math.EPSILON2 )
  57. initDirection(new h3d.Vector(1,0,0));
  58. else {
  59. var ay = new h3d.col.Point(dir.x, dir.y, 0).normalized();
  60. var az = dir.cross(ay);
  61. var ax = dir.cross(az).toVector();
  62. if (dir.z < 0.0)
  63. initDirection(ax, new Vector(0.0, 0.0, -1.0));
  64. else
  65. initDirection(ax);
  66. }
  67. if ( rotate != 0.0) {
  68. var quat = new Quat();
  69. quat.initRotateAxis(dir.x, dir.y, dir.z, rotate);
  70. multiply(quat, this);
  71. }
  72. }
  73. public function initDirection( dir : Vector, ?up : Vector ) {
  74. // inlined version of initRotationMatrix(Matrix.lookAtX(dir))
  75. var ax = dir.clone().normalized();
  76. var ay = new Vector(-ax.y, ax.x, 0);
  77. if( up != null )
  78. ay.load(up.cross(ax));
  79. ay.normalize();
  80. if( ay.lengthSq() < Math.EPSILON2 ) {
  81. ay.x = ax.y;
  82. ay.y = ax.z;
  83. ay.z = ax.x;
  84. }
  85. var az = ax.cross(ay);
  86. var tr = ax.x + ay.y + az.z;
  87. if( tr > 0 ) {
  88. var s = (tr + 1.0).sqrt() * 2;
  89. var ins = 1 / s;
  90. x = (ay.z - az.y) * ins;
  91. y = (az.x - ax.z) * ins;
  92. z = (ax.y - ay.x) * ins;
  93. w = 0.25 * s;
  94. } else if( ax.x > ay.y && ax.x > az.z ) {
  95. var s = (1.0 + ax.x - ay.y - az.z).sqrt() * 2;
  96. var ins = 1 / s;
  97. x = 0.25 * s;
  98. y = (ay.x + ax.y) * ins;
  99. z = (az.x + ax.z) * ins;
  100. w = (ay.z - az.y) * ins;
  101. } else if( ay.y > az.z ) {
  102. var s = (1.0 + ay.y - ax.x - az.z).sqrt() * 2;
  103. var ins = 1 / s;
  104. x = (ay.x + ax.y) * ins;
  105. y = 0.25 * s;
  106. z = (az.y + ay.z) * ins;
  107. w = (az.x - ax.z) * ins;
  108. } else {
  109. var s = (1.0 + az.z - ax.x - ay.y).sqrt() * 2;
  110. var ins = 1 / s;
  111. x = (az.x + ax.z) * ins;
  112. y = (az.y + ay.z) * ins;
  113. z = 0.25 * s;
  114. w = (ax.y - ay.x) * ins;
  115. }
  116. }
  117. public function initRotateAxis( x : Float, y : Float, z : Float, a : Float ) {
  118. var sin = (a / 2).sin();
  119. var cos = (a / 2).cos();
  120. this.x = x * sin;
  121. this.y = y * sin;
  122. this.z = z * sin;
  123. this.w = cos * (x * x + y * y + z * z).sqrt(); // allow not normalized axis
  124. normalize();
  125. }
  126. public function initRotateMatrix( m : Matrix ) {
  127. var tr = m._11 + m._22 + m._33;
  128. if( tr > 0 ) {
  129. var s = (tr + 1.0).sqrt() * 2;
  130. var ins = 1 / s;
  131. x = (m._23 - m._32) * ins;
  132. y = (m._31 - m._13) * ins;
  133. z = (m._12 - m._21) * ins;
  134. w = 0.25 * s;
  135. } else if( m._11 > m._22 && m._11 > m._33 ) {
  136. var s = (1.0 + m._11 - m._22 - m._33).sqrt() * 2;
  137. var ins = 1 / s;
  138. x = 0.25 * s;
  139. y = (m._21 + m._12) * ins;
  140. z = (m._31 + m._13) * ins;
  141. w = (m._23 - m._32) * ins;
  142. } else if( m._22 > m._33 ) {
  143. var s = (1.0 + m._22 - m._11 - m._33).sqrt() * 2;
  144. var ins = 1 / s;
  145. x = (m._21 + m._12) * ins;
  146. y = 0.25 * s;
  147. z = (m._32 + m._23) * ins;
  148. w = (m._31 - m._13) * ins;
  149. } else {
  150. var s = (1.0 + m._33 - m._11 - m._22).sqrt() * 2;
  151. var ins = 1 / s;
  152. x = (m._31 + m._13) * ins;
  153. y = (m._32 + m._23) * ins;
  154. z = 0.25 * s;
  155. w = (m._12 - m._21) * ins;
  156. }
  157. }
  158. public function normalize() {
  159. var len = x * x + y * y + z * z + w * w;
  160. if( len < hxd.Math.EPSILON2 ) {
  161. x = y = z = 0;
  162. w = 1;
  163. } else {
  164. var m = len.invSqrt();
  165. x *= m;
  166. y *= m;
  167. z *= m;
  168. w *= m;
  169. }
  170. }
  171. public function initRotation( ax : Float, ay : Float, az : Float ) {
  172. var sinX = ( ax * 0.5 ).sin();
  173. var cosX = ( ax * 0.5 ).cos();
  174. var sinY = ( ay * 0.5 ).sin();
  175. var cosY = ( ay * 0.5 ).cos();
  176. var sinZ = ( az * 0.5 ).sin();
  177. var cosZ = ( az * 0.5 ).cos();
  178. var cosYZ = cosY * cosZ;
  179. var sinYZ = sinY * sinZ;
  180. x = sinX * cosYZ - cosX * sinYZ;
  181. y = cosX * sinY * cosZ + sinX * cosY * sinZ;
  182. z = cosX * cosY * sinZ - sinX * sinY * cosZ;
  183. w = cosX * cosYZ + sinX * sinYZ;
  184. }
  185. public function multiply( q1 : Quat, q2 : Quat ) {
  186. var x2 = q1.x * q2.w + q1.w * q2.x + q1.y * q2.z - q1.z * q2.y;
  187. var y2 = q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x;
  188. var z2 = q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w;
  189. var w2 = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z;
  190. x = x2;
  191. y = y2;
  192. z = z2;
  193. w = w2;
  194. }
  195. public function toEuler() {
  196. return toMatrix().getEulerAngles();
  197. }
  198. public inline function lerp( q1 : Quat, q2 : Quat, v : Float, nearest = false ) {
  199. var v2 = 1 - v;
  200. if( nearest && q1.dot(q2) < 0 )
  201. v = -v;
  202. var x = q1.x * v2 + q2.x * v;
  203. var y = q1.y * v2 + q2.y * v;
  204. var z = q1.z * v2 + q2.z * v;
  205. var w = q1.w * v2 + q2.w * v;
  206. this.x = x;
  207. this.y = y;
  208. this.z = z;
  209. this.w = w;
  210. }
  211. public function slerp( q1 : Quat, q2 : Quat, v : Float ) {
  212. var cosHalfTheta = q1.dot(q2);
  213. if( cosHalfTheta.abs() >= 1 ) {
  214. this.x = q1.x;
  215. this.y = q1.y;
  216. this.z = q1.z;
  217. this.w = q1.w;
  218. return;
  219. }
  220. var halfTheta = cosHalfTheta.acos();
  221. var invSinHalfTheta = (1 - cosHalfTheta * cosHalfTheta).invSqrt();
  222. if( invSinHalfTheta.abs() > 1e3 ) {
  223. this.lerp(q1, q2, 0.5, true);
  224. return;
  225. }
  226. var a = ((1 - v) * halfTheta).sin() * invSinHalfTheta;
  227. var b = (v * halfTheta).sin() * invSinHalfTheta * (cosHalfTheta < 0 ? -1 : 1);
  228. this.x = q1.x * a + q2.x * b;
  229. this.y = q1.y * a + q2.y * b;
  230. this.z = q1.z * a + q2.z * b;
  231. this.w = q1.w * a + q2.w * b;
  232. }
  233. public inline function conjugate() {
  234. x = -x;
  235. y = -y;
  236. z = -z;
  237. }
  238. /**
  239. Makes a unit quaternion to the power of the value.
  240. **/
  241. public inline function pow( v : Float ) {
  242. // ln()
  243. var r = Math.sqrt(x*x+y*y+z*z);
  244. var t = r > Math.EPSILON ? Math.atan2(r,w)/r : 0;
  245. w = 0.5 * hxd.Math.log(w*w+x*x+y*y+z*z);
  246. x *= t;
  247. y *= t;
  248. z *= t;
  249. // scale
  250. x *= v;
  251. y *= v;
  252. z *= v;
  253. w *= v;
  254. // exp
  255. var r = Math.sqrt(x*x+y*y+z*z);
  256. var et = hxd.Math.exp(w);
  257. var s = r > Math.EPSILON ? et *Math.sin(r)/r : 0;
  258. w = et * Math.cos(r);
  259. x *= s;
  260. y *= s;
  261. z *= s;
  262. }
  263. /**
  264. Negate the quaternion: this will not change the actual angle, use `conjugate` for that.
  265. **/
  266. public inline function negate() {
  267. x = -x;
  268. y = -y;
  269. z = -z;
  270. w = -w;
  271. }
  272. public inline function dot( q : Quat ) {
  273. return x * q.x + y * q.y + z * q.z + w * q.w;
  274. }
  275. public inline function getDirection() {
  276. return new h3d.Vector(1 - 2 * ( y * y + z * z ), 2 * ( x * y + z * w ), 2 * ( x * z - y * w ));
  277. }
  278. public inline function getUpAxis() {
  279. return new h3d.Vector(2 * ( x*z + y*w ),2 * ( y*z - x*w ), 1 - 2 * ( x*x + y*y ));
  280. }
  281. /**
  282. Save to a Left-Handed matrix
  283. **/
  284. public function toMatrix( ?m : h3d.Matrix ) {
  285. if( m == null ) m = new h3d.Matrix();
  286. var xx = x * x;
  287. var xy = x * y;
  288. var xz = x * z;
  289. var xw = x * w;
  290. var yy = y * y;
  291. var yz = y * z;
  292. var yw = y * w;
  293. var zz = z * z;
  294. var zw = z * w;
  295. m._11 = 1 - 2 * ( yy + zz );
  296. m._12 = 2 * ( xy + zw );
  297. m._13 = 2 * ( xz - yw );
  298. m._14 = 0;
  299. m._21 = 2 * ( xy - zw );
  300. m._22 = 1 - 2 * ( xx + zz );
  301. m._23 = 2 * ( yz + xw );
  302. m._24 = 0;
  303. m._31 = 2 * ( xz + yw );
  304. m._32 = 2 * ( yz - xw );
  305. m._33 = 1 - 2 * ( xx + yy );
  306. m._34 = 0;
  307. m._41 = 0;
  308. m._42 = 0;
  309. m._43 = 0;
  310. m._44 = 1;
  311. return m;
  312. }
  313. public function toString() {
  314. return '{${x.fmt()},${y.fmt()},${z.fmt()},${w.fmt()}}';
  315. }
  316. /**
  317. Blends the sourceQuats together with the given weights and store the result in `this`.
  318. ReferenceQuat is the default rotation to use as the base for the blend
  319. (for example the default rotation of a bone in a skeletal mesh)
  320. **/
  321. public function weightedBlend(sourceQuats: Array<Quat>, weights: Array<Float>, referenceQuat: Quat) {
  322. // Algorithm from https://theorangeduck.com/page/quaternion-weighted-average
  323. this.set(0,0,0,0);
  324. var mulRes = inline new h3d.Quat();
  325. var invRef = inline referenceQuat.clone();
  326. inline invRef.conjugate();
  327. for (index => rotation in sourceQuats) {
  328. var weight = weights[index];
  329. inline mulRes.multiply(invRef, rotation);
  330. if (mulRes.w < 0) inline mulRes.negate();
  331. mulRes.w *= weight;
  332. mulRes.x *= weight;
  333. mulRes.y *= weight;
  334. mulRes.z *= weight;
  335. this.w += mulRes.w;
  336. this.x += mulRes.x;
  337. this.y += mulRes.y;
  338. this.z += mulRes.z;
  339. }
  340. inline this.normalize();
  341. inline this.multiply(referenceQuat, this);
  342. if (this.w < 0) inline this.negate();
  343. }
  344. }