ソースを参照

Add Transform.position, Transform.orientation, Transform.scale and Transform._decompose()

customisations
alemart 10ヶ月前
コミット
636da50646
2個のファイルの変更271行の追加9行の削除
  1. 26
    2
      docs/api/transform.md
  2. 245
    7
      src/geometry/transform.ts

+ 26
- 2
docs/api/transform.md ファイルの表示

@@ -1,6 +1,6 @@
1 1
 # Transform
2 2
 
3
-A Transform represents a rotation and a translation in 3D space.
3
+A Transform represents a position, a rotation and a scale in 3D space.
4 4
 
5 5
 ## Properties
6 6
 
@@ -14,4 +14,28 @@ A 4x4 matrix encoding the transform.
14 14
 
15 15
 `transform.inverse: Transform, read-only`
16 16
 
17
-The inverse transform.
17
+The inverse transform.
18
+
19
+## position
20
+
21
+`transform.position: Vector3, read-only`
22
+
23
+The 3D position encoded by the transform.
24
+
25
+*Since:* 0.4.0
26
+
27
+## orientation
28
+
29
+`transform.orientation: Quaternion, read-only`
30
+
31
+A unit [quaternion](quaternion.md) describing the rotational component of the transform.
32
+
33
+*Since:* 0.4.0
34
+
35
+## scale
36
+
37
+`transform.scale: Vector3, read-only`
38
+
39
+The scale encoded by the transform.
40
+
41
+*Since:* 0.4.0

+ 245
- 7
src/geometry/transform.ts ファイルの表示

@@ -23,7 +23,13 @@
23 23
 import Speedy from 'speedy-vision';
24 24
 import { SpeedyMatrix } from 'speedy-vision/types/core/speedy-matrix';
25 25
 import { Nullable } from '../utils/utils';
26
-import { IllegalArgumentError } from '../utils/errors';
26
+import { IllegalArgumentError, IllegalOperationError } from '../utils/errors';
27
+import { Vector3 } from './vector3';
28
+import { Quaternion } from './quaternion';
29
+
30
+/** Small number */
31
+const EPSILON = 1e-6;
32
+
27 33
 
28 34
 
29 35
 /**
@@ -37,6 +43,18 @@ export class Transform
37 43
     /** inverse transform, computed lazily */
38 44
     private _inverse: Nullable<Transform>;
39 45
 
46
+    /** position component, computed lazily */
47
+    private _position: Vector3;
48
+
49
+    /** orientation component, computed lazily */
50
+    private _orientation: Quaternion;
51
+
52
+    /** scale component, computed lazily */
53
+    private _scale: Vector3;
54
+
55
+    /** whether or not this transformation has been decomposed */
56
+    private _isDecomposed: boolean;
57
+
40 58
 
41 59
 
