@scratch/scratch-render 11.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/CHANGELOG.md +2281 -0
- package/LICENSE +12 -0
- package/README.md +87 -0
- package/TRADEMARK +1 -0
- package/commitlint.config.js +4 -0
- package/docs/Rectangle-AABB-Matrix.md +192 -0
- package/package.json +84 -0
- package/release.config.js +10 -0
- package/src/BitmapSkin.js +120 -0
- package/src/Drawable.js +734 -0
- package/src/EffectTransform.js +197 -0
- package/src/PenSkin.js +350 -0
- package/src/Rectangle.js +196 -0
- package/src/RenderConstants.js +34 -0
- package/src/RenderWebGL.js +2029 -0
- package/src/SVGSkin.js +239 -0
- package/src/ShaderManager.js +187 -0
- package/src/Silhouette.js +257 -0
- package/src/Skin.js +235 -0
- package/src/TextBubbleSkin.js +284 -0
- package/src/index.js +7 -0
- package/src/playground/getMousePosition.js +37 -0
- package/src/playground/index.html +41 -0
- package/src/playground/playground.js +202 -0
- package/src/playground/queryPlayground.html +73 -0
- package/src/playground/queryPlayground.js +196 -0
- package/src/playground/style.css +11 -0
- package/src/shaders/sprite.frag +249 -0
- package/src/shaders/sprite.vert +75 -0
- package/src/util/canvas-measurement-provider.js +41 -0
- package/src/util/color-conversions.js +97 -0
- package/src/util/log.js +4 -0
- package/src/util/text-wrapper.js +112 -0
package/src/Drawable.js
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
const twgl = require('twgl.js');
|
|
2
|
+
|
|
3
|
+
const Rectangle = require('./Rectangle');
|
|
4
|
+
const RenderConstants = require('./RenderConstants');
|
|
5
|
+
const ShaderManager = require('./ShaderManager');
|
|
6
|
+
const Skin = require('./Skin');
|
|
7
|
+
const EffectTransform = require('./EffectTransform');
|
|
8
|
+
const log = require('./util/log');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An internal workspace for calculating texture locations from world vectors
|
|
12
|
+
* this is REUSED for memory conservation reasons
|
|
13
|
+
* @type {twgl.v3}
|
|
14
|
+
*/
|
|
15
|
+
const __isTouchingPosition = twgl.v3.create();
|
|
16
|
+
const FLOATING_POINT_ERROR_ALLOWANCE = 1e-6;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert a scratch space location into a texture space float. Uses the
|
|
20
|
+
* internal __isTouchingPosition as a return value, so this should be copied
|
|
21
|
+
* if you ever need to get two local positions and store both. Requires that
|
|
22
|
+
* the drawable inverseMatrix is up to date.
|
|
23
|
+
*
|
|
24
|
+
* @param {Drawable} drawable The drawable to get the inverse matrix and uniforms from
|
|
25
|
+
* @param {twgl.v3} vec [x,y] scratch space vector
|
|
26
|
+
* @return {twgl.v3} [x,y] texture space float vector - transformed by effects and matrix
|
|
27
|
+
*/
|
|
28
|
+
const getLocalPosition = (drawable, vec) => {
|
|
29
|
+
// Transfrom from world coordinates to Drawable coordinates.
|
|
30
|
+
const localPosition = __isTouchingPosition;
|
|
31
|
+
const v0 = vec[0];
|
|
32
|
+
const v1 = vec[1];
|
|
33
|
+
const m = drawable._inverseMatrix;
|
|
34
|
+
// var v2 = v[2];
|
|
35
|
+
const d = (v0 * m[3]) + (v1 * m[7]) + m[15];
|
|
36
|
+
// The RenderWebGL quad flips the texture's X axis. So rendered bottom
|
|
37
|
+
// left is 1, 0 and the top right is 0, 1. Flip the X axis so
|
|
38
|
+
// localPosition matches that transformation.
|
|
39
|
+
localPosition[0] = 0.5 - (((v0 * m[0]) + (v1 * m[4]) + m[12]) / d);
|
|
40
|
+
localPosition[1] = (((v0 * m[1]) + (v1 * m[5]) + m[13]) / d) + 0.5;
|
|
41
|
+
// Fix floating point issues near 0. Filed https://github.com/LLK/scratch-render/issues/688 that
|
|
42
|
+
// they're happening in the first place.
|
|
43
|
+
// TODO: Check if this can be removed after render pull 479 is merged
|
|
44
|
+
if (Math.abs(localPosition[0]) < FLOATING_POINT_ERROR_ALLOWANCE) localPosition[0] = 0;
|
|
45
|
+
if (Math.abs(localPosition[1]) < FLOATING_POINT_ERROR_ALLOWANCE) localPosition[1] = 0;
|
|
46
|
+
// Apply texture effect transform if the localPosition is within the drawable's space,
|
|
47
|
+
// and any effects are currently active.
|
|
48
|
+
if (drawable.enabledEffects !== 0 &&
|
|
49
|
+
(localPosition[0] >= 0 && localPosition[0] < 1) &&
|
|
50
|
+
(localPosition[1] >= 0 && localPosition[1] < 1)) {
|
|
51
|
+
|
|
52
|
+
EffectTransform.transformPoint(drawable, localPosition, localPosition);
|
|
53
|
+
}
|
|
54
|
+
return localPosition;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
class Drawable {
|
|
58
|
+
/**
|
|
59
|
+
* An object which can be drawn by the renderer.
|
|
60
|
+
* @todo double-buffer all rendering state (position, skin, effects, etc.)
|
|
61
|
+
* @param {!int} id - This Drawable's unique ID.
|
|
62
|
+
* @constructor
|
|
63
|
+
*/
|
|
64
|
+
constructor (id) {
|
|
65
|
+
/** @type {!int} */
|
|
66
|
+
this._id = id;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The uniforms to be used by the vertex and pixel shaders.
|
|
70
|
+
* Some of these are used by other parts of the renderer as well.
|
|
71
|
+
* @type {Object.<string,*>}
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
this._uniforms = {
|
|
75
|
+
/**
|
|
76
|
+
* The model matrix, to concat with projection at draw time.
|
|
77
|
+
* @type {module:twgl/m4.Mat4}
|
|
78
|
+
*/
|
|
79
|
+
u_modelMatrix: twgl.m4.identity(),
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The color to use in the silhouette draw mode.
|
|
83
|
+
* @type {Array<number>}
|
|
84
|
+
*/
|
|
85
|
+
u_silhouetteColor: Drawable.color4fFromID(this._id)
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Effect values are uniforms too
|
|
89
|
+
const numEffects = ShaderManager.EFFECTS.length;
|
|
90
|
+
for (let index = 0; index < numEffects; ++index) {
|
|
91
|
+
const effectName = ShaderManager.EFFECTS[index];
|
|
92
|
+
const effectInfo = ShaderManager.EFFECT_INFO[effectName];
|
|
93
|
+
const converter = effectInfo.converter;
|
|
94
|
+
this._uniforms[effectInfo.uniformName] = converter(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this._position = twgl.v3.create(0, 0);
|
|
98
|
+
this._scale = twgl.v3.create(100, 100);
|
|
99
|
+
this._direction = 90;
|
|
100
|
+
this._transformDirty = true;
|
|
101
|
+
this._rotationMatrix = twgl.m4.identity();
|
|
102
|
+
this._rotationTransformDirty = true;
|
|
103
|
+
this._rotationAdjusted = twgl.v3.create();
|
|
104
|
+
this._rotationCenterDirty = true;
|
|
105
|
+
this._skinScale = twgl.v3.create(0, 0, 0);
|
|
106
|
+
this._skinScaleDirty = true;
|
|
107
|
+
this._inverseMatrix = twgl.m4.identity();
|
|
108
|
+
this._inverseTransformDirty = true;
|
|
109
|
+
this._visible = true;
|
|
110
|
+
|
|
111
|
+
/** A bitmask identifying which effects are currently in use.
|
|
112
|
+
* @readonly
|
|
113
|
+
* @type {int} */
|
|
114
|
+
this.enabledEffects = 0;
|
|
115
|
+
|
|
116
|
+
/** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */
|
|
117
|
+
this._convexHullPoints = null;
|
|
118
|
+
this._convexHullDirty = true;
|
|
119
|
+
|
|
120
|
+
// The precise bounding box will be from the transformed convex hull points,
|
|
121
|
+
// so initialize the array of transformed hull points in setConvexHullPoints.
|
|
122
|
+
// Initializing it once per convex hull recalculation avoids unnecessary creation of twgl.v3 objects.
|
|
123
|
+
this._transformedHullPoints = null;
|
|
124
|
+
this._transformedHullDirty = true;
|
|
125
|
+
|
|
126
|
+
this._skinWasAltered = this._skinWasAltered.bind(this);
|
|
127
|
+
|
|
128
|
+
this.isTouching = this._isTouchingNever;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Dispose of this Drawable. Do not use it after calling this method.
|
|
133
|
+
*/
|
|
134
|
+
dispose () {
|
|
135
|
+
// Use the setter: disconnect events
|
|
136
|
+
this.skin = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Mark this Drawable's transform as dirty.
|
|
141
|
+
* It will be recalculated next time it's needed.
|
|
142
|
+
*/
|
|
143
|
+
setTransformDirty () {
|
|
144
|
+
this._transformDirty = true;
|
|
145
|
+
this._inverseTransformDirty = true;
|
|
146
|
+
this._transformedHullDirty = true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @returns {number} The ID for this Drawable.
|
|
151
|
+
*/
|
|
152
|
+
get id () {
|
|
153
|
+
return this._id;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @returns {Skin} the current skin for this Drawable.
|
|
158
|
+
*/
|
|
159
|
+
get skin () {
|
|
160
|
+
return this._skin;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {Skin} newSkin - A new Skin for this Drawable.
|
|
165
|
+
*/
|
|
166
|
+
set skin (newSkin) {
|
|
167
|
+
if (this._skin !== newSkin) {
|
|
168
|
+
if (this._skin) {
|
|
169
|
+
this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered);
|
|
170
|
+
}
|
|
171
|
+
this._skin = newSkin;
|
|
172
|
+
if (this._skin) {
|
|
173
|
+
this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered);
|
|
174
|
+
}
|
|
175
|
+
this._skinWasAltered();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @returns {Array<number>} the current scaling percentages applied to this Drawable. [100,100] is normal size.
|
|
181
|
+
*/
|
|
182
|
+
get scale () {
|
|
183
|
+
return [this._scale[0], this._scale[1]];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @returns {object.<string, *>} the shader uniforms to be used when rendering this Drawable.
|
|
188
|
+
*/
|
|
189
|
+
getUniforms () {
|
|
190
|
+
if (this._transformDirty) {
|
|
191
|
+
this._calculateTransform();
|
|
192
|
+
}
|
|
193
|
+
return this._uniforms;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @returns {boolean} whether this Drawable is visible.
|
|
198
|
+
*/
|
|
199
|
+
getVisible () {
|
|
200
|
+
return this._visible;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Update the position if it is different. Marks the transform as dirty.
|
|
205
|
+
* @param {Array.<number>} position A new position.
|
|
206
|
+
*/
|
|
207
|
+
updatePosition (position) {
|
|
208
|
+
if (this._position[0] !== position[0] ||
|
|
209
|
+
this._position[1] !== position[1]) {
|
|
210
|
+
this._position[0] = Math.round(position[0]);
|
|
211
|
+
this._position[1] = Math.round(position[1]);
|
|
212
|
+
this.setTransformDirty();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update the direction if it is different. Marks the transform as dirty.
|
|
218
|
+
* @param {number} direction A new direction.
|
|
219
|
+
*/
|
|
220
|
+
updateDirection (direction) {
|
|
221
|
+
if (this._direction !== direction) {
|
|
222
|
+
this._direction = direction;
|
|
223
|
+
this._rotationTransformDirty = true;
|
|
224
|
+
this.setTransformDirty();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Update the scale if it is different. Marks the transform as dirty.
|
|
230
|
+
* @param {Array.<number>} scale A new scale.
|
|
231
|
+
*/
|
|
232
|
+
updateScale (scale) {
|
|
233
|
+
if (this._scale[0] !== scale[0] ||
|
|
234
|
+
this._scale[1] !== scale[1]) {
|
|
235
|
+
this._scale[0] = scale[0];
|
|
236
|
+
this._scale[1] = scale[1];
|
|
237
|
+
this._rotationCenterDirty = true;
|
|
238
|
+
this._skinScaleDirty = true;
|
|
239
|
+
this.setTransformDirty();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Update visibility if it is different. Marks the convex hull as dirty.
|
|
245
|
+
* @param {boolean} visible A new visibility state.
|
|
246
|
+
*/
|
|
247
|
+
updateVisible (visible) {
|
|
248
|
+
if (this._visible !== visible) {
|
|
249
|
+
this._visible = visible;
|
|
250
|
+
this.setConvexHullDirty();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Update an effect. Marks the convex hull as dirty if the effect changes shape.
|
|
256
|
+
* @param {string} effectName The name of the effect.
|
|
257
|
+
* @param {number} rawValue A new effect value.
|
|
258
|
+
*/
|
|
259
|
+
updateEffect (effectName, rawValue) {
|
|
260
|
+
const effectInfo = ShaderManager.EFFECT_INFO[effectName];
|
|
261
|
+
if (rawValue) {
|
|
262
|
+
this.enabledEffects |= effectInfo.mask;
|
|
263
|
+
} else {
|
|
264
|
+
this.enabledEffects &= ~effectInfo.mask;
|
|
265
|
+
}
|
|
266
|
+
const converter = effectInfo.converter;
|
|
267
|
+
this._uniforms[effectInfo.uniformName] = converter(rawValue);
|
|
268
|
+
if (effectInfo.shapeChanges) {
|
|
269
|
+
this.setConvexHullDirty();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Update the position, direction, scale, or effect properties of this Drawable.
|
|
275
|
+
* @deprecated Use specific update* methods instead.
|
|
276
|
+
* @param {object.<string,*>} properties The new property values to set.
|
|
277
|
+
*/
|
|
278
|
+
updateProperties (properties) {
|
|
279
|
+
if ('position' in properties) {
|
|
280
|
+
this.updatePosition(properties.position);
|
|
281
|
+
}
|
|
282
|
+
if ('direction' in properties) {
|
|
283
|
+
this.updateDirection(properties.direction);
|
|
284
|
+
}
|
|
285
|
+
if ('scale' in properties) {
|
|
286
|
+
this.updateScale(properties.scale);
|
|
287
|
+
}
|
|
288
|
+
if ('visible' in properties) {
|
|
289
|
+
this.updateVisible(properties.visible);
|
|
290
|
+
}
|
|
291
|
+
const numEffects = ShaderManager.EFFECTS.length;
|
|
292
|
+
for (let index = 0; index < numEffects; ++index) {
|
|
293
|
+
const effectName = ShaderManager.EFFECTS[index];
|
|
294
|
+
if (effectName in properties) {
|
|
295
|
+
this.updateEffect(effectName, properties[effectName]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Calculate the transform to use when rendering this Drawable.
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
_calculateTransform () {
|
|
305
|
+
if (this._rotationTransformDirty) {
|
|
306
|
+
const rotation = (270 - this._direction) * Math.PI / 180;
|
|
307
|
+
|
|
308
|
+
// Calling rotationZ sets the destination matrix to a rotation
|
|
309
|
+
// around the Z axis setting matrix components 0, 1, 4 and 5 with
|
|
310
|
+
// cosine and sine values of the rotation.
|
|
311
|
+
// twgl.m4.rotationZ(rotation, this._rotationMatrix);
|
|
312
|
+
|
|
313
|
+
// twgl assumes the last value set to the matrix was anything.
|
|
314
|
+
// Drawable knows, it was another rotationZ matrix, so we can skip
|
|
315
|
+
// assigning the values that will never change.
|
|
316
|
+
const c = Math.cos(rotation);
|
|
317
|
+
const s = Math.sin(rotation);
|
|
318
|
+
this._rotationMatrix[0] = c;
|
|
319
|
+
this._rotationMatrix[1] = s;
|
|
320
|
+
// this._rotationMatrix[2] = 0;
|
|
321
|
+
// this._rotationMatrix[3] = 0;
|
|
322
|
+
this._rotationMatrix[4] = -s;
|
|
323
|
+
this._rotationMatrix[5] = c;
|
|
324
|
+
// this._rotationMatrix[6] = 0;
|
|
325
|
+
// this._rotationMatrix[7] = 0;
|
|
326
|
+
// this._rotationMatrix[8] = 0;
|
|
327
|
+
// this._rotationMatrix[9] = 0;
|
|
328
|
+
// this._rotationMatrix[10] = 1;
|
|
329
|
+
// this._rotationMatrix[11] = 0;
|
|
330
|
+
// this._rotationMatrix[12] = 0;
|
|
331
|
+
// this._rotationMatrix[13] = 0;
|
|
332
|
+
// this._rotationMatrix[14] = 0;
|
|
333
|
+
// this._rotationMatrix[15] = 1;
|
|
334
|
+
|
|
335
|
+
this._rotationTransformDirty = false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Adjust rotation center relative to the skin.
|
|
339
|
+
if (this._rotationCenterDirty && this.skin !== null) {
|
|
340
|
+
// twgl version of the following in function work.
|
|
341
|
+
// let rotationAdjusted = twgl.v3.subtract(
|
|
342
|
+
// this.skin.rotationCenter,
|
|
343
|
+
// twgl.v3.divScalar(this.skin.size, 2, this._rotationAdjusted),
|
|
344
|
+
// this._rotationAdjusted
|
|
345
|
+
// );
|
|
346
|
+
// rotationAdjusted = twgl.v3.multiply(
|
|
347
|
+
// rotationAdjusted, this._scale, rotationAdjusted
|
|
348
|
+
// );
|
|
349
|
+
// rotationAdjusted = twgl.v3.divScalar(
|
|
350
|
+
// rotationAdjusted, 100, rotationAdjusted
|
|
351
|
+
// );
|
|
352
|
+
// rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate.
|
|
353
|
+
// rotationAdjusted[2] = 0; // Z coordinate is 0.
|
|
354
|
+
|
|
355
|
+
// Locally assign rotationCenter and skinSize to keep from having
|
|
356
|
+
// the Skin getter properties called twice while locally assigning
|
|
357
|
+
// their components for readability.
|
|
358
|
+
const rotationCenter = this.skin.rotationCenter;
|
|
359
|
+
const skinSize = this.skin.size;
|
|
360
|
+
const center0 = rotationCenter[0];
|
|
361
|
+
const center1 = rotationCenter[1];
|
|
362
|
+
const skinSize0 = skinSize[0];
|
|
363
|
+
const skinSize1 = skinSize[1];
|
|
364
|
+
const scale0 = this._scale[0];
|
|
365
|
+
const scale1 = this._scale[1];
|
|
366
|
+
|
|
367
|
+
const rotationAdjusted = this._rotationAdjusted;
|
|
368
|
+
rotationAdjusted[0] = (center0 - (skinSize0 / 2)) * scale0 / 100;
|
|
369
|
+
rotationAdjusted[1] = ((center1 - (skinSize1 / 2)) * scale1 / 100) * -1;
|
|
370
|
+
// rotationAdjusted[2] = 0;
|
|
371
|
+
|
|
372
|
+
this._rotationCenterDirty = false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (this._skinScaleDirty && this.skin !== null) {
|
|
376
|
+
// twgl version of the following in function work.
|
|
377
|
+
// const scaledSize = twgl.v3.divScalar(
|
|
378
|
+
// twgl.v3.multiply(this.skin.size, this._scale),
|
|
379
|
+
// 100
|
|
380
|
+
// );
|
|
381
|
+
// // was NaN because the vectors have only 2 components.
|
|
382
|
+
// scaledSize[2] = 0;
|
|
383
|
+
|
|
384
|
+
// Locally assign skinSize to keep from having the Skin getter
|
|
385
|
+
// properties called twice.
|
|
386
|
+
const skinSize = this.skin.size;
|
|
387
|
+
const scaledSize = this._skinScale;
|
|
388
|
+
scaledSize[0] = skinSize[0] * this._scale[0] / 100;
|
|
389
|
+
scaledSize[1] = skinSize[1] * this._scale[1] / 100;
|
|
390
|
+
// scaledSize[2] = 0;
|
|
391
|
+
|
|
392
|
+
this._skinScaleDirty = false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const modelMatrix = this._uniforms.u_modelMatrix;
|
|
396
|
+
|
|
397
|
+
// twgl version of the following in function work.
|
|
398
|
+
// twgl.m4.identity(modelMatrix);
|
|
399
|
+
// twgl.m4.translate(modelMatrix, this._position, modelMatrix);
|
|
400
|
+
// twgl.m4.multiply(modelMatrix, this._rotationMatrix, modelMatrix);
|
|
401
|
+
// twgl.m4.translate(modelMatrix, this._rotationAdjusted, modelMatrix);
|
|
402
|
+
// twgl.m4.scale(modelMatrix, scaledSize, modelMatrix);
|
|
403
|
+
|
|
404
|
+
// Drawable configures a 3D matrix for drawing in WebGL, but most values
|
|
405
|
+
// will never be set because the inputs are on the X and Y position axis
|
|
406
|
+
// and the Z rotation axis. Drawable can bring the work inside
|
|
407
|
+
// _calculateTransform and greatly reduce the ammount of math and array
|
|
408
|
+
// assignments needed.
|
|
409
|
+
|
|
410
|
+
const scale0 = this._skinScale[0];
|
|
411
|
+
const scale1 = this._skinScale[1];
|
|
412
|
+
const rotation00 = this._rotationMatrix[0];
|
|
413
|
+
const rotation01 = this._rotationMatrix[1];
|
|
414
|
+
const rotation10 = this._rotationMatrix[4];
|
|
415
|
+
const rotation11 = this._rotationMatrix[5];
|
|
416
|
+
const adjusted0 = this._rotationAdjusted[0];
|
|
417
|
+
const adjusted1 = this._rotationAdjusted[1];
|
|
418
|
+
const position0 = this._position[0];
|
|
419
|
+
const position1 = this._position[1];
|
|
420
|
+
|
|
421
|
+
// Commented assignments show what the values are when the matrix was
|
|
422
|
+
// instantiated. Those values will never change so they do not need to
|
|
423
|
+
// be reassigned.
|
|
424
|
+
modelMatrix[0] = scale0 * rotation00;
|
|
425
|
+
modelMatrix[1] = scale0 * rotation01;
|
|
426
|
+
// modelMatrix[2] = 0;
|
|
427
|
+
// modelMatrix[3] = 0;
|
|
428
|
+
modelMatrix[4] = scale1 * rotation10;
|
|
429
|
+
modelMatrix[5] = scale1 * rotation11;
|
|
430
|
+
// modelMatrix[6] = 0;
|
|
431
|
+
// modelMatrix[7] = 0;
|
|
432
|
+
// modelMatrix[8] = 0;
|
|
433
|
+
// modelMatrix[9] = 0;
|
|
434
|
+
// modelMatrix[10] = 1;
|
|
435
|
+
// modelMatrix[11] = 0;
|
|
436
|
+
modelMatrix[12] = (rotation00 * adjusted0) + (rotation10 * adjusted1) + position0;
|
|
437
|
+
modelMatrix[13] = (rotation01 * adjusted0) + (rotation11 * adjusted1) + position1;
|
|
438
|
+
// modelMatrix[14] = 0;
|
|
439
|
+
// modelMatrix[15] = 1;
|
|
440
|
+
|
|
441
|
+
this._transformDirty = false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Whether the Drawable needs convex hull points provided by the renderer.
|
|
446
|
+
* @return {boolean} True when no convex hull known, or it's dirty.
|
|
447
|
+
*/
|
|
448
|
+
needsConvexHullPoints () {
|
|
449
|
+
return !this._convexHullPoints || this._convexHullDirty || this._convexHullPoints.length === 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Set the convex hull to be dirty.
|
|
454
|
+
* Do this whenever the Drawable's shape has possibly changed.
|
|
455
|
+
*/
|
|
456
|
+
setConvexHullDirty () {
|
|
457
|
+
this._convexHullDirty = true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Set the convex hull points for the Drawable.
|
|
462
|
+
* @param {Array<Array<number>>} points Convex hull points, as [[x, y], ...]
|
|
463
|
+
*/
|
|
464
|
+
setConvexHullPoints (points) {
|
|
465
|
+
this._convexHullPoints = points;
|
|
466
|
+
this._convexHullDirty = false;
|
|
467
|
+
|
|
468
|
+
// Re-create the "transformed hull points" array.
|
|
469
|
+
// We only do this when the hull points change to avoid unnecessary allocations and GC.
|
|
470
|
+
this._transformedHullPoints = [];
|
|
471
|
+
for (let i = 0; i < points.length; i++) {
|
|
472
|
+
this._transformedHullPoints.push(twgl.v3.create());
|
|
473
|
+
}
|
|
474
|
+
this._transformedHullDirty = true;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* @function
|
|
479
|
+
* @name isTouching
|
|
480
|
+
* Check if the world position touches the skin.
|
|
481
|
+
* The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date.
|
|
482
|
+
* @see updateCPURenderAttributes
|
|
483
|
+
* @param {twgl.v3} vec World coordinate vector.
|
|
484
|
+
* @return {boolean} True if the world position touches the skin.
|
|
485
|
+
*/
|
|
486
|
+
|
|
487
|
+
// `updateCPURenderAttributes` sets this Drawable instance's `isTouching` method
|
|
488
|
+
// to one of the following three functions:
|
|
489
|
+
// If this drawable has no skin, set it to `_isTouchingNever`.
|
|
490
|
+
// Otherwise, if this drawable uses nearest-neighbor scaling at its current scale, set it to `_isTouchingNearest`.
|
|
491
|
+
// Otherwise, set it to `_isTouchingLinear`.
|
|
492
|
+
// This allows several checks to be moved from the `isTouching` function to `updateCPURenderAttributes`.
|
|
493
|
+
|
|
494
|
+
// eslint-disable-next-line no-unused-vars
|
|
495
|
+
_isTouchingNever (vec) {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
_isTouchingNearest (vec) {
|
|
500
|
+
return this.skin.isTouchingNearest(getLocalPosition(this, vec));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
_isTouchingLinear (vec) {
|
|
504
|
+
return this.skin.isTouchingLinear(getLocalPosition(this, vec));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get the precise bounds for a Drawable.
|
|
509
|
+
* This function applies the transform matrix to the known convex hull,
|
|
510
|
+
* and then finds the minimum box along the axes.
|
|
511
|
+
* Before calling this, ensure the renderer has updated convex hull points.
|
|
512
|
+
* @param {?Rectangle} result optional destination for bounds calculation
|
|
513
|
+
* @return {!Rectangle} Bounds for a tight box around the Drawable.
|
|
514
|
+
*/
|
|
515
|
+
getBounds (result) {
|
|
516
|
+
if (this.needsConvexHullPoints()) {
|
|
517
|
+
throw new Error('Needs updated convex hull points before bounds calculation.');
|
|
518
|
+
}
|
|
519
|
+
if (this._transformDirty) {
|
|
520
|
+
this._calculateTransform();
|
|
521
|
+
}
|
|
522
|
+
const transformedHullPoints = this._getTransformedHullPoints();
|
|
523
|
+
// Search through transformed points to generate box on axes.
|
|
524
|
+
result = result || new Rectangle();
|
|
525
|
+
result.initFromPointsAABB(transformedHullPoints);
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get the precise bounds for the upper 8px slice of the Drawable.
|
|
531
|
+
* Used for calculating where to position a text bubble.
|
|
532
|
+
* Before calling this, ensure the renderer has updated convex hull points.
|
|
533
|
+
* @param {?Rectangle} result optional destination for bounds calculation
|
|
534
|
+
* @return {!Rectangle} Bounds for a tight box around a slice of the Drawable.
|
|
535
|
+
*/
|
|
536
|
+
getBoundsForBubble (result) {
|
|
537
|
+
if (this.needsConvexHullPoints()) {
|
|
538
|
+
throw new Error('Needs updated convex hull points before bubble bounds calculation.');
|
|
539
|
+
}
|
|
540
|
+
if (this._transformDirty) {
|
|
541
|
+
this._calculateTransform();
|
|
542
|
+
}
|
|
543
|
+
const slice = 8; // px, how tall the top slice to measure should be.
|
|
544
|
+
const transformedHullPoints = this._getTransformedHullPoints();
|
|
545
|
+
const maxY = Math.max.apply(null, transformedHullPoints.map(p => p[1]));
|
|
546
|
+
const filteredHullPoints = transformedHullPoints.filter(p => p[1] > maxY - slice);
|
|
547
|
+
// Search through filtered points to generate box on axes.
|
|
548
|
+
result = result || new Rectangle();
|
|
549
|
+
result.initFromPointsAABB(filteredHullPoints);
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Get the rough axis-aligned bounding box for the Drawable.
|
|
555
|
+
* Calculated by transforming the skin's bounds.
|
|
556
|
+
* Note that this is less precise than the box returned by `getBounds`,
|
|
557
|
+
* which is tightly snapped to account for a Drawable's transparent regions.
|
|
558
|
+
* `getAABB` returns a much less accurate bounding box, but will be much
|
|
559
|
+
* faster to calculate so may be desired for quick checks/optimizations.
|
|
560
|
+
* @param {?Rectangle} result optional destination for bounds calculation
|
|
561
|
+
* @return {!Rectangle} Rough axis-aligned bounding box for Drawable.
|
|
562
|
+
*/
|
|
563
|
+
getAABB (result) {
|
|
564
|
+
if (this._transformDirty) {
|
|
565
|
+
this._calculateTransform();
|
|
566
|
+
}
|
|
567
|
+
const tm = this._uniforms.u_modelMatrix;
|
|
568
|
+
result = result || new Rectangle();
|
|
569
|
+
result.initFromModelMatrix(tm);
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Return the best Drawable bounds possible without performing graphics queries.
|
|
575
|
+
* I.e., returns the tight bounding box when the convex hull points are already
|
|
576
|
+
* known, but otherwise return the rough AABB of the Drawable.
|
|
577
|
+
* @param {?Rectangle} result optional destination for bounds calculation
|
|
578
|
+
* @return {!Rectangle} Bounds for the Drawable.
|
|
579
|
+
*/
|
|
580
|
+
getFastBounds (result) {
|
|
581
|
+
if (!this.needsConvexHullPoints()) {
|
|
582
|
+
return this.getBounds(result);
|
|
583
|
+
}
|
|
584
|
+
return this.getAABB(result);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Transform all the convex hull points by the current Drawable's
|
|
589
|
+
* transform. This allows us to skip recalculating the convex hull
|
|
590
|
+
* for many Drawable updates, including translation, rotation, scaling.
|
|
591
|
+
* @return {!Array.<!Array.number>} Array of glPoints which are Array<x, y>
|
|
592
|
+
* @private
|
|
593
|
+
*/
|
|
594
|
+
_getTransformedHullPoints () {
|
|
595
|
+
if (!this._transformedHullDirty) {
|
|
596
|
+
return this._transformedHullPoints;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const projection = twgl.m4.ortho(-1, 1, -1, 1, -1, 1);
|
|
600
|
+
const skinSize = this.skin.size;
|
|
601
|
+
const halfXPixel = 1 / skinSize[0] / 2;
|
|
602
|
+
const halfYPixel = 1 / skinSize[1] / 2;
|
|
603
|
+
const tm = twgl.m4.multiply(this._uniforms.u_modelMatrix, projection);
|
|
604
|
+
for (let i = 0; i < this._convexHullPoints.length; i++) {
|
|
605
|
+
const point = this._convexHullPoints[i];
|
|
606
|
+
const dstPoint = this._transformedHullPoints[i];
|
|
607
|
+
|
|
608
|
+
dstPoint[0] = 0.5 + (-point[0] / skinSize[0]) - halfXPixel;
|
|
609
|
+
dstPoint[1] = (point[1] / skinSize[1]) - 0.5 + halfYPixel;
|
|
610
|
+
twgl.m4.transformPoint(tm, dstPoint, dstPoint);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this._transformedHullDirty = false;
|
|
614
|
+
|
|
615
|
+
return this._transformedHullPoints;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Update the transform matrix and calculate it's inverse for collision
|
|
620
|
+
* and local texture position purposes.
|
|
621
|
+
*/
|
|
622
|
+
updateMatrix () {
|
|
623
|
+
if (this._transformDirty) {
|
|
624
|
+
this._calculateTransform();
|
|
625
|
+
}
|
|
626
|
+
// Get the inverse of the model matrix or update it.
|
|
627
|
+
if (this._inverseTransformDirty) {
|
|
628
|
+
const inverse = this._inverseMatrix;
|
|
629
|
+
twgl.m4.copy(this._uniforms.u_modelMatrix, inverse);
|
|
630
|
+
// The normal matrix uses a z scaling of 0 causing model[10] to be
|
|
631
|
+
// 0. Getting a 4x4 inverse is impossible without a scaling in x, y,
|
|
632
|
+
// and z.
|
|
633
|
+
inverse[10] = 1;
|
|
634
|
+
twgl.m4.inverse(inverse, inverse);
|
|
635
|
+
this._inverseTransformDirty = false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Update everything necessary to render this drawable on the CPU.
|
|
641
|
+
*/
|
|
642
|
+
updateCPURenderAttributes () {
|
|
643
|
+
this.updateMatrix();
|
|
644
|
+
// CPU rendering always occurs at the "native" size, so no need to scale up this._scale
|
|
645
|
+
if (this.skin) {
|
|
646
|
+
this.skin.updateSilhouette(this._scale);
|
|
647
|
+
|
|
648
|
+
if (this.skin.useNearest(this._scale, this)) {
|
|
649
|
+
this.isTouching = this._isTouchingNearest;
|
|
650
|
+
} else {
|
|
651
|
+
this.isTouching = this._isTouchingLinear;
|
|
652
|
+
}
|
|
653
|
+
} else {
|
|
654
|
+
log.warn(`Could not find skin for drawable with id: ${this._id}`);
|
|
655
|
+
|
|
656
|
+
this.isTouching = this._isTouchingNever;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Respond to an internal change in the current Skin.
|
|
662
|
+
* @private
|
|
663
|
+
*/
|
|
664
|
+
_skinWasAltered () {
|
|
665
|
+
this._rotationCenterDirty = true;
|
|
666
|
+
this._skinScaleDirty = true;
|
|
667
|
+
this.setConvexHullDirty();
|
|
668
|
+
this.setTransformDirty();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Calculate a color to represent the given ID number. At least one component of
|
|
673
|
+
* the resulting color will be non-zero if the ID is not RenderConstants.ID_NONE.
|
|
674
|
+
* @param {int} id The ID to convert.
|
|
675
|
+
* @returns {Array<number>} An array of [r,g,b,a], each component in the range [0,1].
|
|
676
|
+
*/
|
|
677
|
+
static color4fFromID (id) {
|
|
678
|
+
id -= RenderConstants.ID_NONE;
|
|
679
|
+
const r = ((id >> 0) & 255) / 255.0;
|
|
680
|
+
const g = ((id >> 8) & 255) / 255.0;
|
|
681
|
+
const b = ((id >> 16) & 255) / 255.0;
|
|
682
|
+
return [r, g, b, 1.0];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Calculate the ID number represented by the given color. If all components of
|
|
687
|
+
* the color are zero, the result will be RenderConstants.ID_NONE; otherwise the result
|
|
688
|
+
* will be a valid ID.
|
|
689
|
+
* @param {int} r The red value of the color, in the range [0,255].
|
|
690
|
+
* @param {int} g The green value of the color, in the range [0,255].
|
|
691
|
+
* @param {int} b The blue value of the color, in the range [0,255].
|
|
692
|
+
* @returns {int} The ID represented by that color.
|
|
693
|
+
*/
|
|
694
|
+
static color3bToID (r, g, b) {
|
|
695
|
+
let id;
|
|
696
|
+
id = (r & 255) << 0;
|
|
697
|
+
id |= (g & 255) << 8;
|
|
698
|
+
id |= (b & 255) << 16;
|
|
699
|
+
return id + RenderConstants.ID_NONE;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Sample a color from a drawable's texture.
|
|
704
|
+
* The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date.
|
|
705
|
+
* @see updateCPURenderAttributes
|
|
706
|
+
* @param {twgl.v3} vec The scratch space [x,y] vector
|
|
707
|
+
* @param {Drawable} drawable The drawable to sample the texture from
|
|
708
|
+
* @param {Uint8ClampedArray} dst The "color4b" representation of the texture at point.
|
|
709
|
+
* @param {number} [effectMask] A bitmask for which effects to use. Optional.
|
|
710
|
+
* @returns {Uint8ClampedArray} The dst object filled with the color4b
|
|
711
|
+
*/
|
|
712
|
+
static sampleColor4b (vec, drawable, dst, effectMask) {
|
|
713
|
+
const localPosition = getLocalPosition(drawable, vec);
|
|
714
|
+
if (localPosition[0] < 0 || localPosition[1] < 0 ||
|
|
715
|
+
localPosition[0] > 1 || localPosition[1] > 1) {
|
|
716
|
+
dst[0] = 0;
|
|
717
|
+
dst[1] = 0;
|
|
718
|
+
dst[2] = 0;
|
|
719
|
+
dst[3] = 0;
|
|
720
|
+
return dst;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const textColor =
|
|
724
|
+
// commenting out to only use nearest for now
|
|
725
|
+
// drawable.skin.useNearest(drawable._scale, drawable) ?
|
|
726
|
+
drawable.skin._silhouette.colorAtNearest(localPosition, dst);
|
|
727
|
+
// : drawable.skin._silhouette.colorAtLinear(localPosition, dst);
|
|
728
|
+
|
|
729
|
+
if (drawable.enabledEffects === 0) return textColor;
|
|
730
|
+
return EffectTransform.transformColor(drawable, textColor, effectMask);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
module.exports = Drawable;
|