-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathTransformable.ts
373 lines (319 loc) · 9.68 KB
/
Transformable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
import * as matrix from './matrix';
import * as vector from './vector';
const mIdentity = matrix.identity;
const EPSILON = 5e-5;
function isNotAroundZero(val: number) {
return val > EPSILON || val < -EPSILON;
}
const scaleTmp: vector.VectorArray = [];
const tmpTransform: matrix.MatrixArray = [];
const originTransform = matrix.create();
const abs = Math.abs;
class Transformable {
parent: Transformable
x: number
y: number
scaleX: number
scaleY: number
skewX: number
skewY: number
rotation: number
/**
* Will translated the element to the anchor position before applying other transforms.
*/
anchorX: number
anchorY: number
/**
* Origin of scale, rotation, skew
*/
originX: number
originY: number
/**
* Scale ratio
*/
globalScaleRatio: number
transform: matrix.MatrixArray
invTransform: matrix.MatrixArray
/**
* Get computed local transform
*/
getLocalTransform(m?: matrix.MatrixArray) {
return Transformable.getLocalTransform(this, m);
}
/**
* Set position from array
*/
setPosition(arr: number[]) {
this.x = arr[0];
this.y = arr[1];
}
/**
* Set scale from array
*/
setScale(arr: number[]) {
this.scaleX = arr[0];
this.scaleY = arr[1];
}
/**
* Set skew from array
*/
setSkew(arr: number[]) {
this.skewX = arr[0];
this.skewY = arr[1];
}
/**
* Set origin from array
*/
setOrigin(arr: number[]) {
this.originX = arr[0];
this.originY = arr[1];
}
/**
* If needs to compute transform
*/
needLocalTransform(): boolean {
return isNotAroundZero(this.rotation)
|| isNotAroundZero(this.x)
|| isNotAroundZero(this.y)
|| isNotAroundZero(this.scaleX - 1)
|| isNotAroundZero(this.scaleY - 1)
|| isNotAroundZero(this.skewX)
|| isNotAroundZero(this.skewY);
}
/**
* Update global transform
*/
updateTransform() {
const parentTransform = this.parent && this.parent.transform;
const needLocalTransform = this.needLocalTransform();
let m = this.transform;
if (!(needLocalTransform || parentTransform)) {
m && mIdentity(m);
return;
}
m = m || matrix.create();
if (needLocalTransform) {
this.getLocalTransform(m);
}
else {
mIdentity(m);
}
// 应用父节点变换
if (parentTransform) {
if (needLocalTransform) {
matrix.mul(m, parentTransform, m);
}
else {
matrix.copy(m, parentTransform);
}
}
// 保存这个变换矩阵
this.transform = m;
this._resolveGlobalScaleRatio(m);
}
private _resolveGlobalScaleRatio(m: matrix.MatrixArray) {
const globalScaleRatio = this.globalScaleRatio;
if (globalScaleRatio != null && globalScaleRatio !== 1) {
this.getGlobalScale(scaleTmp);
const relX = scaleTmp[0] < 0 ? -1 : 1;
const relY = scaleTmp[1] < 0 ? -1 : 1;
const sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0;
const sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0;
m[0] *= sx;
m[1] *= sx;
m[2] *= sy;
m[3] *= sy;
}
this.invTransform = this.invTransform || matrix.create();
matrix.invert(this.invTransform, m);
}
/**
* Get computed global transform
* NOTE: this method will force update transform on all ancestors.
* Please be aware of the potential performance cost.
*/
getComputedTransform() {
let transformNode: Transformable = this;
const ancestors: Transformable[] = [];
while (transformNode) {
ancestors.push(transformNode);
transformNode = transformNode.parent;
}
// Update from topdown.
while (transformNode = ancestors.pop()) {
transformNode.updateTransform();
}
return this.transform;
}
setLocalTransform(m: vector.VectorArray) {
if (!m) {
// TODO return or set identity?
return;
}
let sx = m[0] * m[0] + m[1] * m[1];
let sy = m[2] * m[2] + m[3] * m[3];
const rotation = Math.atan2(m[1], m[0]);
const shearX = Math.PI / 2 + rotation - Math.atan2(m[3], m[2]);
sy = Math.sqrt(sy) * Math.cos(shearX);
sx = Math.sqrt(sx);
this.skewX = shearX;
this.skewY = 0;
this.rotation = -rotation;
this.x = +m[4];
this.y = +m[5];
this.scaleX = sx;
this.scaleY = sy;
this.originX = 0;
this.originY = 0;
}
/**
* 分解`transform`矩阵到`position`, `rotation`, `scale`
*/
decomposeTransform() {
if (!this.transform) {
return;
}
const parent = this.parent;
let m = this.transform;
if (parent && parent.transform) {
// Get local transform and decompose them to position, scale, rotation
matrix.mul(tmpTransform, parent.invTransform, m);
m = tmpTransform;
}
const ox = this.originX;
const oy = this.originY;
if (ox || oy) {
originTransform[4] = ox;
originTransform[5] = oy;
matrix.mul(tmpTransform, m, originTransform);
tmpTransform[4] -= ox;
tmpTransform[5] -= oy;
m = tmpTransform;
}
this.setLocalTransform(m);
}
/**
* Get global scale
*/
getGlobalScale(out?: vector.VectorArray): vector.VectorArray {
const m = this.transform;
out = out || [];
if (!m) {
out[0] = 1;
out[1] = 1;
return out;
}
out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
if (m[0] < 0) {
out[0] = -out[0];
}
if (m[3] < 0) {
out[1] = -out[1];
}
return out;
}
/**
* 变换坐标位置到 shape 的局部坐标空间
*/
transformCoordToLocal(x: number, y: number): number[] {
const v2 = [x, y];
const invTransform = this.invTransform;
if (invTransform) {
vector.applyTransform(v2, v2, invTransform);
}
return v2;
}
/**
* 变换局部坐标位置到全局坐标空间
*/
transformCoordToGlobal(x: number, y: number): number[] {
const v2 = [x, y];
const transform = this.transform;
if (transform) {
vector.applyTransform(v2, v2, transform);
}
return v2;
}
getLineScale() {
const m = this.transform;
// Get the line scale.
// Determinant of `m` means how much the area is enlarged by the
// transformation. So its square root can be used as a scale factor
// for width.
return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10
? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1]))
: 1;
}
copyTransform(source: Transformable) {
copyTransform(this, source);
}
static getLocalTransform(target: Transformable, m?: matrix.MatrixArray): matrix.MatrixArray {
m = m || [];
const ox = target.originX || 0;
const oy = target.originY || 0;
const sx = target.scaleX;
const sy = target.scaleY;
const ax = target.anchorX;
const ay = target.anchorY;
const rotation = target.rotation || 0;
const x = target.x;
const y = target.y;
const skewX = target.skewX ? Math.tan(target.skewX) : 0;
// TODO: zrender use different hand in coordinate system and y axis is inversed.
const skewY = target.skewY ? Math.tan(-target.skewY) : 0;
// The order of transform (-anchor * -origin * scale * skew * rotate * origin * translate).
// We merge (-origin * scale * skew) into one. Also did identity in these operations.
// origin
if (ox || oy || ax || ay) {
const dx = ox + ax;
const dy = oy + ay;
m[4] = -dx * sx - skewX * dy * sy;
m[5] = -dy * sy - skewY * dx * sx;
}
else {
m[4] = m[5] = 0;
}
// scale
m[0] = sx;
m[3] = sy;
// skew
m[1] = skewY * sx;
m[2] = skewX * sy;
// Apply rotation
rotation && matrix.rotate(m, m, rotation);
// Translate back from origin and apply translation
m[4] += ox + x;
m[5] += oy + y;
return m;
}
private static initDefaultProps = (function () {
const proto = Transformable.prototype;
proto.scaleX =
proto.scaleY =
proto.globalScaleRatio = 1;
proto.x =
proto.y =
proto.originX =
proto.originY =
proto.skewX =
proto.skewY =
proto.rotation =
proto.anchorX =
proto.anchorY = 0;
})()
};
export const TRANSFORMABLE_PROPS = [
'x', 'y', 'originX', 'originY', 'anchorX', 'anchorY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY'
] as const;
export type TransformProp = (typeof TRANSFORMABLE_PROPS)[number]
export function copyTransform(
target: Partial<Pick<Transformable, TransformProp>>,
source: Pick<Transformable, TransformProp>
) {
for (let i = 0; i < TRANSFORMABLE_PROPS.length; i++) {
const propName = TRANSFORMABLE_PROPS[i];
target[propName] = source[propName];
}
}
export default Transformable;