42 60
     /**
@@ -50,6 +68,11 @@ export class Transform
50 68
 
51 69
         this._matrix = matrix;
52 70
         this._inverse = null;
71
+
72
+        this._position = Vector3.Zero();
73
+        this._orientation = Quaternion.Identity();
74
+        this._scale = new Vector3(1, 1, 1);
75
+        this._isDecomposed = false;
53 76
     }
54 77
 
55 78
     /**
@@ -66,20 +89,161 @@ export class Transform
66 89
     get inverse(): Transform
67 90
     {
68 91
         if(this._inverse === null)
69
-            this._inverse = new Transform(Transform._invert(this._matrix));
92
+            this._inverse = new Transform(this._inverseMatrix());
70 93
 
71 94
         return this._inverse;
72 95
     }
73 96
 
74 97
     /**
75
-     * Compute the inverse of a transformation matrix
76
-     * @param matrix the transformation matrix to invert
98
+     * The 3D position encoded by the transform
99
+     */
100
+    get position(): Vector3
101
+    {
102
+        if(!this._isDecomposed)
103
+            this._decompose();
104
+
105
+        return this._position;
106
+    }
107
+
108
+    /**
109
+     * A unit quaternion describing the rotational component of the transform
110
+     */
111
+    get orientation(): Quaternion
112
+    {
113
+        if(!this._isDecomposed)
114
+            this._decompose();
115
+
116
+        return this._orientation;
117
+    }
118
+
119
+    /**
120
+     * The scale encoded by the transform
121
+     */
122
+    get scale(): Vector3
123
+    {
124
+        if(!this._isDecomposed)
125
+            this._decompose();
126
+
127
+        return this._scale;
128
+    }
129
+
130
+    /**
131
+     * Decompose this transform
132
+     */
133
+    private _decompose(): void
134
+    {
135
+        /*
136
+
137
+        The shape of a 4x4 transform T * R * S is
138
+
139
+        [  RS  t  ]
140
+        [  0'  1  ]
141
+
142
+        where S is a 3x3 diagonal matrix, R is a 3x3 rotation matrix, t is a
143
+        3x1 translation vector and 0' is a 1x3 zero vector.
144
+
145
+        How do we decompose it?
146
+
147
+        1) Decomposing the translation vector t is trivial
148
+
149
+        2) Decomposing matrices R (rotation) and S (scale) can be done by
150
+           noticing that (RS)'(RS) = (S'R')(RS) = S'(R'R) S = S'S is diagonal
151
+
152
+        3) Since R is a rotation matrix, we have det R = +1. This means that
153
+           det RS = det R * det S = det S. If det RS < 0, then we have a change
154
+           of handedness (i.e., a negative scale). We may flip the forward axis
155
+           (Z) and let the rotation matrix encode the rest of the transformation
156
+
157
+        4) Use 2) and 3) to find a suitable S
158
+
159
+        5) Compute R = (RS) * S^(-1)
160
+
161
+        */
162
+        const m = this._matrix.read();
163
+        const h = Math.abs(m[15]) < EPSILON ? Number.NaN : 1 / m[15]; // usually h = 1
164
+
165
+        // find t
166
+        const tx = m[12] * h;
167
+        const ty = m[13] * h;
168
+        const tz = m[14] * h;
169
+
170
+        // find RS
171
+        const rs11 = m[0] * h;
172
+        const rs21 = m[1] * h;
173
+        const rs31 = m[2] * h;
174
+        const rs12 = m[4] * h;
175
+        const rs22 = m[5] * h;
176
+        const rs32 = m[6] * h;
177
+        const rs13 = m[8] * h;
178
+        const rs23 = m[9] * h;
179
+        const rs33 = m[10] * h;
180
+
181
+        // do we have a change of handedness?
182
+        const det = rs13 * (rs21 * rs32 - rs22 * rs31) + rs33 * (rs11 * rs22 - rs12 * rs21) - rs23 * (rs11 * rs32 - rs12 * rs31);
183
+        const sign = +(det >= 0) - +(det < 0);
184
+
185
+        // if det = 0, RS is not invertible!
186
+
187
+        // find S
188
+        const sx = Math.sqrt(rs11 * rs11 + rs12 * rs12 + rs13 * rs13);
189
+        const sy = Math.sqrt(rs21 * rs21 + rs22 * rs22 + rs23 * rs23);
190
+        const sz = Math.sqrt(rs31 * rs31 + rs32 * rs32 + rs33 * rs33) * sign;
191
+
192
+        // zero scale?
193
+        if(sx < EPSILON || sy < EPSILON || sz * sign < EPSILON) {
194
+            this._position._set(tx, ty, tz);
195
+            this._scale._set(sx, sy, sz);
196
+            this._orientation._copyFrom(Quaternion.Identity());
197
+            this._isDecomposed = true;
198
+            return;
199
+        }
200
+
201
+        // find S^(-1)
202
+        const zx = 1 / sx;
203
+        const zy = 1 / sy;
204
+        const zz = 1 / sz;
205
+
206
+        // find R
207
+        const r11 = rs11 * zx;
208
+        const r21 = rs21 * zx;
209
+        const r31 = rs31 * zx;
210
+        const r12 = rs12 * zy;
211
+        const r22 = rs22 * zy;
212
+        const r32 = rs32 * zy;
213
+        const r13 = rs13 * zz;
214
+        const r23 = rs23 * zz;
215
+        const r33 = rs33 * zz;
216
+
217
+        // set the components
218
+        this._position._set(tx, ty, tz);
219
+        this._scale._set(sx, sy, sz);
220
+        this._orientation._fromRotationMatrix(Speedy.Matrix(3, 3, [
221
+            r11, r21, r31,
222
+            r12, r22, r32,
223
+            r13, r23, r33
224
+        ]));
225
+
226
+        // done!
227
+        this._isDecomposed = true;
228
+    }
229
+
230
+    /**
231
+     * Compute the inverse matrix of this transform
77 232
      * @returns the inverse matrix
78 233
      */
