@aethervtt/shard-transform 0.0.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/dist/grid.js ADDED
@@ -0,0 +1,648 @@
1
+ import {
2
+ affine64,
3
+ ChildOf,
4
+ defineComponent,
5
+ defineEvent,
6
+ defineResource,
7
+ defineTag,
8
+ quat,
9
+ ShardError,
10
+ t
11
+ } from "@aethervtt/shard-core";
12
+ import { Transform } from "./components.js";
13
+ const Grid = defineComponent(
14
+ "transform/Grid",
15
+ {
16
+ cellSize: t.f64({
17
+ default: 2e3,
18
+ min: 1,
19
+ unit: "m",
20
+ description: "Edge length of one cell. Children store their position as a cell plus an offset."
21
+ }),
22
+ hysteresis: t.f32({
23
+ default: 100,
24
+ min: 0,
25
+ unit: "m",
26
+ description: "How far past the half-cell an entity may go before it moves to the next cell, so it does not flip back and forth."
27
+ })
28
+ },
29
+ {
30
+ description: "A frame for large worlds. Direct children carry GridCell; a nested grid is a Grid with a GridCell in its parent grid.",
31
+ requires: [Transform]
32
+ }
33
+ );
34
+ const GridCell = defineComponent(
35
+ "transform/GridCell",
36
+ {
37
+ cell: t.ivec3({
38
+ description: "Integer cell in the parent grid. Position = cell \xD7 Grid.cellSize + Transform.translation."
39
+ })
40
+ },
41
+ {
42
+ description: "The cell of a direct child of a Grid. Moved automatically when the translation leaves the cell.",
43
+ requires: [Transform]
44
+ }
45
+ );
46
+ const FloatingOrigin = defineTag("transform/FloatingOrigin", {
47
+ description: "At most one per world, usually the camera. GlobalTransform is relative to its grid cell, so everything near it is precise."
48
+ });
49
+ const OriginShift = defineEvent("transform/OriginShift", {
50
+ description: "The floating origin moved to another cell or grid. Add offset to positions cached from GlobalTransform."
51
+ });
52
+ const ROOT = -1;
53
+ class GridFrames {
54
+ /** Grid entity per slot (ROOT for slot 0). */
55
+ entity = [ROOT];
56
+ /** f64 affine per slot (12 each). */
57
+ a = new Float64Array(12 * 16);
58
+ /** Reference cell per slot (3 each). */
59
+ ref = new Int32Array(3 * 16);
60
+ cellSize = new Float64Array(16);
61
+ /** The grid's own frame → origin, rounded to f32 (its GlobalTransform). */
62
+ frame32 = new Float32Array(12 * 16);
63
+ /** 1 when the slot's A or ref changed this frame: everything under it re-propagates. */
64
+ moved = new Uint8Array(16);
65
+ /** Solve stamp per slot; a slot is current when it equals `stamp`. */
66
+ solved = new Uint32Array(16);
67
+ /** Seen stamp per slot, to drop slots of despawned grids. */
68
+ seen = new Uint32Array(16);
69
+ count = 1;
70
+ stamp = 1;
71
+ slotOf = /* @__PURE__ */ new Map();
72
+ /** Origin grid slot and cell. */
73
+ originSlot = 0;
74
+ originCell = new Int32Array(3);
75
+ originEntity = ROOT;
76
+ /** Whether any grid or origin is active; false means the root frame is the identity. */
77
+ active = false;
78
+ /** True when slot 0 (root frame) is exactly the identity. */
79
+ rootIdentity = true;
80
+ /**
81
+ * Some grid sits under an ordinary entity, whose transform change detection can't see cheaply,
82
+ * so every frame solves in full.
83
+ */
84
+ indirect = false;
85
+ constructor() {
86
+ affine64.identityAt(this.a, 0);
87
+ identity32(this.frame32, 0);
88
+ }
89
+ slot(grid) {
90
+ let s = this.slotOf.get(grid);
91
+ if (s !== void 0) return s;
92
+ s = this.count++;
93
+ if (s >= this.cellSize.length) this.grow();
94
+ this.slotOf.set(grid, s);
95
+ this.entity[s] = grid;
96
+ this.solved[s] = 0;
97
+ this.moved[s] = 1;
98
+ this.a[s * 12] = Number.NaN;
99
+ return s;
100
+ }
101
+ grow() {
102
+ const n = this.cellSize.length * 2;
103
+ const a = new Float64Array(n * 12);
104
+ a.set(this.a);
105
+ this.a = a;
106
+ const ref = new Int32Array(n * 3);
107
+ ref.set(this.ref);
108
+ this.ref = ref;
109
+ const cs = new Float64Array(n);
110
+ cs.set(this.cellSize);
111
+ this.cellSize = cs;
112
+ const f = new Float32Array(n * 12);
113
+ f.set(this.frame32);
114
+ this.frame32 = f;
115
+ const moved = new Uint8Array(n);
116
+ moved.set(this.moved);
117
+ this.moved = moved;
118
+ const solved = new Uint32Array(n);
119
+ solved.set(this.solved);
120
+ this.solved = solved;
121
+ const seen = new Uint32Array(n);
122
+ seen.set(this.seen);
123
+ this.seen = seen;
124
+ }
125
+ /** Drops slots whose grid no longer exists. Rare: only when grids despawn. */
126
+ compact() {
127
+ let w = 1;
128
+ this.slotOf.clear();
129
+ for (let s = 1; s < this.count; s++) {
130
+ if (this.seen[s] !== this.stamp) continue;
131
+ if (w !== s) {
132
+ this.entity[w] = this.entity[s];
133
+ this.a.copyWithin(w * 12, s * 12, s * 12 + 12);
134
+ this.ref.copyWithin(w * 3, s * 3, s * 3 + 3);
135
+ this.cellSize[w] = this.cellSize[s];
136
+ this.frame32.copyWithin(w * 12, s * 12, s * 12 + 12);
137
+ this.moved[w] = 1;
138
+ this.solved[w] = this.solved[s];
139
+ this.seen[w] = this.seen[s];
140
+ }
141
+ this.slotOf.set(this.entity[w], w);
142
+ w++;
143
+ }
144
+ this.count = w;
145
+ this.entity.length = w;
146
+ }
147
+ }
148
+ const GridFramesResource = defineResource("transform/GridFrames", {
149
+ description: "Grid frames relative to the floating origin, solved during transform propagation.",
150
+ init: () => new GridFrames()
151
+ });
152
+ function identity32(out, o) {
153
+ for (let i = 0; i < 12; i++) out[o + i] = 0;
154
+ out[o] = 1;
155
+ out[o + 5] = 1;
156
+ out[o + 10] = 1;
157
+ }
158
+ const createPlacement = () => ({
159
+ grid: ROOT,
160
+ cell: new Int32Array(3),
161
+ m: affine64.create(),
162
+ depth: 0
163
+ });
164
+ const trs64 = new Float64Array(12);
165
+ function parentOf(world, e) {
166
+ const table = world.entityTableUnchecked(e);
167
+ if (!table.has(ChildOf)) return ROOT;
168
+ const p = table.column(ChildOf, "parent")[world.entityRowUnchecked(e)];
169
+ return p >= 0 && world.isAlive(p) ? p : ROOT;
170
+ }
171
+ function isGrid(world, e) {
172
+ return e !== ROOT && world.entityTableUnchecked(e).has(Grid);
173
+ }
174
+ function localTRS(world, e, out) {
175
+ const table = world.entityTableUnchecked(e);
176
+ if (!table.has(Transform)) {
177
+ affine64.identity(out);
178
+ return;
179
+ }
180
+ const row = world.entityRowUnchecked(e);
181
+ affine64.fromTRSAt(
182
+ out,
183
+ 0,
184
+ table.column(Transform, "translation"),
185
+ row * 3,
186
+ table.column(Transform, "rotation"),
187
+ row * 4,
188
+ table.column(Transform, "scale"),
189
+ row * 3
190
+ );
191
+ }
192
+ function readCell(world, e, out) {
193
+ const table = world.entityTableUnchecked(e);
194
+ if (!table.has(GridCell)) {
195
+ out[0] = out[1] = out[2] = 0;
196
+ return;
197
+ }
198
+ const c = table.column(GridCell, "cell");
199
+ const o = world.entityRowUnchecked(e) * 3;
200
+ out[0] = c[o];
201
+ out[1] = c[o + 1];
202
+ out[2] = c[o + 2];
203
+ }
204
+ function placementOf(world, e, out, self = true) {
205
+ affine64.identity(out.m);
206
+ out.cell[0] = out.cell[1] = out.cell[2] = 0;
207
+ let cur = e;
208
+ let first = true;
209
+ out.depth = 0;
210
+ for (; ; ) {
211
+ if (self || !first) {
212
+ localTRS(world, cur, trs64);
213
+ affine64.multiply(out.m, trs64, out.m);
214
+ out.depth++;
215
+ }
216
+ first = false;
217
+ const parent = parentOf(world, cur);
218
+ if (parent === ROOT) {
219
+ out.grid = ROOT;
220
+ return out;
221
+ }
222
+ if (isGrid(world, parent)) {
223
+ out.grid = parent;
224
+ readCell(world, cur, out.cell);
225
+ return out;
226
+ }
227
+ cur = parent;
228
+ }
229
+ }
230
+ const place = createPlacement();
231
+ const inv64 = new Float64Array(12);
232
+ const tmp64 = new Float64Array(12);
233
+ const chain = [];
234
+ function cellSizeOf(world, grid) {
235
+ if (grid === ROOT) return 0;
236
+ const table = world.entityTableUnchecked(grid);
237
+ return table.column(Grid, "cellSize")[world.entityRowUnchecked(grid)];
238
+ }
239
+ function solveOrigin(world, frames, origin) {
240
+ if (origin === ROOT || !world.isAlive(origin)) {
241
+ frames.originSlot = 0;
242
+ frames.originEntity = ROOT;
243
+ frames.originCell[0] = frames.originCell[1] = frames.originCell[2] = 0;
244
+ return;
245
+ }
246
+ frames.originEntity = origin;
247
+ placementOf(world, origin, place, !isGrid(world, origin));
248
+ if (isGrid(world, origin)) {
249
+ const parent = parentOf(world, origin);
250
+ place.grid = isGrid(world, parent) ? parent : ROOT;
251
+ readCell(world, origin, place.cell);
252
+ }
253
+ frames.originSlot = place.grid === ROOT ? 0 : frames.slot(place.grid);
254
+ frames.originCell[0] = place.cell[0];
255
+ frames.originCell[1] = place.cell[1];
256
+ frames.originCell[2] = place.cell[2];
257
+ }
258
+ function setSolved(frames, s, a, rx, ry, rz) {
259
+ const o = s * 12;
260
+ const prev = frames.a;
261
+ let changed = frames.ref[s * 3] !== rx || frames.ref[s * 3 + 1] !== ry || frames.ref[s * 3 + 2] !== rz;
262
+ for (let i = 0; i < 12 && !changed; i++) if (prev[o + i] !== a[i]) changed = true;
263
+ if (changed) {
264
+ for (let i = 0; i < 12; i++) prev[o + i] = a[i];
265
+ frames.ref[s * 3] = rx;
266
+ frames.ref[s * 3 + 1] = ry;
267
+ frames.ref[s * 3 + 2] = rz;
268
+ const cs = frames.cellSize[s];
269
+ affine64.translateAt(tmp64, 0, a, 0, -rx * cs, -ry * cs, -rz * cs);
270
+ for (let i = 0; i < 12; i++) frames.frame32[o + i] = tmp64[i];
271
+ }
272
+ frames.moved[s] = changed ? 1 : 0;
273
+ frames.solved[s] = frames.stamp;
274
+ }
275
+ const solveA = new Float64Array(12);
276
+ function solveSlot(world, frames, s, depth) {
277
+ if (frames.solved[s] === frames.stamp) return;
278
+ if (depth > 64) {
279
+ throw new ShardError("transform/grid-cycle", "Grids nest more than 64 deep, or form a cycle", {
280
+ hint: "Check the ChildOf chain of your Grid entities."
281
+ });
282
+ }
283
+ const grid = frames.entity[s];
284
+ frames.cellSize[s] = cellSizeOf(world, grid);
285
+ const below = childTowardOrigin(world, frames, grid);
286
+ if (below !== void 0) {
287
+ const bs = frames.slot(below);
288
+ solveSlot(world, frames, bs, depth + 1);
289
+ placementOf(world, below, place, true);
290
+ const bcs = frames.cellSize[bs];
291
+ const bo = bs * 3;
292
+ affine64.translateAt(
293
+ solveA,
294
+ 0,
295
+ frames.a,
296
+ bs * 12,
297
+ -frames.ref[bo] * bcs,
298
+ -frames.ref[bo + 1] * bcs,
299
+ -frames.ref[bo + 2] * bcs
300
+ );
301
+ if (!affine64.invert(inv64, place.m)) affine64.identity(inv64);
302
+ affine64.multiply(solveA, solveA, inv64);
303
+ setSolved(frames, s, solveA, place.cell[0], place.cell[1], place.cell[2]);
304
+ return;
305
+ }
306
+ if (s === frames.originSlot) {
307
+ affine64.identity(solveA);
308
+ setSolved(
309
+ frames,
310
+ s,
311
+ solveA,
312
+ frames.originCell[0],
313
+ frames.originCell[1],
314
+ frames.originCell[2]
315
+ );
316
+ return;
317
+ }
318
+ placementOf(world, grid, place, true);
319
+ if (place.depth > 1) frames.indirect = true;
320
+ const ps = place.grid === ROOT ? 0 : frames.slot(place.grid);
321
+ solveSlot(world, frames, ps, depth + 1);
322
+ placementOf(world, grid, place, true);
323
+ const pcs = frames.cellSize[ps];
324
+ const po = ps * 3;
325
+ affine64.translateAt(
326
+ solveA,
327
+ 0,
328
+ frames.a,
329
+ ps * 12,
330
+ (place.cell[0] - frames.ref[po]) * pcs,
331
+ (place.cell[1] - frames.ref[po + 1]) * pcs,
332
+ (place.cell[2] - frames.ref[po + 2]) * pcs
333
+ );
334
+ affine64.multiply(solveA, solveA, place.m);
335
+ setSolved(frames, s, solveA, 0, 0, 0);
336
+ }
337
+ function childTowardOrigin(world, frames, grid) {
338
+ if (frames.originSlot === 0) return void 0;
339
+ for (let i = 0; i < chain.length; i++) {
340
+ const parent = i + 1 < chain.length ? chain[i + 1] : ROOT;
341
+ if (parent === grid) return chain[i];
342
+ }
343
+ void world;
344
+ return void 0;
345
+ }
346
+ function buildChain(world, frames) {
347
+ chain.length = 0;
348
+ if (frames.originSlot === 0) return;
349
+ let g = frames.entity[frames.originSlot];
350
+ for (let i = 0; i < 64 && g !== ROOT; i++) {
351
+ chain.push(g);
352
+ placementOf(world, g, place, true);
353
+ if (place.depth > 1) frames.indirect = true;
354
+ g = place.grid;
355
+ }
356
+ }
357
+ function gridsUnchanged(gridTables, since) {
358
+ for (let t2 = 0; t2 < gridTables.length; t2++) {
359
+ const table = gridTables[t2];
360
+ if (table.lastStructural > since) return false;
361
+ if (table.count === 0) continue;
362
+ if (table.lastChanged(Transform) > since || table.lastChanged(Grid) > since) return false;
363
+ if (table.has(GridCell) && table.lastChanged(GridCell) > since) return false;
364
+ }
365
+ return true;
366
+ }
367
+ function solveGrids(world, frames, gridTables, origin, shift, since = -1) {
368
+ frames.stamp++;
369
+ const prevSlot = frames.originSlot;
370
+ const prevGrid = prevSlot === 0 ? ROOT : frames.entity[prevSlot];
371
+ const px = frames.originCell[0];
372
+ const py = frames.originCell[1];
373
+ const pz = frames.originCell[2];
374
+ const prevCs = frames.cellSize[prevSlot];
375
+ const wasActive = frames.active;
376
+ if (since >= 0 && wasActive && !frames.indirect && origin === frames.originEntity && gridsUnchanged(gridTables, since)) {
377
+ solveOrigin(world, frames, origin);
378
+ if (frames.originSlot === prevSlot && frames.originCell[0] === px && frames.originCell[1] === py && frames.originCell[2] === pz) {
379
+ frames.moved.fill(0, 0, frames.count);
380
+ return false;
381
+ }
382
+ }
383
+ frames.indirect = false;
384
+ frames.seen[0] = frames.stamp;
385
+ for (let t2 = 0; t2 < gridTables.length; t2++) {
386
+ const table = gridTables[t2];
387
+ const list = table.entities;
388
+ for (let i = 0; i < table.count; i++)
389
+ frames.seen[frames.slot(list[i])] = frames.stamp;
390
+ }
391
+ solveOrigin(world, frames, origin);
392
+ if (frames.originSlot !== 0) frames.seen[frames.originSlot] = frames.stamp;
393
+ let stale = false;
394
+ for (let s = 1; s < frames.count; s++) if (frames.seen[s] !== frames.stamp) stale = true;
395
+ if (stale) {
396
+ const originGrid = frames.originSlot === 0 ? ROOT : frames.entity[frames.originSlot];
397
+ frames.compact();
398
+ frames.originSlot = originGrid === ROOT ? 0 : frames.slot(originGrid);
399
+ }
400
+ buildChain(world, frames);
401
+ frames.cellSize[0] = 0;
402
+ solveSlot(world, frames, 0, 0);
403
+ for (let s = 1; s < frames.count; s++) solveSlot(world, frames, s, 0);
404
+ frames.active = true;
405
+ const a = frames.a;
406
+ frames.rootIdentity = a[0] === 1 && a[1] === 0 && a[2] === 0 && a[3] === 0 && a[4] === 0 && a[5] === 1 && a[6] === 0 && a[7] === 0 && a[8] === 0 && a[9] === 0 && a[10] === 1 && a[11] === 0;
407
+ const nextGrid = frames.originSlot === 0 ? ROOT : frames.entity[frames.originSlot];
408
+ const nx = frames.originCell[0];
409
+ const ny = frames.originCell[1];
410
+ const nz = frames.originCell[2];
411
+ if (!wasActive) return false;
412
+ if (nextGrid === prevGrid) {
413
+ if (nx === px && ny === py && nz === pz) return false;
414
+ shift.grid = nextGrid === ROOT ? null : nextGrid;
415
+ shift.delta[0] = px - nx;
416
+ shift.delta[1] = py - ny;
417
+ shift.delta[2] = pz - nz;
418
+ shift.offset[0] = shift.delta[0] * prevCs;
419
+ shift.offset[1] = shift.delta[1] * prevCs;
420
+ shift.offset[2] = shift.delta[2] * prevCs;
421
+ return true;
422
+ }
423
+ shift.grid = nextGrid === ROOT ? null : nextGrid;
424
+ shift.delta[0] = shift.delta[1] = shift.delta[2] = 0;
425
+ const os = prevGrid === ROOT ? 0 : frames.slotOf.get(prevGrid);
426
+ if (os === void 0 || prevGrid !== ROOT && !world.isAlive(prevGrid)) {
427
+ shift.offset[0] = shift.offset[1] = shift.offset[2] = 0;
428
+ return true;
429
+ }
430
+ const cs = frames.cellSize[os];
431
+ const r = os * 3;
432
+ const ao = os * 12;
433
+ const x = (px - frames.ref[r]) * cs;
434
+ const y = (py - frames.ref[r + 1]) * cs;
435
+ const z = (pz - frames.ref[r + 2]) * cs;
436
+ shift.offset[0] = a[ao] * x + a[ao + 1] * y + a[ao + 2] * z + a[ao + 3];
437
+ shift.offset[1] = a[ao + 4] * x + a[ao + 5] * y + a[ao + 6] * z + a[ao + 7];
438
+ shift.offset[2] = a[ao + 8] * x + a[ao + 9] * y + a[ao + 10] * z + a[ao + 11];
439
+ return true;
440
+ }
441
+ function resetGrids(frames) {
442
+ if (!frames.active) {
443
+ frames.moved[0] = 0;
444
+ return false;
445
+ }
446
+ frames.active = false;
447
+ frames.count = 1;
448
+ frames.entity.length = 1;
449
+ frames.slotOf.clear();
450
+ frames.originSlot = 0;
451
+ frames.originEntity = ROOT;
452
+ frames.originCell.fill(0);
453
+ const wasIdentity = frames.rootIdentity;
454
+ affine64.identityAt(frames.a, 0);
455
+ identity32(frames.frame32, 0);
456
+ frames.rootIdentity = true;
457
+ frames.moved[0] = wasIdentity ? 0 : 1;
458
+ return !wasIdentity;
459
+ }
460
+ const helperFrames = new GridFrames();
461
+ const helperPlace = createPlacement();
462
+ const helperShift = { grid: null, delta: [0, 0, 0], offset: [0, 0, 0] };
463
+ function findOrigin(world) {
464
+ const q = world.query({ with: [FloatingOrigin] });
465
+ for (let t2 = 0; t2 < q.tables.length; t2++) {
466
+ const table = q.tables[t2];
467
+ if (table.count > 0) return table.entities[0];
468
+ }
469
+ return ROOT;
470
+ }
471
+ function solveHelper(world) {
472
+ helperFrames.active = false;
473
+ solveGrids(
474
+ world,
475
+ helperFrames,
476
+ world.query({ with: [Grid] }).tables,
477
+ findOrigin(world),
478
+ helperShift
479
+ );
480
+ return helperFrames;
481
+ }
482
+ function slotIn(frames, grid) {
483
+ return grid === ROOT ? 0 : frames.slot(grid);
484
+ }
485
+ function originMatrix64(world, e, out) {
486
+ const frames = solveHelper(world);
487
+ placementOf(world, e, helperPlace, true);
488
+ const s = slotIn(frames, helperPlace.grid);
489
+ const cs = frames.cellSize[s];
490
+ const r = s * 3;
491
+ affine64.translateAt(
492
+ out,
493
+ 0,
494
+ frames.a,
495
+ s * 12,
496
+ (helperPlace.cell[0] - frames.ref[r]) * cs,
497
+ (helperPlace.cell[1] - frames.ref[r + 1]) * cs,
498
+ (helperPlace.cell[2] - frames.ref[r + 2]) * cs
499
+ );
500
+ affine64.multiply(out, out, helperPlace.m);
501
+ return out;
502
+ }
503
+ const m64 = new Float64Array(12);
504
+ const frameInv = new Float64Array(12);
505
+ const p3 = new Float64Array(3);
506
+ function worldPosition64(world, e, out, frame) {
507
+ if (frame === void 0) {
508
+ originMatrix64(world, e, m64);
509
+ out[0] = m64[3];
510
+ out[1] = m64[7];
511
+ out[2] = m64[11];
512
+ return out;
513
+ }
514
+ placementOf(world, e, helperPlace, true);
515
+ if (helperPlace.grid === frame) {
516
+ const cs = cellSizeOf(world, frame);
517
+ out[0] = helperPlace.cell[0] * cs + helperPlace.m[3];
518
+ out[1] = helperPlace.cell[1] * cs + helperPlace.m[7];
519
+ out[2] = helperPlace.cell[2] * cs + helperPlace.m[11];
520
+ return out;
521
+ }
522
+ originMatrix64(world, e, m64);
523
+ p3[0] = m64[3];
524
+ p3[1] = m64[7];
525
+ p3[2] = m64[11];
526
+ if (isGrid(world, frame)) {
527
+ const s = slotIn(helperFrames, frame);
528
+ const cs = helperFrames.cellSize[s];
529
+ affine64.copyAt(frameInv, 0, helperFrames.a, s * 12);
530
+ if (!affine64.invert(frameInv, frameInv)) affine64.identity(frameInv);
531
+ affine64.transformPoint(out, frameInv, p3);
532
+ out[0] = out[0] + helperFrames.ref[s * 3] * cs;
533
+ out[1] = out[1] + helperFrames.ref[s * 3 + 1] * cs;
534
+ out[2] = out[2] + helperFrames.ref[s * 3 + 2] * cs;
535
+ return out;
536
+ }
537
+ originMatrix64(world, frame, frameInv);
538
+ if (!affine64.invert(frameInv, frameInv)) affine64.identity(frameInv);
539
+ affine64.transformPoint(out, frameInv, p3);
540
+ return out;
541
+ }
542
+ const pa = createPlacement();
543
+ const pb = createPlacement();
544
+ const va = new Float64Array(3);
545
+ const vb = new Float64Array(3);
546
+ function distance64(world, a, b) {
547
+ placementOf(world, a, pa, true);
548
+ placementOf(world, b, pb, true);
549
+ if (pa.grid === pb.grid) {
550
+ const cs = pa.grid === ROOT ? 0 : cellSizeOf(world, pa.grid);
551
+ const dx2 = (pa.cell[0] - pb.cell[0]) * cs + (pa.m[3] - pb.m[3]);
552
+ const dy2 = (pa.cell[1] - pb.cell[1]) * cs + (pa.m[7] - pb.m[7]);
553
+ const dz2 = (pa.cell[2] - pb.cell[2]) * cs + (pa.m[11] - pb.m[11]);
554
+ return Math.sqrt(dx2 * dx2 + dy2 * dy2 + dz2 * dz2);
555
+ }
556
+ worldPosition64(world, a, va);
557
+ worldPosition64(world, b, vb);
558
+ const dx = va[0] - vb[0];
559
+ const dy = va[1] - vb[1];
560
+ const dz = va[2] - vb[2];
561
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
562
+ }
563
+ function assertGrid(world, grid) {
564
+ if (!world.isAlive(grid) || !world.has(grid, Grid)) {
565
+ throw new ShardError("transform/not-a-grid", `Entity ${grid} is not a Grid`, {
566
+ hint: "Pass an entity that has the transform/Grid component."
567
+ });
568
+ }
569
+ }
570
+ function placeInGrid(world, e, grid, position) {
571
+ assertGrid(world, grid);
572
+ const cs = cellSizeOf(world, grid);
573
+ const cx = Math.round(position[0] / cs);
574
+ const cy = Math.round(position[1] / cs);
575
+ const cz = Math.round(position[2] / cs);
576
+ const translation = [
577
+ position[0] - cx * cs,
578
+ position[1] - cy * cs,
579
+ position[2] - cz * cs
580
+ ];
581
+ if (parentOf(world, e) !== grid) world.add(e, ChildOf, { parent: grid });
582
+ if (world.has(e, Transform)) world.set(e, Transform, { translation });
583
+ else world.add(e, Transform, { translation });
584
+ world.add(e, GridCell, { cell: [cx, cy, cz] });
585
+ }
586
+ const pose64 = new Float64Array(12);
587
+ const local64 = new Float64Array(12);
588
+ const basis = [new Float64Array(3), new Float64Array(3), new Float64Array(3)];
589
+ function reparentToGrid(world, e, grid) {
590
+ assertGrid(world, grid);
591
+ originMatrix64(world, e, pose64);
592
+ const frames = helperFrames;
593
+ const s = slotIn(frames, grid);
594
+ const cs = frames.cellSize[s];
595
+ affine64.copyAt(frameInv, 0, frames.a, s * 12);
596
+ if (!affine64.invert(frameInv, frameInv)) affine64.identity(frameInv);
597
+ affine64.multiply(local64, frameInv, pose64);
598
+ const rx = frames.ref[s * 3];
599
+ const ry = frames.ref[s * 3 + 1];
600
+ const rz = frames.ref[s * 3 + 2];
601
+ const kx = Math.round(local64[3] / cs);
602
+ const ky = Math.round(local64[7] / cs);
603
+ const kz = Math.round(local64[11] / cs);
604
+ const translation = [
605
+ local64[3] - kx * cs,
606
+ local64[7] - ky * cs,
607
+ local64[11] - kz * cs
608
+ ];
609
+ const scale = [0, 0, 0];
610
+ for (let c = 0; c < 3; c++) {
611
+ const x = local64[c];
612
+ const y = local64[4 + c];
613
+ const z = local64[8 + c];
614
+ const len = Math.sqrt(x * x + y * y + z * z) || 1;
615
+ scale[c] = len;
616
+ const axis = basis[c];
617
+ axis[0] = x / len;
618
+ axis[1] = y / len;
619
+ axis[2] = z / len;
620
+ }
621
+ const rotation = quat.fromBasis([0, 0, 0, 1], basis[0], basis[1], basis[2]);
622
+ if (parentOf(world, e) !== grid) world.add(e, ChildOf, { parent: grid });
623
+ world.add(e, Transform, { translation, rotation, scale });
624
+ world.add(e, GridCell, { cell: [rx + kx, ry + ky, rz + kz] });
625
+ }
626
+ function gridOf(world, e) {
627
+ const p = parentOf(world, e);
628
+ return p !== ROOT && isGrid(world, p) ? p : void 0;
629
+ }
630
+ export {
631
+ FloatingOrigin,
632
+ Grid,
633
+ GridCell,
634
+ GridFrames,
635
+ GridFramesResource,
636
+ OriginShift,
637
+ createPlacement,
638
+ distance64,
639
+ gridOf,
640
+ isGrid,
641
+ originMatrix64,
642
+ placeInGrid,
643
+ placementOf,
644
+ reparentToGrid,
645
+ resetGrids,
646
+ solveGrids,
647
+ worldPosition64
648
+ };
@@ -0,0 +1,3 @@
1
+ export { GlobalTransform, Transform, type TransformValue } from './components.js';
2
+ export { createPlacement, distance64, FloatingOrigin, Grid, GridCell, GridFrames, GridFramesResource, gridOf, OriginShift, type OriginShiftData, originMatrix64, type Placement, placeInGrid, placementOf, reparentToGrid, worldPosition64, } from './grid.js';
3
+ export { lookAt, propagateSubtree, propagateTransforms, recenterGridCells, TransformPlugin, TransformSystems, transform2d, worldPosition, } from './transform.js';
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import { GlobalTransform, Transform } from "./components.js";
2
+ import {
3
+ createPlacement,
4
+ distance64,
5
+ FloatingOrigin,
6
+ Grid,
7
+ GridCell,
8
+ GridFrames,
9
+ GridFramesResource,
10
+ gridOf,
11
+ OriginShift,
12
+ originMatrix64,
13
+ placeInGrid,
14
+ placementOf,
15
+ reparentToGrid,
16
+ worldPosition64
17
+ } from "./grid.js";
18
+ import {
19
+ lookAt,
20
+ propagateSubtree,
21
+ propagateTransforms,
22
+ recenterGridCells,
23
+ TransformPlugin,
24
+ TransformSystems,
25
+ transform2d,
26
+ worldPosition
27
+ } from "./transform.js";
28
+ export {
29
+ FloatingOrigin,
30
+ GlobalTransform,
31
+ Grid,
32
+ GridCell,
33
+ GridFrames,
34
+ GridFramesResource,
35
+ OriginShift,
36
+ Transform,
37
+ TransformPlugin,
38
+ TransformSystems,
39
+ createPlacement,
40
+ distance64,
41
+ gridOf,
42
+ lookAt,
43
+ originMatrix64,
44
+ placeInGrid,
45
+ placementOf,
46
+ propagateSubtree,
47
+ propagateTransforms,
48
+ recenterGridCells,
49
+ reparentToGrid,
50
+ transform2d,
51
+ worldPosition,
52
+ worldPosition64
53
+ };