您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

quaternion.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. /*
  2. * encantar.js
  3. * GPU-accelerated Augmented Reality for the web
  4. * Copyright (C) 2022-2024 Alexandre Martins <alemartf(at)gmail.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. * quaternion.ts
  20. * Quaternions
  21. */
  22. import Speedy from 'speedy-vision';
  23. import { SpeedyMatrix } from 'speedy-vision/types/core/speedy-matrix';
  24. import { IllegalArgumentError } from '../utils/errors';
  25. /** Small number */
  26. const EPSILON = 1e-6;
  27. // public / non-internal methods do not change the contents of the quaternion
  28. /**
  29. * Quaternion q = x i + y j + z k + w
  30. */
  31. export class Quaternion
  32. {
  33. /** x coordinate (imaginary) */
  34. private _x: number;
  35. /** y coordinate (imaginary) */
  36. private _y: number;
  37. /** z coordinate (imaginary) */
  38. private _z: number;
  39. /** w coordinate (real) */
  40. private _w: number;
  41. /**
  42. * Constructor
  43. * @param x x coordinate (imaginary)
  44. * @param y y coordinate (imaginary)
  45. * @param z z coordinate (imaginary)
  46. * @param w w coordinate (real)
  47. */
  48. constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1)
  49. {
  50. this._x = +x;
  51. this._y = +y;
  52. this._z = +z;
  53. this._w = +w;
  54. }
  55. /**
  56. * Instantiate an identity quaternion q = 1
  57. * @returns a new identity quaternion
  58. */
  59. static Identity(): Quaternion
  60. {
  61. return new Quaternion(0, 0, 0, 1);
  62. }
  63. /**
  64. * The x coordinate of the quaternion (imaginary)
  65. */
  66. get x(): number
  67. {
  68. return this._x;
  69. }
  70. /**
  71. * The y coordinate of the quaternion (imaginary)
  72. */
  73. get y(): number
  74. {
  75. return this._y;
  76. }
  77. /**
  78. * The z coordinate of the quaternion (imaginary)
  79. */
  80. get z(): number
  81. {
  82. return this._z;
  83. }
  84. /**
  85. * The w coordinate of the quaternion (real)
  86. */
  87. get w(): number
  88. {
  89. return this._w;
  90. }
  91. /**
  92. * The length of this quaternion
  93. * @returns sqrt(x^2 + y^2 + z^2 + w^2)
  94. */
  95. length(): number
  96. {
  97. const x = this._x;
  98. const y = this._y;
  99. const z = this._z;
  100. const w = this._w;
  101. return Math.sqrt(x*x + y*y + z*z + w*w);
  102. }
  103. /**
  104. * Clone this quaternion
  105. * @returns a clone of this quaternion
  106. */
  107. clone(): Quaternion
  108. {
  109. return new Quaternion(this._x, this._y, this._z, this._w);
  110. }
  111. /**
  112. * Check if this and q have the same coordinates
  113. * @param q a quaternion
  114. * @returns true if this and q have the same coordinates
  115. */
  116. equals(q: Quaternion): boolean
  117. {
  118. return this._w === q._w && this._x === q._x && this._y === q._y && this._z === q._z;
  119. }
  120. /**
  121. * Convert to string
  122. * @returns a string
  123. */
  124. toString(): string
  125. {
  126. const x = this._x.toFixed(4);
  127. const y = this._y.toFixed(4);
  128. const z = this._z.toFixed(4);
  129. const w = this._w.toFixed(4);
  130. return `Quaternion(${x},${y},${z},${w})`;
  131. }
  132. /**
  133. * Normalize this quaternion
  134. * @returns this quaternion, normalized
  135. * @internal
  136. */
  137. _normalize(): Quaternion
  138. {
  139. const length = this.length();
  140. if(length < EPSILON) // zero?
  141. return this;
  142. this._x /= length;
  143. this._y /= length;
  144. this._z /= length;
  145. this._w /= length;
  146. return this;
  147. }
  148. /**
  149. * Conjugate this quaternion
  150. * @returns this quaternion, conjugated
  151. * @internal
  152. */
  153. _conjugate(): Quaternion
  154. {
  155. this._x = -this._x;
  156. this._y = -this._y;
  157. this._z = -this._z;
  158. return this;
  159. }
  160. /**
  161. * Set the coordinates of this quaternion
  162. * @param x x-coordinate
  163. * @param y y-coordinate
  164. * @param z z-coordinate
  165. * @param w w-coordinate
  166. * @returns this quaternion
  167. * @internal
  168. */
  169. _set(x: number, y: number, z: number, w: number): Quaternion
  170. {
  171. this._x = +x;
  172. this._y = +y;
  173. this._z = +z;
  174. this._w = +w;
  175. return this;
  176. }
  177. /**
  178. * Copy q to this
  179. * @param q a quaternion
  180. * @returns this quaternion
  181. * @internal
  182. */
  183. _copyFrom(q: Quaternion): Quaternion
  184. {
  185. this._x = q._x;
  186. this._y = q._y;
  187. this._z = q._z;
  188. this._w = q._w;
  189. return this;
  190. }
  191. /**
  192. * Convert a quaternion to a 3x3 rotation matrix
  193. * @returns a 3x3 rotation matrix
  194. * @internal
  195. */
  196. _toRotationMatrix(): SpeedyMatrix
  197. {
  198. const length = this.length(); // should be ~ 1
  199. // sanity check
  200. if(length < EPSILON)
  201. return Speedy.Matrix.Eye(3);
  202. // let q = (x,y,z,w) be a unit quaternion
  203. const x = this._x / length;
  204. const y = this._y / length;
  205. const z = this._z / length;
  206. const w = this._w / length;
  207. /*
  208. Let q = x i + y j + z k + w be a unit quaternion and
  209. p = x_p i + y_p j + z_p k be a purely imaginary quaternion (w_p = 0)
  210. representing a vector or point P = (x_p, y_p, z_p) in 3D space.
  211. Let's rewrite q as q = v + w, where v = x i + y j + z k, and then
  212. substitute v by the unit vector u = v / |v|, so that q = |v| u + w.
  213. Since q is a unit quaternion, it follows that:
  214. 1 = |q|^2 = x^2 + y^2 + z^2 + w^2 = |v|^2 + w^2.
  215. Given that cos(t~)^2 + sin(t~)^2 = 1 for all real t~, there is a real t
  216. such that cos(t) = w and sin(t) = |v|. Let's rewrite q as:
  217. q = cos(t) + u sin(t)
  218. (since 0 <= |v| = sin(t), it follows that 0 <= t <= pi)
  219. A rotation of P, of 2t radians around axis u, can be computed as:
  220. r_q(p) = q p q*
  221. where q* is the conjugate of q. (note: since |q| = 1, q q* = q* q = 1)
  222. ---
  223. Let h = x_h i + y_h j + z_h k + w_h be a quaternion. The multplication
  224. q h can be performed by pre-multiplying h, written as a column vector,
  225. by the following L_q matrix:
  226. [ w -z y x ] [ x_h ]
  227. q h = L_q h = [ z w -x y ] [ y_h ]
  228. [ -y x w z ] [ z_h ]
  229. [ -x -y -z w ] [ w_h ]
  230. (expand q h = (x i + y j + z k + w) (x_h i + y_h j + z_h k + w_h) to see)
  231. Similarly, the product h q* can be expressed by pre-multiplying h by the
  232. following R_q* matrix:
  233. [ w -z y -x ] [ x_h ]
  234. h q* = R_q* h = [ z w -x -y ] [ y_h ]
  235. [ -y x w -z ] [ z_h ]
  236. [ x y z w ] [ w_h ]
  237. (expand h q* = (x_h i + y_h j + z_h k + w_h) (-x i - y j - z k + w) to see)
  238. ---
  239. Although quaternion multiplication is not commutative, it is associative,
  240. i.e., r_q(p) = (q p)q* = q(p q*). From the matrix equations above, it
  241. follows that r_q(p) can be expressed as R_q* L_q p = L_q R_q* p. If we
  242. define M_q = L_q R_q* = R_q* L_q, we can write r_q(p) = M_q p. Matrix M_q
  243. has the following form:
  244. [ w^2 + x^2 - y^2 - z^2 2xy - 2wz 2xz + 2wy 0 ]
  245. M_q = [ 2xy + 2wz w^2 - x^2 + y^2 - z^2 2yz - 2wx 0 ]
  246. [ 2xz - 2wy 2yz + 2wx w^2 - x^2 - y^2 + z^2 0 ]
  247. [ 0 0 0 1 ]
  248. Note: the bottom-right entry is x^2 + y^2 + z^2 + w^2 = |q|^2 = 1.
  249. Let M be the top-left 3x3 submatrix of M_q. A direct, but boring,
  250. computation shows that M'M = M M' = I, where M' is the transpose of M.
  251. In addition, det M = |q|^6 = +1. Therefore, M is a 3x3 rotation matrix.
  252. */
  253. const x2 = x*x, y2 = y*y, z2 = z*z;//, w2 = w*w;
  254. const xy = 2*x*y, xz = 2*x*z, yz = 2*y*z;
  255. const wx = 2*w*x, wy = 2*w*y, wz = 2*w*z;
  256. return Speedy.Matrix(3, 3, [
  257. 1-2*(y2+z2), xy+wz, xz-wy,
  258. xy-wz, 1-2*(x2+z2), yz+wx,
  259. xz+wy, yz-wx, 1-2*(x2+y2)
  260. ]);
  261. }
  262. /**
  263. * Convert a 3x3 rotation matrix to a unit quaternion
  264. * @param m a 3x3 rotation matrix. You should ensure that it is a rotation matrix
  265. * @returns this quaternion
  266. * @internal
  267. */
  268. _fromRotationMatrix(m: SpeedyMatrix): Quaternion
  269. {
  270. if(m.rows != 3 || m.columns != 3)
  271. throw new IllegalArgumentError();
  272. /*
  273. Let M be the rotation matrix defined above. We're going to find a
  274. unit quaternion q associated with M.
  275. Before we begin, note that q and (-q) encode the same rotation, for
  276. r_(-q)(p) = (-q)p(-q)* = (-1)q p (-1)q* = (-1)(-1)q p q* = = r_q(p).
  277. Quaternion multiplication is commutative when a factor is a scalar, i.e.,
  278. d p = p d for a real d and a quaternion p (check: distributive operation).
  279. The trace of M, denoted as tr M, is 3w^2 - x^2 - y^2 - z^2. Since |q| = 1,
  280. it follows that tr M = 3w^2 - (1 - w^2), which means that 4w^2 = 1 + tr M.
  281. That being the case, we can write:
  282. |w| = sqrt(1 + tr M) / 2
  283. We'll arbitrarily pick w >= 0, for q and (-q) encode the same rotation.
  284. Let mij denote the element at the i-th row and at the j-th column of M.
  285. A direct verification shows that m21 - m12 = 4wz. Since w >= 0, it follows
  286. that sign(z) = sign(m21 - m12). Similarly, sign(y) = sign(m13 - m31) and
  287. sign(x) = sign(m32 - m23).
  288. The quantity m11 + m22 is equal to 2w^2 - 2z^2, which means that 4z^2 =
  289. 4w^2 - 2(m11 + m22) = (1 + tr M) - 2(m11 + m22). Therefore, let's write:
  290. |z| = sqrt((1 + tr M) - 2(m11 + m22)) / 2
  291. Of course, z = |z| sign(z). Similarly,
  292. |y| = sqrt((1 + tr M) - 2(m11 + m33)) / 2
  293. |x| = sqrt((1 + tr M) - 2(m22 + m33)) / 2
  294. This gives (x, y, z, w).
  295. ---
  296. We quickly verify that (1 + tr M) - 2(m11 + m22) >= 0 if M is (really)
  297. a rotation matrix: (1 + tr M) - 2(m11 + m22) = 1 + tr M - 2(tr M - m33) =
  298. 1 - tr M + 2 m33 = 1 - (3w^2 - x^2 - y^2 - z^2) + 2(w^2 - x^2 - y^2 + z^2) =
  299. 1 - w^2 + z^2 = (x^2 + y^2 + z^2 + w^2) - w^2 + z^2 = x^2 + y^2 + 2 z^2 >= 0.
  300. Similarly, (1 + tr M) - 2(m11 + m33) >= 0 and (1 + tr M) - 2(m22 + m33) >= 0.
  301. */
  302. const data = m.read();
  303. const m11 = data[0], m21 = data[1], m31 = data[2],
  304. m12 = data[3], m22 = data[4], m32 = data[5],
  305. m13 = data[6], m23 = data[7], m33 = data[8];
  306. const tr = 1 + m11 + m22 + m33; // 1 + tr M
  307. const sx = +(m32 >= m23) - +(m32 < m23); // sign(x)
  308. const sy = +(m13 >= m31) - +(m13 < m31); // sign(y)
  309. const sz = +(m21 >= m12) - +(m21 < m12); // sign(z)
  310. const w = 0.5 * Math.sqrt(Math.max(0, tr)); // |w| = w
  311. const x = 0.5 * Math.sqrt(Math.max(0, tr - 2 * (m22 + m33))); // |x|
  312. const y = 0.5 * Math.sqrt(Math.max(0, tr - 2 * (m11 + m33))); // |y|
  313. const z = 0.5 * Math.sqrt(Math.max(0, tr - 2 * (m11 + m22))); // |z|
  314. const length = Math.sqrt(x*x + y*y + z*z + w*w); // should be ~ 1
  315. this._x = (x * sx) / length;
  316. this._y = (y * sy) / length;
  317. this._z = (z * sz) / length;
  318. this._w = w / length;
  319. return this;
  320. }
  321. }