axelpale / affineplane

Affine 2D and 3D geometry library for JavaScript
https://axelpale.github.io/affineplane/
MIT License
10 stars 0 forks source link

Full 3D Helmert transformation #7

Open axelpale opened 2 years ago

axelpale commented 2 years ago

In affineplane 2.x the helm3 is quite limited in 3D and more like 2.3 dimensional variant of the transformation.

The limitation brings complications: in order to make each dimension to respect the scale uniformly, we need to know the scale in almost every operation we make. For x and y, the scale is factored in the a and b properties. For z however, we would need to compute the scale explicitly with square root and squares. Computationally this is not a big deal, adding one square root, two multiplications and one addition to the necessary functions. From architectural perspective it just feels very wrong to compute the scale over and over again.

For a reference on full 3D Helmert matrices, see: https://proj.org/operations/transformations/helmert.html

See also 3D affine coordinate transformations by Constantin-Octavian 2006.

See also Helmert transformation in Wikipedia.

axelpale commented 2 years ago

Approach 1: to avoid recomputation of scale, introduce a latent property c so that helm3 and so plane3 becomes { a, b, c, x, y, z } and where c^2 = a^2 + b^2. This approach frees us from recomputing the scale in every occasion. Downsides are that this feels a dirty optimisation step for a rare special case, although necessary for Tapspace.

Approach 2: jump directly to full 3D Helmert with seven properties: { s, rx, ry, rz, x, y, z }. As in Approach 1, the scale is stored explicitly as s. The props rx, ry, and rz are the rotation angles. This parametrisation, especially if stored like this, requires huge number of trigonometric function computations which is not ideal. The rotation matrix, when opened, yields 9 numbers to store in addition to the scale and translation, implying the total of 13 properties.

Approach 3: introduce the explicit scale property and decompose the scale away from a and b. This takes us to five independent parameters { s, rz, x, y, z } which we could store conveniently with six properties { s, cz, sz, x, y, z } where cz = cos(rz) and sz = sin(rz). Alternative namings could be { s, a, b, x, y, z } or { a, b, c, x, y, z } where c is the scale and a = cos(rz) and b = sin(rz).

axelpale commented 2 years ago

Approach 4: Use quaternions to store rotations. A quaternion is more compact and still quite efficient, requiring only four properties for rotation computations without trigonometric functions. The 3D Helmert transformation could be represented as { s, a, b, c, d, x, y, z } or { s, r, rx, ry, rz, x, y, z }. Some research exists, for example Quaternion-based Helmert transformations by Milenkovic 2015.

axelpale commented 2 years ago

Approach 4.2: It seems possible to store the scale into the quaternion so that scale = sqrt(a*a+b*b+c*c+d*d). This way the object representation becomes { a, b, c, d, x, y, z } which is nicely in line with the 2D { a, b, x, y }. Actually, this 4-parameter 2D transform representation can be seen to be based on a complex number a + bi.

axelpale commented 2 years ago

Finding 1: When restricting to rotations around z-axis, we can derive that { a, b, x, y } is actually special case { a, 0, 0, b, x, y, 0 } of the full quaternion helmert. Therefore it would be more consistent if helm2 has properties { a, d, x, y } and the full helm3 has { a, b, c, d, x, y, z }. This way we would not need to change the meaning of b later when the lib might have some users.

axelpale commented 2 years ago

After studying lots of quaternions, it seems common to denote scale with m for "magnitude" or "multiplier". Also, complex- and quaternion-based rotation usually requires unitary or orthogonal matrices for computation and algebra to work. In affineplane and tapspace code, it is common to compute the scale or magnitude from (a,b) with a square root, two multiplications and one addition. The scale is especially needed in inversions, that are very common operation in tapspace.

Therefore it seems practical to use 8-parameter { m, a, b, c, d, x, y, z } for full helm3 and also convert helm2 and helm23 to use m property: { m, a, b, x, y } and { m, a, b, x, y, z } where a*a + b*b = 1.

In the context of zoomable UIs and infinite scaling, it feels extremely practical to separate scale and rotation in order to extend the range of numerical stability.

These changes require new major version increment, affineplane v3.