|
@@ -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
|
}
|