-
-
Notifications
You must be signed in to change notification settings - Fork 459
/
Copy pathshape.js
433 lines (384 loc) · 10.8 KB
/
shape.js
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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
import { Events } from './events.js';
import { Element } from './element.js';
import { Matrix } from './matrix.js';
import { Vector } from './vector.js';
import { getComputedMatrix } from './utils/math.js';
/**
* @name Two.Shape
* @class
* @extends Two.Element
* @description The foundational transformation object for the Two.js scenegraph.
*/
export class Shape extends Element {
/**
* @name Two.Shape#_flagMatrix
* @private
* @property {Boolean} - Determines whether the matrix needs updating.
*/
_flagMatrix = true;
/**
* @name Two.Shape#_flagScale
* @private
* @property {Boolean} - Determines whether the scale needs updating.
*/
_flagScale = false;
// Underlying Properties
/**
* @name Two.Shape#_matrix
* @private
* @property {Two.Matrix} - The matrix value of the shape's position, rotation, and scale.
*/
_matrix = null;
/**
* @name Two.Shape#_worldMatrix
* @private
* @property {Two.Matrix} - The matrix value of the shape's position, rotation, and scale in the scene.
*/
_worldMatrix = null;
/**
* @name Two.Shape#_position
* @private
* @property {Two.Vector} - The translation values as a {@link Two.Vector}.
*/
_position = null;
/**
* @name Two.Shape#_rotation
* @private
* @property {Number} - The rotation value in radians.
*/
_rotation = 0;
/**
* @name Two.Shape#_scale
* @private
* @property {Number|Two.Vector} - The scale value in Number. Can be a vector for non-uniform scaling.
*/
_scale = 1;
/**
* @name Two.Shape#_skewX
* @private
* @property {Number} - The rotation value in Number.
*/
_skewX = 0;
/**
* @name Two.Shape#_skewY
* @private
* @property {Number} - The rotation value in Number.
*/
_skewY = 0;
constructor() {
super();
for (let prop in proto) {
Object.defineProperty(this, prop, proto[prop]);
}
/**
* @name Two.Shape#renderer
* @property {Object}
* @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
* @nota-bene With the {@link Two.SVGRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
*/
this._renderer.flagMatrix = FlagMatrix.bind(this);
this.isShape = true;
/**
* @name Two.Shape#matrix
* @property {Two.Matrix}
* @description The transformation matrix of the shape.
* @nota-bene {@link Two.Shape#position}, {@link Two.Shape#rotation}, {@link Two.Shape#scale}, {@link Two.Shape#skewX}, and {@link Two.Shape#skewY} apply their values to the matrix when changed. The matrix is what is sent to the renderer to be drawn.
*/
this.matrix = new Matrix();
/**
* @name Two.Shape#worldMatrix
* @property {Two.Matrix}
* @description The transformation matrix of the shape in the scene.
*/
this.worldMatrix = new Matrix();
/**
* @name Two.Shape#position
* @property {Two.Vector} - The x and y value for where the shape is placed relative to its parent.
*/
this.position = new Vector();
/**
* @name Two.Shape#rotation
* @property {Number} - The value in Number for how much the shape is rotated relative to its parent.
*/
this.rotation = 0;
/**
* @name Two.Shape#scale
* @property {Number} - The value for how much the shape is scaled relative to its parent.
* @nota-bene This value can be replaced with a {@link Two.Vector} to do non-uniform scaling. e.g: `shape.scale = new Two.Vector(2, 1);`
*/
this.scale = 1;
/**
* @name Two.Shape#skewX
* @property {Number} - The value in Number for how much the shape is skewed relative to its parent.
* @description Skew the shape by an angle in the x axis direction.
*/
this.skewX = 0;
/**
* @name Two.Shape#skewY
* @property {Number} - The value in Number for how much the shape is skewed relative to its parent.
* @description Skew the shape by an angle in the y axis direction.
*/
this.skewY = 0;
}
/**
* @name Two.Shape.fromObject
* @function
* @param {Object} obj - Object notation of a {@link Two.Shape} to create a new instance
* @returns {Two.Shape}
* @description Create a new {@link Two.Shape} from an object notation of a {@link Two.Shape}.
* @nota-bene Works in conjunction with {@link Two.Shape#toObject}
*/
static fromObject(obj) {
const shape = new Shape().copy(obj);
if ('id' in obj) {
shape.id = obj.id;
}
return shape;
}
get renderer() {
return this._renderer;
}
set renderer(v) {
this._renderer = v;
}
/**
* @name Two.Shape#translation
* @description Alias for {@link Two.Shape#position}.
*/
get translation() {
return proto.position.get.apply(this, arguments);
}
set translation(v) {
proto.position.set.apply(this, arguments);
}
/**
* @name Two.Shape#addTo
* @function
* @param {Two.Group} group - The parent the shape adds itself to.
* @description Convenience method to add itself to the scenegraph.
*/
addTo(group) {
group.add(this);
return this;
}
/**
* @name Two.Shape#remove
* @function
* @description Remove self from the scene / parent.
*/
remove() {
if (!this.parent) {
return this;
}
this.parent.remove(this);
return this;
}
/**
* @name Two.Shape#copy
* @function
* @param {Two.Shape} shape
* @description Copy the properties of one {@link Two.Shape} onto another.
*/
copy(shape) {
super.copy.call(this, shape);
if ('position' in shape) {
if (shape.position instanceof Vector) {
this.position = shape.position;
} else {
this.position.copy(shape.position);
}
}
if ('rotation' in shape) {
this.rotation = shape.rotation;
}
if ('scale' in shape) {
this.scale =
typeof shape.scale === 'number' || shape.scale instanceof Vector
? shape.scale
: new Vector(shape.scale.x, shape.scale.y);
}
if ('skewX' in shape) {
this.skewX = shape.skewX;
}
if ('skewY' in shape) {
this.skewY = shape.skewY;
}
if ('matrix' in shape && shape.matrix.manual) {
this.matrix.copy(shape.matrix);
this.matrix.manual = true;
}
return this;
}
/**
* @name Two.Shape#clone
* @function
* @param {Two.Group} [parent] - Optional argument to automatically add the shape to a scenegraph.
* @returns {Two.Shape}
* @description Create a new {@link Two.Shape} with the same values as the current shape.
*/
clone(parent) {
const clone = new Shape();
clone.position.copy(this.position);
clone.rotation = this.rotation;
clone.scale = this.scale;
clone.skewX = this.skewX;
clone.skewY = this.skewY;
if (this.matrix.manual) {
clone.matrix.copy(this.matrix);
}
if (parent) {
parent.add(clone);
}
return clone._update();
}
/**
* @name Two.Shape#toObject
* @function
* @description Create a JSON compatible object that represents information of the shape.
* @nota-bene Works in conjunction with {@link Two.Shape.fromObject}
*/
toObject() {
const result = super.toObject.call(this);
result.renderer = { type: 'shape' };
result.isShape = true;
result.translation = this.translation.toObject();
result.rotation = this.translation.rotation;
result.scale =
this.scale instanceof Vector ? this.scale.toObject() : this.scale;
result.skewX = this.skewX;
result.skewY = this.skewY;
result.matrix = this.matrix.toObject();
return result;
}
/**
* @name Two.Shape#_update
* @function
* @private
* @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
* @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
* @nota-bene Try not to call this method more than once a frame.
*/
_update(bubbles) {
if (!this._matrix.manual && this._flagMatrix) {
this._matrix.identity().translate(this.position.x, this.position.y);
if (this._scale instanceof Vector) {
this._matrix.scale(this._scale.x, this._scale.y);
} else {
this._matrix.scale(this._scale);
}
this._matrix.rotate(this.rotation);
this._matrix.skewX(this.skewX);
this._matrix.skewY(this.skewY);
}
if (bubbles) {
if (this.parent && this.parent._update) {
this.parent._update();
}
}
return this;
}
/**
* @name Two.Shape#flagReset
* @function
* @private
* @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
*/
flagReset() {
this._flagMatrix = this._flagScale = false;
super.flagReset.call(this);
return this;
}
}
const proto = {
position: {
enumerable: true,
get: function () {
return this._position;
},
set: function (v) {
if (this._position) {
this._position.unbind(Events.Types.change, this._renderer.flagMatrix);
}
this._position = v;
this._position.bind(Events.Types.change, this._renderer.flagMatrix);
FlagMatrix.call(this);
},
},
rotation: {
enumerable: true,
get: function () {
return this._rotation;
},
set: function (v) {
this._rotation = v;
this._flagMatrix = true;
},
},
scale: {
enumerable: true,
get: function () {
return this._scale;
},
set: function (v) {
if (this._scale instanceof Vector) {
this._scale.unbind(Events.Types.change, this._renderer.flagMatrix);
}
this._scale = v;
if (this._scale instanceof Vector) {
this._scale.bind(Events.Types.change, this._renderer.flagMatrix);
}
this._flagMatrix = true;
this._flagScale = true;
},
},
skewX: {
enumerable: true,
get: function () {
return this._skewX;
},
set: function (v) {
this._skewX = v;
this._flagMatrix = true;
},
},
skewY: {
enumerable: true,
get: function () {
return this._skewY;
},
set: function (v) {
this._skewY = v;
this._flagMatrix = true;
},
},
matrix: {
enumerable: true,
get: function () {
return this._matrix;
},
set: function (v) {
this._matrix = v;
this._flagMatrix = true;
},
},
worldMatrix: {
enumerable: true,
get: function () {
// TODO: Make DRY
getComputedMatrix(this, this._worldMatrix);
return this._worldMatrix;
},
set: function (v) {
this._worldMatrix = v;
},
},
};
/**
* @name FlagMatrix
* @function
* @private
* @description Utility function used in conjunction with event handlers to update the flagMatrix of a shape.
*/
function FlagMatrix() {
this._flagMatrix = true;
}