@solidtv/renderer 1.1.1 → 1.1.2
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/dist/src/core/CoreNode.d.ts +22 -0
- package/dist/src/core/CoreNode.js +131 -25
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/Stage.js +3 -2
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/lib/Matrix3d.d.ts +9 -0
- package/dist/src/core/lib/Matrix3d.js +29 -3
- package/dist/src/core/lib/Matrix3d.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/CoreNode.test.ts +438 -0
- package/src/core/CoreNode.ts +140 -39
- package/src/core/Stage.ts +3 -2
- package/src/core/lib/Matrix3d.test.ts +72 -0
- package/src/core/lib/Matrix3d.ts +30 -3
package/src/core/CoreNode.ts
CHANGED
|
@@ -811,6 +811,28 @@ export class CoreNode extends EventEmitter {
|
|
|
811
811
|
public isRenderable = false;
|
|
812
812
|
public renderState: CoreNodeRenderState = CoreNodeRenderState.Init;
|
|
813
813
|
public isSimple = true;
|
|
814
|
+
/**
|
|
815
|
+
* `true` when `localTransform` is in identity-shape (ta=1, tb=0, tc=0, td=1)
|
|
816
|
+
* — i.e. a pure translation. Lets the simple-path `updateLocalTransform`
|
|
817
|
+
* skip redundant ta/tb/tc/td writes between frames. Defaults to `true`
|
|
818
|
+
* because the matrix is eagerly allocated as an identity in the constructor.
|
|
819
|
+
*/
|
|
820
|
+
public _localIsTranslate = true;
|
|
821
|
+
/**
|
|
822
|
+
* Cached result of the texture `contain` resizeMode check used by
|
|
823
|
+
* `updateLocalTransform` and `updateIsSimple`. Updated whenever the
|
|
824
|
+
* texture or textureOptions change (via `updateIsSimple`), so the hot
|
|
825
|
+
* paths can avoid the optional-chain + string compare on every frame.
|
|
826
|
+
*/
|
|
827
|
+
public _hasContainResize = false;
|
|
828
|
+
/**
|
|
829
|
+
* `true` when `globalTransform` is in identity-shape (ta=1, tb=0, tc=0, td=1).
|
|
830
|
+
* Propagates from parent: a node's global is translate-only iff the parent's
|
|
831
|
+
* global is translate-only AND the node itself is `isSimple`. Default `true`
|
|
832
|
+
* because freshly-constructed nodes have no transform applied yet, and the
|
|
833
|
+
* Stage root is configured with an identity-shape global.
|
|
834
|
+
*/
|
|
835
|
+
public _globalIsTranslate = true;
|
|
814
836
|
|
|
815
837
|
public worldAlpha = 1;
|
|
816
838
|
public premultipliedColorTl = 0;
|
|
@@ -835,6 +857,13 @@ export class CoreNode extends EventEmitter {
|
|
|
835
857
|
constructor(readonly stage: Stage, props: CoreNodeProps) {
|
|
836
858
|
super();
|
|
837
859
|
|
|
860
|
+
// Eagerly allocate the local/global transform matrices as identity.
|
|
861
|
+
// This keeps the field's hidden class monomorphic (Matrix3d, not
|
|
862
|
+
// Matrix3d|undefined) from construction onward and lets the simple
|
|
863
|
+
// / translate-only fast paths take effect on the very first update.
|
|
864
|
+
this.localTransform = Matrix3d.identity();
|
|
865
|
+
this.globalTransform = Matrix3d.identity();
|
|
866
|
+
|
|
838
867
|
//inital update type
|
|
839
868
|
let initialUpdateType =
|
|
840
869
|
UpdateType.Local | UpdateType.RenderBounds | UpdateType.RenderState;
|
|
@@ -1089,7 +1118,18 @@ export class CoreNode extends EventEmitter {
|
|
|
1089
1118
|
const { x, y } = p;
|
|
1090
1119
|
|
|
1091
1120
|
if (this.isSimple) {
|
|
1121
|
+
// Fast path: when localTransform is already in identity-shape
|
|
1122
|
+
// (ta=1, tb=0, tc=0, td=1), only tx/ty change between frames, so we
|
|
1123
|
+
// skip the 4 redundant field writes Matrix3d.translate would do.
|
|
1124
|
+
// _localIsTranslate becomes stale when a node was non-simple (had
|
|
1125
|
+
// rotation/scale/mount) on a previous frame — in that case do a
|
|
1126
|
+
// full reset to identity-translate.
|
|
1127
|
+
if (this._localIsTranslate === true) {
|
|
1128
|
+
this.localTransform!.setTranslate(x, y);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1092
1131
|
this.localTransform = Matrix3d.translate(x, y, this.localTransform);
|
|
1132
|
+
this._localIsTranslate = true;
|
|
1093
1133
|
return;
|
|
1094
1134
|
}
|
|
1095
1135
|
|
|
@@ -1097,10 +1137,15 @@ export class CoreNode extends EventEmitter {
|
|
|
1097
1137
|
const mountTranslateX = p.mountX * w;
|
|
1098
1138
|
const mountTranslateY = p.mountY * h;
|
|
1099
1139
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1140
|
+
const rotation = p.rotation;
|
|
1141
|
+
const scaleX = p.scaleX;
|
|
1142
|
+
const scaleY = p.scaleY;
|
|
1143
|
+
|
|
1144
|
+
if (rotation !== 0) {
|
|
1145
|
+
// Full rotation (+ optional scale + pivot)
|
|
1146
|
+
const scaleRotate = Matrix3d.rotate(rotation, Matrix3d.temp).scale(
|
|
1147
|
+
scaleX,
|
|
1148
|
+
scaleY,
|
|
1104
1149
|
);
|
|
1105
1150
|
const pivotTranslateX = p.pivotX * w;
|
|
1106
1151
|
const pivotTranslateY = p.pivotY * h;
|
|
@@ -1112,7 +1157,21 @@ export class CoreNode extends EventEmitter {
|
|
|
1112
1157
|
)
|
|
1113
1158
|
.multiply(scaleRotate)
|
|
1114
1159
|
.translate(-pivotTranslateX, -pivotTranslateY);
|
|
1160
|
+
} else if (scaleX !== 1 || scaleY !== 1) {
|
|
1161
|
+
// Scale (+ optional pivot) without rotation — skip the rotate matrix
|
|
1162
|
+
// and the 8-mul multiply; `.scale()` is a 4-mul in-place op.
|
|
1163
|
+
const pivotTranslateX = p.pivotX * w;
|
|
1164
|
+
const pivotTranslateY = p.pivotY * h;
|
|
1165
|
+
|
|
1166
|
+
this.localTransform = Matrix3d.translate(
|
|
1167
|
+
x - mountTranslateX + pivotTranslateX,
|
|
1168
|
+
y - mountTranslateY + pivotTranslateY,
|
|
1169
|
+
this.localTransform,
|
|
1170
|
+
)
|
|
1171
|
+
.scale(scaleX, scaleY)
|
|
1172
|
+
.translate(-pivotTranslateX, -pivotTranslateY);
|
|
1115
1173
|
} else {
|
|
1174
|
+
// Mount (or texture-contain) only — pure translation.
|
|
1116
1175
|
this.localTransform = Matrix3d.translate(
|
|
1117
1176
|
x - mountTranslateX,
|
|
1118
1177
|
y - mountTranslateY,
|
|
@@ -1120,12 +1179,13 @@ export class CoreNode extends EventEmitter {
|
|
|
1120
1179
|
);
|
|
1121
1180
|
}
|
|
1122
1181
|
|
|
1123
|
-
// Handle 'contain' resize mode
|
|
1182
|
+
// Handle 'contain' resize mode (cached check; dimensions still need
|
|
1183
|
+
// a runtime null-check because they're populated asynchronously).
|
|
1124
1184
|
const texture = p.texture;
|
|
1125
1185
|
if (
|
|
1126
|
-
|
|
1127
|
-
texture
|
|
1128
|
-
|
|
1186
|
+
this._hasContainResize === true &&
|
|
1187
|
+
texture !== null &&
|
|
1188
|
+
texture.dimensions !== null
|
|
1129
1189
|
) {
|
|
1130
1190
|
let resizeModeScaleX = 1;
|
|
1131
1191
|
let resizeModeScaleY = 1;
|
|
@@ -1157,17 +1217,23 @@ export class CoreNode extends EventEmitter {
|
|
|
1157
1217
|
.translate(extraX, extraY)
|
|
1158
1218
|
.scale(resizeModeScaleX, resizeModeScaleY);
|
|
1159
1219
|
}
|
|
1220
|
+
|
|
1221
|
+
this._localIsTranslate = false;
|
|
1160
1222
|
}
|
|
1161
1223
|
|
|
1162
1224
|
updateIsSimple() {
|
|
1163
1225
|
const p = this.props;
|
|
1226
|
+
// Cache the texture-contain check so updateLocalTransform doesn't have to
|
|
1227
|
+
// run the optional-chain + string compare on every Local update.
|
|
1228
|
+
this._hasContainResize =
|
|
1229
|
+
p.texture !== null && p.textureOptions?.resizeMode?.type === 'contain';
|
|
1164
1230
|
this.isSimple =
|
|
1165
1231
|
p.rotation === 0 &&
|
|
1166
1232
|
p.scaleX === 1 &&
|
|
1167
1233
|
p.scaleY === 1 &&
|
|
1168
1234
|
p.mountX === 0 &&
|
|
1169
1235
|
p.mountY === 0 &&
|
|
1170
|
-
|
|
1236
|
+
this._hasContainResize === false;
|
|
1171
1237
|
}
|
|
1172
1238
|
|
|
1173
1239
|
/**
|
|
@@ -1209,6 +1275,10 @@ export class CoreNode extends EventEmitter {
|
|
|
1209
1275
|
}
|
|
1210
1276
|
|
|
1211
1277
|
if (updateType & UpdateType.Global) {
|
|
1278
|
+
const lt = this.localTransform!;
|
|
1279
|
+
const gt = this.globalTransform!;
|
|
1280
|
+
let fastPathApplied = false;
|
|
1281
|
+
|
|
1212
1282
|
if (
|
|
1213
1283
|
USE_RTT &&
|
|
1214
1284
|
this.parentHasRenderTexture === true &&
|
|
@@ -1216,7 +1286,7 @@ export class CoreNode extends EventEmitter {
|
|
|
1216
1286
|
) {
|
|
1217
1287
|
// we are at the start of the RTT chain, so we need to reset the globalTransform
|
|
1218
1288
|
// for correct RTT rendering
|
|
1219
|
-
|
|
1289
|
+
Matrix3d.identity(gt);
|
|
1220
1290
|
|
|
1221
1291
|
// Maintain a full scene global transform for bounds detection
|
|
1222
1292
|
const parentTransform =
|
|
@@ -1225,7 +1295,10 @@ export class CoreNode extends EventEmitter {
|
|
|
1225
1295
|
this.sceneGlobalTransform = Matrix3d.copy(
|
|
1226
1296
|
parentTransform,
|
|
1227
1297
|
this.sceneGlobalTransform,
|
|
1228
|
-
).translateOrMultiply(
|
|
1298
|
+
).translateOrMultiply(lt);
|
|
1299
|
+
|
|
1300
|
+
// identity * local => translate-only iff this node is simple
|
|
1301
|
+
this._globalIsTranslate = this.isSimple;
|
|
1229
1302
|
} else if (
|
|
1230
1303
|
USE_RTT &&
|
|
1231
1304
|
this.parentHasRenderTexture === true &&
|
|
@@ -1234,32 +1307,48 @@ export class CoreNode extends EventEmitter {
|
|
|
1234
1307
|
// we're part of an RTT chain but our parent is not the main RTT node
|
|
1235
1308
|
// so we need to propogate the sceneGlobalTransform of the parent
|
|
1236
1309
|
// to maintain a full scene global transform for bounds detection
|
|
1237
|
-
const parentSceneTransform =
|
|
1238
|
-
parent.sceneGlobalTransform || this.localTransform!;
|
|
1310
|
+
const parentSceneTransform = parent.sceneGlobalTransform || lt;
|
|
1239
1311
|
|
|
1240
1312
|
this.sceneGlobalTransform = Matrix3d.copy(
|
|
1241
1313
|
parentSceneTransform,
|
|
1242
1314
|
this.sceneGlobalTransform,
|
|
1243
|
-
).translateOrMultiply(
|
|
1315
|
+
).translateOrMultiply(lt);
|
|
1244
1316
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1317
|
+
Matrix3d.copy(parent.globalTransform!, gt);
|
|
1318
|
+
|
|
1319
|
+
// Conservative: RTT chains rarely hit the translate fast path
|
|
1320
|
+
this._globalIsTranslate = false;
|
|
1249
1321
|
} else {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1322
|
+
// Common non-RTT path
|
|
1323
|
+
const parentGT = parent.globalTransform!;
|
|
1324
|
+
if (this.isSimple === true && parent._globalIsTranslate === true) {
|
|
1325
|
+
// Translate-only fast path: parent global and local are both pure
|
|
1326
|
+
// translations, so the resulting global is also a pure translation
|
|
1327
|
+
// and collapses to 2 adds on tx/ty.
|
|
1328
|
+
if (this._globalIsTranslate === false) {
|
|
1329
|
+
// Transitioning back into translate-only — reset ta/tb/tc/td
|
|
1330
|
+
// that may have been left non-identity by a prior frame.
|
|
1331
|
+
gt.ta = 1;
|
|
1332
|
+
gt.tb = 0;
|
|
1333
|
+
gt.tc = 0;
|
|
1334
|
+
gt.td = 1;
|
|
1335
|
+
}
|
|
1336
|
+
gt.setTranslate(parentGT.tx + lt.tx, parentGT.ty + lt.ty);
|
|
1337
|
+
this._globalIsTranslate = true;
|
|
1338
|
+
fastPathApplied = true;
|
|
1339
|
+
} else {
|
|
1340
|
+
Matrix3d.copy(parentGT, gt);
|
|
1341
|
+
this._globalIsTranslate =
|
|
1342
|
+
this.isSimple === true && parent._globalIsTranslate === true;
|
|
1343
|
+
}
|
|
1254
1344
|
}
|
|
1255
1345
|
|
|
1256
|
-
if (
|
|
1257
|
-
this.
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
this.globalTransform.translateOrMultiply(this.localTransform!);
|
|
1346
|
+
if (fastPathApplied === false) {
|
|
1347
|
+
if (this.isSimple) {
|
|
1348
|
+
gt.translate(lt.tx, lt.ty);
|
|
1349
|
+
} else {
|
|
1350
|
+
gt.translateOrMultiply(lt);
|
|
1351
|
+
}
|
|
1263
1352
|
}
|
|
1264
1353
|
this.calculateRenderCoords();
|
|
1265
1354
|
this.updateBoundingRect();
|
|
@@ -1380,18 +1469,30 @@ export class CoreNode extends EventEmitter {
|
|
|
1380
1469
|
childClippingRect = NO_CLIPPING_RECT;
|
|
1381
1470
|
}
|
|
1382
1471
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1472
|
+
const children = this.children;
|
|
1473
|
+
const length = children.length;
|
|
1474
|
+
if (childUpdateType !== 0) {
|
|
1475
|
+
// Specialized loop: OR-in the inherited update bits for every child,
|
|
1476
|
+
// then update if non-zero. Avoids the per-iter `childUpdateType !== 0`
|
|
1477
|
+
// compare.
|
|
1478
|
+
for (let i = 0; i < length; i++) {
|
|
1479
|
+
const child = children[i] as CoreNode;
|
|
1387
1480
|
child.updateType |= childUpdateType;
|
|
1481
|
+
if (child.updateType === 0) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
child.update(delta, childClippingRect);
|
|
1388
1485
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1486
|
+
} else {
|
|
1487
|
+
// Specialized loop: nothing to inherit, so only walk children that
|
|
1488
|
+
// already have pending work of their own.
|
|
1489
|
+
for (let i = 0; i < length; i++) {
|
|
1490
|
+
const child = children[i] as CoreNode;
|
|
1491
|
+
if (child.updateType === 0) {
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
child.update(delta, childClippingRect);
|
|
1392
1495
|
}
|
|
1393
|
-
|
|
1394
|
-
child.update(delta, childClippingRect);
|
|
1395
1496
|
}
|
|
1396
1497
|
}
|
|
1397
1498
|
|
|
@@ -1503,7 +1604,7 @@ export class CoreNode extends EventEmitter {
|
|
|
1503
1604
|
const renderCoords = (this.sceneRenderCoords ||
|
|
1504
1605
|
this.renderCoords) as RenderCoords;
|
|
1505
1606
|
|
|
1506
|
-
if (transform.tb === 0
|
|
1607
|
+
if (transform.tb === 0 && transform.tc === 0) {
|
|
1507
1608
|
this.renderBound = createBound(
|
|
1508
1609
|
renderCoords.x1,
|
|
1509
1610
|
renderCoords.y1,
|
package/src/core/Stage.ts
CHANGED
|
@@ -359,9 +359,10 @@ export class Stage {
|
|
|
359
359
|
|
|
360
360
|
this.root = rootNode;
|
|
361
361
|
|
|
362
|
-
// Initialize root node properties
|
|
362
|
+
// Initialize root node properties. Copy in place into the eagerly-allocated
|
|
363
|
+
// matrices so the hidden class for these fields stays stable.
|
|
363
364
|
rootNode.updateLocalTransform();
|
|
364
|
-
|
|
365
|
+
Matrix3d.copy(rootNode.localTransform!, rootNode.globalTransform);
|
|
365
366
|
rootNode.sceneGlobalTransform = Matrix3d.copy(rootNode.localTransform!);
|
|
366
367
|
rootNode.calculateRenderCoords();
|
|
367
368
|
rootNode.updateBoundingRect();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Matrix3d } from './Matrix3d.js';
|
|
3
|
+
|
|
4
|
+
describe('Matrix3d.setTranslate', () => {
|
|
5
|
+
it('writes tx/ty without touching ta/tb/tc/td', () => {
|
|
6
|
+
const m = Matrix3d.identity();
|
|
7
|
+
m.setTranslate(10, 20);
|
|
8
|
+
expect(m.tx).toBe(10);
|
|
9
|
+
expect(m.ty).toBe(20);
|
|
10
|
+
expect(m.ta).toBe(1);
|
|
11
|
+
expect(m.tb).toBe(0);
|
|
12
|
+
expect(m.tc).toBe(0);
|
|
13
|
+
expect(m.td).toBe(1);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('updates the float array on subsequent getFloatArr() calls', () => {
|
|
17
|
+
const m = Matrix3d.identity();
|
|
18
|
+
m.setTranslate(5, 7);
|
|
19
|
+
// First read populates the array
|
|
20
|
+
const arr = m.getFloatArr();
|
|
21
|
+
expect(arr[6]).toBe(5);
|
|
22
|
+
expect(arr[7]).toBe(7);
|
|
23
|
+
|
|
24
|
+
// Mutate, then expect getFloatArr() to pick up the change.
|
|
25
|
+
m.setTranslate(9, 11);
|
|
26
|
+
const arr2 = m.getFloatArr();
|
|
27
|
+
expect(arr2[6]).toBe(9);
|
|
28
|
+
expect(arr2[7]).toBe(11);
|
|
29
|
+
// Same array reference reused (no GC pressure).
|
|
30
|
+
expect(arr2).toBe(arr);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('overwrites prior tx/ty values', () => {
|
|
34
|
+
const m = Matrix3d.translate(100, 200);
|
|
35
|
+
m.setTranslate(0, 0);
|
|
36
|
+
expect(m.tx).toBe(0);
|
|
37
|
+
expect(m.ty).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Matrix3d.rotate(0) fast path', () => {
|
|
42
|
+
it('produces an identity matrix for angle=0', () => {
|
|
43
|
+
const m = Matrix3d.rotate(0);
|
|
44
|
+
expect(m.ta).toBe(1);
|
|
45
|
+
expect(m.tb).toBe(0);
|
|
46
|
+
expect(m.tc).toBe(0);
|
|
47
|
+
expect(m.td).toBe(1);
|
|
48
|
+
expect(m.tx).toBe(0);
|
|
49
|
+
expect(m.ty).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('resets a pre-populated out matrix to identity on angle=0', () => {
|
|
53
|
+
const m = Matrix3d.rotate(Math.PI / 4);
|
|
54
|
+
// Now reuse `m` with angle=0; should overwrite to identity.
|
|
55
|
+
Matrix3d.rotate(0, m);
|
|
56
|
+
expect(m.ta).toBe(1);
|
|
57
|
+
expect(m.tb).toBe(0);
|
|
58
|
+
expect(m.tc).toBe(0);
|
|
59
|
+
expect(m.td).toBe(1);
|
|
60
|
+
expect(m.tx).toBe(0);
|
|
61
|
+
expect(m.ty).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('still produces a real rotation when angle != 0', () => {
|
|
65
|
+
const m = Matrix3d.rotate(Math.PI / 2);
|
|
66
|
+
// cos(pi/2) ~ 0, sin(pi/2) = 1
|
|
67
|
+
expect(Math.abs(m.ta)).toBeLessThan(1e-10);
|
|
68
|
+
expect(m.tb).toBeCloseTo(-1, 10);
|
|
69
|
+
expect(m.tc).toBeCloseTo(1, 10);
|
|
70
|
+
expect(Math.abs(m.td)).toBeLessThan(1e-10);
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/core/lib/Matrix3d.ts
CHANGED
|
@@ -124,11 +124,24 @@ export class Matrix3d {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
public static rotate(angle: number, out?: Matrix3d): Matrix3d {
|
|
127
|
-
|
|
128
|
-
const sin = Math.sin(angle);
|
|
129
|
-
if (!out) {
|
|
127
|
+
if (out === undefined) {
|
|
130
128
|
out = new Matrix3d();
|
|
131
129
|
}
|
|
130
|
+
if (angle === 0) {
|
|
131
|
+
// Skip Math.cos(0) / Math.sin(0) — neither V8 nor JSC constant-folds
|
|
132
|
+
// these calls. Identity rotation is the common case when callers
|
|
133
|
+
// combine rotate().scale() and only the scale is non-default.
|
|
134
|
+
out.ta = 1;
|
|
135
|
+
out.tb = 0;
|
|
136
|
+
out.tx = 0;
|
|
137
|
+
out.tc = 0;
|
|
138
|
+
out.td = 1;
|
|
139
|
+
out.ty = 0;
|
|
140
|
+
out.mutation = true;
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
const cos = Math.cos(angle);
|
|
144
|
+
const sin = Math.sin(angle);
|
|
132
145
|
out.ta = cos;
|
|
133
146
|
out.tb = -sin;
|
|
134
147
|
out.tx = 0;
|
|
@@ -160,6 +173,20 @@ export class Matrix3d {
|
|
|
160
173
|
return this;
|
|
161
174
|
}
|
|
162
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Writes `tx`/`ty` directly and marks the matrix mutated.
|
|
178
|
+
*
|
|
179
|
+
* Caller is responsible for ensuring `ta`/`tb`/`tc`/`td` are already the
|
|
180
|
+
* desired values — typically the identity rotation/scale (1, 0, 0, 1).
|
|
181
|
+
* Used by hot paths that know the matrix is in identity-shape so the
|
|
182
|
+
* 4 redundant field writes performed by `Matrix3d.translate` can be skipped.
|
|
183
|
+
*/
|
|
184
|
+
public setTranslate(x: number, y: number): void {
|
|
185
|
+
this.tx = x;
|
|
186
|
+
this.ty = y;
|
|
187
|
+
this.mutation = true;
|
|
188
|
+
}
|
|
189
|
+
|
|
163
190
|
public scale(sx: number, sy: number): Matrix3d {
|
|
164
191
|
this.ta = this.ta * sx;
|
|
165
192
|
this.tb = this.tb * sy;
|