79
-    private static _invert(matrix: SpeedyMatrix): SpeedyMatrix
234
+    private _inverseMatrix(): SpeedyMatrix
80 235
     {
236
+        // test
237
+        //console.log(Speedy.Matrix(this._matrix.inverse().times(this._matrix)).toString());
238
+
239
+        // this works, but this inverse is straightforward
240
+        return Speedy.Matrix(this._matrix.inverse());
241
+
81 242
         /*
82 243
 
244
+        Simple analytic method
245
+        ----------------------
246
+
83 247
         The inverse of a 4x4 transform T * R * S
84 248
 
85 249
         [  RS  t  ]    is    [  ZR' -ZR't ]
@@ -91,7 +255,81 @@ export class Transform
91 255
 
92 256
         */
93 257
 
94
-        // this works, but this inverse is straightforward
95
-        return Speedy.Matrix(matrix.inverse());
258
+        /*
259
+        // decompose the transform
260
+        if(!this._isDecomposed)
261
+            this._decompose();
262
+
263
+        // find t
264
+        const tx = this._position.x;
265
+        const ty = this._position.y;
266
+        const tz = this._position.z;
267
+
268
+        // find S (typically 1, but not very accurate)
269
+        const sx = this._scale.x;
270
+        const sy = this._scale.y;
271
+        const sz = this._scale.z;
272
+
273
+        // sanity check
274
+        if(Math.abs(sx) < EPSILON || Math.abs(sy) < EPSILON || Math.abs(sz) < EPSILON) {
275
+            //throw new IllegalOperationError('Not an invertible transform: ' + this._matrix.toString());
276
+            return Speedy.Matrix(4, 4, new Array(16).fill(Number.NaN)); // more friendly behavior
277
+        }
278
+
279
+        // find R
280
+        const r = this._rotation.read();
281
+        const r11 = r[0];
282
+        const r21 = r[1];
283
+        const r31 = r[2];
284
+        const r12 = r[3];
285
+        const r22 = r[4];
286
+        const r32 = r[5];
287
+        const r13 = r[6];
288
+        const r23 = r[7];
289
+        const r33 = r[8];
290
+
291
+        // find Z = S^(-1)
292
+        const zx = 1 / sx;
293
+        const zy = 1 / sy;
294
+        const zz = 1 / sz;
295
+
296
+        // compute Z R'
297
+        const zr11 = zx * r11;
298
+        const zr21 = zy * r12;
299
+        const zr31 = zz * r13;
300
+        const zr12 = zx * r21;
301
+        const zr22 = zy * r22;
302
+        const zr32 = zz * r23;
303
+        const zr13 = zx * r31;
304
+        const zr23 = zy * r32;
305
+        const zr33 = zz * r33;
306
+
307
+        // compute -Z R't
308
+        const zrt1 = -(tx * zr11 + ty * zr12 + tz * zr13);
309
+        const zrt2 = -(tx * zr21 + ty * zr22 + tz * zr23);
310
+        const zrt3 = -(tx * zr31 + ty * zr32 + tz * zr33);
311
+
312
+        // test
313
+        console.log('inverse', Speedy.Matrix(Speedy.Matrix(4, 4, [
314
+            zr11, zr21, zr31, 0,
315
+            zr12, zr22, zr32, 0,
316
+            zr13, zr23, zr33, 0,
317
+            zrt1, zrt2, zrt3, 1
318
+        ]).times(this._matrix)).toString());
319
+
320
+        console.log('rotation', Speedy.Matrix(
321
+            this._rotation.transpose().times(this._rotation)
322
+        ).toString());
323
+
324
+        console.log('scale', this._scale);
325
+
326
+        // done!
327
+        return Speedy.Matrix(4, 4, [
328
+            zr11, zr21, zr31, 0,
329
+            zr12, zr22, zr32, 0,
330
+            zr13, zr23, zr33, 0,
331
+            zrt1, zrt2, zrt3, 1
332
+        ]);
333
+        */
96 334
     }
97 335
 }

読み込み中…
キャンセル
保存