@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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/components.d.ts +10 -0
- package/dist/components.js +30 -0
- package/dist/grid.d.ts +124 -0
- package/dist/grid.js +648 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +53 -0
- package/dist/transform.d.ts +68 -0
- package/dist/transform.js +585 -0
- package/package.json +34 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Entity, type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { GlobalTransform, Transform, type TransformValue } from './components.js';
|
|
3
|
+
import { type GridFrames, type OriginShiftData } from './grid.js';
|
|
4
|
+
export { GlobalTransform, Transform, type TransformValue };
|
|
5
|
+
/** Transform propagation runs in this set, in PostUpdate. Order systems that read world matrices after it. */
|
|
6
|
+
export declare const TransformSystems: import("@aethervtt/shard-core").SystemSetDef;
|
|
7
|
+
/** A `Transform` value for 2D: position, angle in radians around Z, uniform or per-axis scale. */
|
|
8
|
+
export declare function transform2d(options?: {
|
|
9
|
+
x?: number;
|
|
10
|
+
y?: number;
|
|
11
|
+
z?: number;
|
|
12
|
+
angle?: number;
|
|
13
|
+
scale?: number | [number, number];
|
|
14
|
+
}): TransformValue;
|
|
15
|
+
/** A rotation that makes something at `from` face `target` (its -Z toward the target). */
|
|
16
|
+
export declare function lookAt(from: ArrayLike<number>, target: ArrayLike<number>, up?: ArrayLike<number>): [number, number, number, number];
|
|
17
|
+
/** World-space position from `GlobalTransform`. Cold path. */
|
|
18
|
+
export declare function worldPosition(world: World, entity: Entity): [number, number, number];
|
|
19
|
+
/** Columns of one table, looked up once per propagation run instead of once per entity. */
|
|
20
|
+
interface TableColumns {
|
|
21
|
+
stamp: number;
|
|
22
|
+
hasTransform: boolean;
|
|
23
|
+
hasGlobal: boolean;
|
|
24
|
+
translation: Float32Array | undefined;
|
|
25
|
+
rotation: Float32Array | undefined;
|
|
26
|
+
scale: Float32Array | undefined;
|
|
27
|
+
changed: Uint32Array | undefined;
|
|
28
|
+
global: Float32Array | undefined;
|
|
29
|
+
globalChanged: Uint32Array | undefined;
|
|
30
|
+
children: ((Entity | null)[] | undefined)[] | undefined;
|
|
31
|
+
isGrid: boolean;
|
|
32
|
+
cell: Int32Array | undefined;
|
|
33
|
+
cellChanged: Uint32Array | undefined;
|
|
34
|
+
}
|
|
35
|
+
interface Walk {
|
|
36
|
+
world: World;
|
|
37
|
+
since: number;
|
|
38
|
+
tick: number;
|
|
39
|
+
cache: (TableColumns | undefined)[];
|
|
40
|
+
frames: GridFrames;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Computes `GlobalTransform` from `Transform` and the `ChildOf` hierarchy. Roots are a hot loop over
|
|
44
|
+
* columns; children are walked depth-first. Only entities whose transform (or an ancestor's)
|
|
45
|
+
* changed since the last run are recomputed, so untouched subtrees keep their change ticks.
|
|
46
|
+
*/
|
|
47
|
+
export declare const propagateTransforms: import("@aethervtt/shard-core").SystemDef<{
|
|
48
|
+
roots: import("@aethervtt/shard-core").Query;
|
|
49
|
+
grids: import("@aethervtt/shard-core").Query;
|
|
50
|
+
origins: import("@aethervtt/shard-core").Query;
|
|
51
|
+
walk: Walk;
|
|
52
|
+
shift: OriginShiftData;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Recomputes `GlobalTransform` for `entity` and everything under it from its parent's current
|
|
56
|
+
* world matrix, for systems that change local transforms after propagation (IK). Marks the
|
|
57
|
+
* matrices changed this tick. Allocation-free.
|
|
58
|
+
*/
|
|
59
|
+
export declare function propagateSubtree(world: World, entity: Entity): void;
|
|
60
|
+
/**
|
|
61
|
+
* Moves grid children whose translation left their cell (past half a cell plus the grid's
|
|
62
|
+
* hysteresis) by whole cells: `cell += k`, `translation -= k × cellSize`. The position doesn't
|
|
63
|
+
* change, so nothing jumps. Only entities whose Transform changed are looked at.
|
|
64
|
+
*/
|
|
65
|
+
export declare const recenterGridCells: import("@aethervtt/shard-core").SystemDef<{
|
|
66
|
+
cells: import("@aethervtt/shard-core").Query;
|
|
67
|
+
}>;
|
|
68
|
+
export declare const TransformPlugin: import("@aethervtt/shard-runtime").Plugin;
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import {
|
|
2
|
+
affine,
|
|
3
|
+
ChildOf,
|
|
4
|
+
Children,
|
|
5
|
+
defineSystem,
|
|
6
|
+
defineSystemSet,
|
|
7
|
+
onAdd,
|
|
8
|
+
onRemove,
|
|
9
|
+
onSet,
|
|
10
|
+
PostUpdate,
|
|
11
|
+
quat,
|
|
12
|
+
vec3
|
|
13
|
+
} from "@aethervtt/shard-core";
|
|
14
|
+
import { definePlugin } from "@aethervtt/shard-runtime";
|
|
15
|
+
import { GlobalTransform, Transform } from "./components.js";
|
|
16
|
+
import {
|
|
17
|
+
FloatingOrigin,
|
|
18
|
+
Grid,
|
|
19
|
+
GridCell,
|
|
20
|
+
GridFramesResource,
|
|
21
|
+
OriginShift,
|
|
22
|
+
resetGrids,
|
|
23
|
+
solveGrids
|
|
24
|
+
} from "./grid.js";
|
|
25
|
+
const TransformSystems = defineSystemSet("core/TransformSystems");
|
|
26
|
+
function transform2d(options = {}) {
|
|
27
|
+
const { x = 0, y = 0, z = 0, angle = 0, scale = 1 } = options;
|
|
28
|
+
const [sx, sy] = typeof scale === "number" ? [scale, scale] : scale;
|
|
29
|
+
return {
|
|
30
|
+
translation: [x, y, z],
|
|
31
|
+
rotation: [0, 0, Math.sin(angle / 2), Math.cos(angle / 2)],
|
|
32
|
+
scale: [sx, sy, 1]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function lookAt(from, target, up = [0, 1, 0]) {
|
|
36
|
+
const forward = vec3.sub([0, 0, 0], target, from);
|
|
37
|
+
return quat.lookRotation([0, 0, 0, 1], forward, up);
|
|
38
|
+
}
|
|
39
|
+
function worldPosition(world, entity) {
|
|
40
|
+
const table = world.entityTable(entity);
|
|
41
|
+
const matrix = table.column(GlobalTransform, "matrix");
|
|
42
|
+
return affine.getTranslationAt([0, 0, 0], matrix, world.entityRow(entity) * 12);
|
|
43
|
+
}
|
|
44
|
+
const scratch = affine.create();
|
|
45
|
+
function columnsOf(walk, table) {
|
|
46
|
+
let c = walk.cache[table.id];
|
|
47
|
+
if (!c) {
|
|
48
|
+
c = {
|
|
49
|
+
stamp: -1,
|
|
50
|
+
hasTransform: false,
|
|
51
|
+
hasGlobal: false,
|
|
52
|
+
translation: void 0,
|
|
53
|
+
rotation: void 0,
|
|
54
|
+
scale: void 0,
|
|
55
|
+
changed: void 0,
|
|
56
|
+
global: void 0,
|
|
57
|
+
globalChanged: void 0,
|
|
58
|
+
children: void 0,
|
|
59
|
+
isGrid: false,
|
|
60
|
+
cell: void 0,
|
|
61
|
+
cellChanged: void 0
|
|
62
|
+
};
|
|
63
|
+
walk.cache[table.id] = c;
|
|
64
|
+
}
|
|
65
|
+
if (c.stamp !== walk.tick) {
|
|
66
|
+
c.stamp = walk.tick;
|
|
67
|
+
c.hasTransform = table.has(Transform);
|
|
68
|
+
c.hasGlobal = table.has(GlobalTransform);
|
|
69
|
+
c.translation = c.hasTransform ? table.column(Transform, "translation") : void 0;
|
|
70
|
+
c.rotation = c.hasTransform ? table.column(Transform, "rotation") : void 0;
|
|
71
|
+
c.scale = c.hasTransform ? table.column(Transform, "scale") : void 0;
|
|
72
|
+
c.changed = c.hasTransform ? table.changedTicks(Transform) : void 0;
|
|
73
|
+
c.global = c.hasGlobal ? table.column(GlobalTransform, "matrix") : void 0;
|
|
74
|
+
c.globalChanged = c.hasGlobal ? table.changedTicks(GlobalTransform) : void 0;
|
|
75
|
+
c.children = table.has(Children) ? table.column(Children, "entities") : void 0;
|
|
76
|
+
c.isGrid = table.has(Grid);
|
|
77
|
+
const hasCell = table.has(GridCell);
|
|
78
|
+
c.cell = hasCell ? table.column(GridCell, "cell") : void 0;
|
|
79
|
+
c.cellChanged = hasCell ? table.changedTicks(GridCell) : void 0;
|
|
80
|
+
}
|
|
81
|
+
return c;
|
|
82
|
+
}
|
|
83
|
+
const propagateTransforms = defineSystem({
|
|
84
|
+
name: "core/transform-propagate",
|
|
85
|
+
description: "Solves grid frames against the floating origin, then computes GlobalTransform from Transform and the parent hierarchy.",
|
|
86
|
+
setup: (world) => ({
|
|
87
|
+
roots: world.query({ with: [Transform, GlobalTransform], without: [ChildOf] }),
|
|
88
|
+
grids: world.query({ with: [Grid] }),
|
|
89
|
+
origins: world.query({ with: [FloatingOrigin] }),
|
|
90
|
+
walk: {
|
|
91
|
+
world,
|
|
92
|
+
since: 0,
|
|
93
|
+
tick: 0,
|
|
94
|
+
cache: [],
|
|
95
|
+
frames: world.initResource(GridFramesResource)
|
|
96
|
+
},
|
|
97
|
+
shift: { grid: null, delta: [0, 0, 0], offset: [0, 0, 0] }
|
|
98
|
+
}),
|
|
99
|
+
run: ({ roots, grids, origins, walk, shift }, world, ctx) => {
|
|
100
|
+
walk.since = ctx.lastRunTick;
|
|
101
|
+
walk.tick = world.tick;
|
|
102
|
+
const since = walk.since;
|
|
103
|
+
const tick = walk.tick;
|
|
104
|
+
const frames = walk.frames;
|
|
105
|
+
let gridCount = 0;
|
|
106
|
+
for (let t = 0; t < grids.tables.length; t++) gridCount += grids.tables[t].count;
|
|
107
|
+
if (gridCount > 0) {
|
|
108
|
+
let origin = -1;
|
|
109
|
+
for (let t = 0; t < origins.tables.length && origin < 0; t++) {
|
|
110
|
+
const table = origins.tables[t];
|
|
111
|
+
if (table.count > 0) origin = table.entities[0];
|
|
112
|
+
}
|
|
113
|
+
if (solveGrids(world, frames, grids.tables, origin, shift, since)) {
|
|
114
|
+
const data = {
|
|
115
|
+
grid: shift.grid,
|
|
116
|
+
delta: [shift.delta[0], shift.delta[1], shift.delta[2]],
|
|
117
|
+
offset: [shift.offset[0], shift.offset[1], shift.offset[2]]
|
|
118
|
+
};
|
|
119
|
+
world.send(OriginShift, data);
|
|
120
|
+
world.trigger(OriginShift, data);
|
|
121
|
+
}
|
|
122
|
+
} else resetGrids(frames);
|
|
123
|
+
const rootIdentity = frames.rootIdentity;
|
|
124
|
+
const rootMoved = frames.moved[0] === 1;
|
|
125
|
+
const rootFrame = frames.frame32;
|
|
126
|
+
const tables = roots.tables;
|
|
127
|
+
for (let ti = 0; ti < tables.length; ti++) {
|
|
128
|
+
const table = tables[ti];
|
|
129
|
+
const n = table.count;
|
|
130
|
+
if (n === 0) continue;
|
|
131
|
+
const c = columnsOf(walk, table);
|
|
132
|
+
if (c.isGrid) {
|
|
133
|
+
propagateGridRows(walk, table, c);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const tr = c.translation;
|
|
137
|
+
const ro = c.rotation;
|
|
138
|
+
const sc = c.scale;
|
|
139
|
+
const g = c.global;
|
|
140
|
+
const changed = c.changed;
|
|
141
|
+
const globalChanged = c.globalChanged;
|
|
142
|
+
const children = c.children;
|
|
143
|
+
let moved = false;
|
|
144
|
+
if (!rootIdentity) {
|
|
145
|
+
for (let i = 0; i < n; i++) {
|
|
146
|
+
const dirty = rootMoved || changed[i] > since;
|
|
147
|
+
if (dirty) {
|
|
148
|
+
affine.fromTRSAt(scratch, 0, tr, i * 3, ro, i * 4, sc, i * 3);
|
|
149
|
+
affine.multiplyAt(g, i * 12, rootFrame, 0, scratch, 0);
|
|
150
|
+
globalChanged[i] = tick;
|
|
151
|
+
moved = true;
|
|
152
|
+
}
|
|
153
|
+
if (children !== void 0) {
|
|
154
|
+
const list = children[i];
|
|
155
|
+
if (list) propagateChildren(walk, list, g, i * 12, dirty);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (moved) table.touch(GlobalTransform);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
for (let i = 0; i < n; i++) {
|
|
162
|
+
const dirty = rootMoved || changed[i] > since;
|
|
163
|
+
if (dirty) {
|
|
164
|
+
const i3 = i * 3;
|
|
165
|
+
const i4 = i * 4;
|
|
166
|
+
const o = i * 12;
|
|
167
|
+
const x = ro[i4];
|
|
168
|
+
const y = ro[i4 + 1];
|
|
169
|
+
const z = ro[i4 + 2];
|
|
170
|
+
const w = ro[i4 + 3];
|
|
171
|
+
const sx = sc[i3];
|
|
172
|
+
const sy = sc[i3 + 1];
|
|
173
|
+
const sz = sc[i3 + 2];
|
|
174
|
+
const x2 = x + x;
|
|
175
|
+
const y2 = y + y;
|
|
176
|
+
const z2 = z + z;
|
|
177
|
+
const xx = x * x2;
|
|
178
|
+
const yy = y * y2;
|
|
179
|
+
const zz = z * z2;
|
|
180
|
+
const xy = x * y2;
|
|
181
|
+
const xz = x * z2;
|
|
182
|
+
const yz = y * z2;
|
|
183
|
+
const wx = w * x2;
|
|
184
|
+
const wy = w * y2;
|
|
185
|
+
const wz = w * z2;
|
|
186
|
+
g[o] = (1 - yy - zz) * sx;
|
|
187
|
+
g[o + 1] = (xy - wz) * sy;
|
|
188
|
+
g[o + 2] = (xz + wy) * sz;
|
|
189
|
+
g[o + 3] = tr[i3];
|
|
190
|
+
g[o + 4] = (xy + wz) * sx;
|
|
191
|
+
g[o + 5] = (1 - xx - zz) * sy;
|
|
192
|
+
g[o + 6] = (yz - wx) * sz;
|
|
193
|
+
g[o + 7] = tr[i3 + 1];
|
|
194
|
+
g[o + 8] = (xz - wy) * sx;
|
|
195
|
+
g[o + 9] = (yz + wx) * sy;
|
|
196
|
+
g[o + 10] = (1 - xx - yy) * sz;
|
|
197
|
+
g[o + 11] = tr[i3 + 2];
|
|
198
|
+
globalChanged[i] = tick;
|
|
199
|
+
moved = true;
|
|
200
|
+
}
|
|
201
|
+
if (children !== void 0) {
|
|
202
|
+
const list = children[i];
|
|
203
|
+
if (list) propagateChildren(walk, list, g, i * 12, dirty);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (moved) table.touch(GlobalTransform);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
function propagateGridRows(walk, table, c) {
|
|
211
|
+
const frames = walk.frames;
|
|
212
|
+
const g = c.global;
|
|
213
|
+
const globalChanged = c.globalChanged;
|
|
214
|
+
const children = c.children;
|
|
215
|
+
let moved = false;
|
|
216
|
+
for (let i = 0; i < table.count; i++) {
|
|
217
|
+
const slot = frames.slotOf.get(table.entities[i]);
|
|
218
|
+
if (slot === void 0) continue;
|
|
219
|
+
const dirty = frames.moved[slot] === 1;
|
|
220
|
+
if (dirty) {
|
|
221
|
+
affine.copyAt(g, i * 12, frames.frame32, slot * 12);
|
|
222
|
+
globalChanged[i] = walk.tick;
|
|
223
|
+
moved = true;
|
|
224
|
+
}
|
|
225
|
+
if (children !== void 0) {
|
|
226
|
+
const list = children[i];
|
|
227
|
+
if (list) propagateGridChildren(walk, list, slot, dirty);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (moved) table.touch(GlobalTransform);
|
|
231
|
+
}
|
|
232
|
+
function propagateGridChildren(walk, list, slot, gridDirty) {
|
|
233
|
+
const world = walk.world;
|
|
234
|
+
const frames = walk.frames;
|
|
235
|
+
for (let k = 0; k < list.length; k++) {
|
|
236
|
+
const child = list[k];
|
|
237
|
+
if (child === null || child === void 0) continue;
|
|
238
|
+
const table = world.entityTableUnchecked(child);
|
|
239
|
+
const row = world.entityRowUnchecked(child);
|
|
240
|
+
const c = columnsOf(walk, table);
|
|
241
|
+
if (c.isGrid) {
|
|
242
|
+
const s = frames.slotOf.get(child);
|
|
243
|
+
if (s === void 0) continue;
|
|
244
|
+
const dirty2 = frames.moved[s] === 1;
|
|
245
|
+
if (dirty2 && c.hasGlobal) {
|
|
246
|
+
affine.copyAt(c.global, row * 12, frames.frame32, s * 12);
|
|
247
|
+
c.globalChanged[row] = walk.tick;
|
|
248
|
+
table.touch(GlobalTransform);
|
|
249
|
+
}
|
|
250
|
+
const grandchildren2 = c.children?.[row];
|
|
251
|
+
if (grandchildren2) propagateGridChildren(walk, grandchildren2, s, dirty2);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!c.hasGlobal) {
|
|
255
|
+
const grandchildren2 = c.children?.[row];
|
|
256
|
+
if (grandchildren2) {
|
|
257
|
+
propagateChildren(walk, grandchildren2, frames.frame32, slot * 12, gridDirty);
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
let dirty = gridDirty;
|
|
262
|
+
if (c.hasTransform) dirty ||= c.changed[row] > walk.since;
|
|
263
|
+
if (c.cellChanged !== void 0) dirty ||= c.cellChanged[row] > walk.since;
|
|
264
|
+
if (dirty) {
|
|
265
|
+
writeGridChild(frames, slot, c, row);
|
|
266
|
+
c.globalChanged[row] = walk.tick;
|
|
267
|
+
table.touch(GlobalTransform);
|
|
268
|
+
}
|
|
269
|
+
const grandchildren = c.children?.[row];
|
|
270
|
+
if (grandchildren) propagateChildren(walk, grandchildren, c.global, row * 12, dirty);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function writeGridChild(frames, slot, c, row) {
|
|
274
|
+
const a = frames.a;
|
|
275
|
+
const ao = slot * 12;
|
|
276
|
+
const cs = frames.cellSize[slot];
|
|
277
|
+
const ref = frames.ref;
|
|
278
|
+
const r3 = slot * 3;
|
|
279
|
+
const cell = c.cell;
|
|
280
|
+
const o3 = row * 3;
|
|
281
|
+
let px = cell === void 0 ? -ref[r3] * cs : (cell[o3] - ref[r3]) * cs;
|
|
282
|
+
let py = cell === void 0 ? -ref[r3 + 1] * cs : (cell[o3 + 1] - ref[r3 + 1]) * cs;
|
|
283
|
+
let pz = cell === void 0 ? -ref[r3 + 2] * cs : (cell[o3 + 2] - ref[r3 + 2]) * cs;
|
|
284
|
+
let l00 = 1;
|
|
285
|
+
let l01 = 0;
|
|
286
|
+
let l02 = 0;
|
|
287
|
+
let l10 = 0;
|
|
288
|
+
let l11 = 1;
|
|
289
|
+
let l12 = 0;
|
|
290
|
+
let l20 = 0;
|
|
291
|
+
let l21 = 0;
|
|
292
|
+
let l22 = 1;
|
|
293
|
+
if (c.hasTransform) {
|
|
294
|
+
const tr = c.translation;
|
|
295
|
+
const ro = c.rotation;
|
|
296
|
+
const sc = c.scale;
|
|
297
|
+
const o4 = row * 4;
|
|
298
|
+
px += tr[o3];
|
|
299
|
+
py += tr[o3 + 1];
|
|
300
|
+
pz += tr[o3 + 2];
|
|
301
|
+
const x = ro[o4];
|
|
302
|
+
const y = ro[o4 + 1];
|
|
303
|
+
const z = ro[o4 + 2];
|
|
304
|
+
const w = ro[o4 + 3];
|
|
305
|
+
const sx = sc[o3];
|
|
306
|
+
const sy = sc[o3 + 1];
|
|
307
|
+
const sz = sc[o3 + 2];
|
|
308
|
+
const x2 = x + x;
|
|
309
|
+
const y2 = y + y;
|
|
310
|
+
const z2 = z + z;
|
|
311
|
+
const xx = x * x2;
|
|
312
|
+
const yy = y * y2;
|
|
313
|
+
const zz = z * z2;
|
|
314
|
+
const xy = x * y2;
|
|
315
|
+
const xz = x * z2;
|
|
316
|
+
const yz = y * z2;
|
|
317
|
+
const wx = w * x2;
|
|
318
|
+
const wy = w * y2;
|
|
319
|
+
const wz = w * z2;
|
|
320
|
+
l00 = (1 - yy - zz) * sx;
|
|
321
|
+
l01 = (xy - wz) * sy;
|
|
322
|
+
l02 = (xz + wy) * sz;
|
|
323
|
+
l10 = (xy + wz) * sx;
|
|
324
|
+
l11 = (1 - xx - zz) * sy;
|
|
325
|
+
l12 = (yz - wx) * sz;
|
|
326
|
+
l20 = (xz - wy) * sx;
|
|
327
|
+
l21 = (yz + wx) * sy;
|
|
328
|
+
l22 = (1 - xx - yy) * sz;
|
|
329
|
+
}
|
|
330
|
+
const g = c.global;
|
|
331
|
+
const o = row * 12;
|
|
332
|
+
for (let r = 0; r < 3; r++) {
|
|
333
|
+
const a0 = a[ao + r * 4];
|
|
334
|
+
const a1 = a[ao + r * 4 + 1];
|
|
335
|
+
const a2 = a[ao + r * 4 + 2];
|
|
336
|
+
g[o + r * 4] = a0 * l00 + a1 * l10 + a2 * l20;
|
|
337
|
+
g[o + r * 4 + 1] = a0 * l01 + a1 * l11 + a2 * l21;
|
|
338
|
+
g[o + r * 4 + 2] = a0 * l02 + a1 * l12 + a2 * l22;
|
|
339
|
+
g[o + r * 4 + 3] = a0 * px + a1 * py + a2 * pz + a[ao + r * 4 + 3];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function propagateChildren(walk, list, parent, parentOffset, parentDirty) {
|
|
343
|
+
const world = walk.world;
|
|
344
|
+
for (let k = 0; k < list.length; k++) {
|
|
345
|
+
const child = list[k];
|
|
346
|
+
if (child === null || child === void 0) continue;
|
|
347
|
+
const table = world.entityTableUnchecked(child);
|
|
348
|
+
const row = world.entityRowUnchecked(child);
|
|
349
|
+
const c = columnsOf(walk, table);
|
|
350
|
+
if (c.isGrid) {
|
|
351
|
+
const frames = walk.frames;
|
|
352
|
+
const s = frames.slotOf.get(child);
|
|
353
|
+
if (s === void 0) continue;
|
|
354
|
+
const moved = frames.moved[s] === 1;
|
|
355
|
+
if (moved) {
|
|
356
|
+
affine.copyAt(c.global, row * 12, frames.frame32, s * 12);
|
|
357
|
+
c.globalChanged[row] = walk.tick;
|
|
358
|
+
table.touch(GlobalTransform);
|
|
359
|
+
}
|
|
360
|
+
const inside = c.children?.[row];
|
|
361
|
+
if (inside) propagateGridChildren(walk, inside, s, moved);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
let dirty = parentDirty;
|
|
365
|
+
let matrix = parent;
|
|
366
|
+
let offset = parentOffset;
|
|
367
|
+
if (c.hasGlobal) {
|
|
368
|
+
matrix = c.global;
|
|
369
|
+
offset = row * 12;
|
|
370
|
+
if (c.hasTransform) {
|
|
371
|
+
dirty ||= c.changed[row] > walk.since;
|
|
372
|
+
if (dirty) {
|
|
373
|
+
affine.fromTRSAt(
|
|
374
|
+
scratch,
|
|
375
|
+
0,
|
|
376
|
+
c.translation,
|
|
377
|
+
row * 3,
|
|
378
|
+
c.rotation,
|
|
379
|
+
row * 4,
|
|
380
|
+
c.scale,
|
|
381
|
+
row * 3
|
|
382
|
+
);
|
|
383
|
+
affine.multiplyAt(matrix, offset, parent, parentOffset, scratch, 0);
|
|
384
|
+
}
|
|
385
|
+
} else if (dirty) {
|
|
386
|
+
affine.copyAt(matrix, offset, parent, parentOffset);
|
|
387
|
+
}
|
|
388
|
+
if (dirty) {
|
|
389
|
+
c.globalChanged[row] = walk.tick;
|
|
390
|
+
table.touch(GlobalTransform);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const grandchildren = c.children?.[row];
|
|
394
|
+
if (grandchildren) propagateChildren(walk, grandchildren, matrix, offset, dirty);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const subtreeScratch = affine.create();
|
|
398
|
+
function propagateSubtree(world, entity) {
|
|
399
|
+
if (!world.isAlive(entity)) return;
|
|
400
|
+
const table = world.entityTableUnchecked(entity);
|
|
401
|
+
if (!table.has(GlobalTransform)) return;
|
|
402
|
+
const row = world.entityRowUnchecked(entity);
|
|
403
|
+
const g = table.column(GlobalTransform, "matrix");
|
|
404
|
+
let parent = -1;
|
|
405
|
+
if (table.has(ChildOf)) parent = table.column(ChildOf, "parent")[row];
|
|
406
|
+
const frames = world.tryResource(GridFramesResource);
|
|
407
|
+
if (parent >= 0 && world.isAlive(parent)) {
|
|
408
|
+
const pt = world.entityTableUnchecked(parent);
|
|
409
|
+
const slot = frames?.active && pt.has(Grid) ? frames.slotOf.get(parent) : void 0;
|
|
410
|
+
if (slot !== void 0) {
|
|
411
|
+
writeGridChild(frames, slot, subtreeColumns(table), row);
|
|
412
|
+
table.changedTicks(GlobalTransform)[row] = world.tick;
|
|
413
|
+
table.touch(GlobalTransform);
|
|
414
|
+
subtreeChildren(world, table, row, g, row * 12);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (pt.has(GlobalTransform)) {
|
|
418
|
+
subtreeNode(
|
|
419
|
+
world,
|
|
420
|
+
table,
|
|
421
|
+
row,
|
|
422
|
+
pt.column(GlobalTransform, "matrix"),
|
|
423
|
+
world.entityRowUnchecked(parent) * 12
|
|
424
|
+
);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (table.has(Transform)) {
|
|
429
|
+
affine.fromTRSAt(
|
|
430
|
+
g,
|
|
431
|
+
row * 12,
|
|
432
|
+
table.column(Transform, "translation"),
|
|
433
|
+
row * 3,
|
|
434
|
+
table.column(Transform, "rotation"),
|
|
435
|
+
row * 4,
|
|
436
|
+
table.column(Transform, "scale"),
|
|
437
|
+
row * 3
|
|
438
|
+
);
|
|
439
|
+
if (frames && !frames.rootIdentity) {
|
|
440
|
+
affine.copyAt(subtreeScratch, 0, g, row * 12);
|
|
441
|
+
affine.multiplyAt(g, row * 12, frames.frame32, 0, subtreeScratch, 0);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
table.changedTicks(GlobalTransform)[row] = world.tick;
|
|
445
|
+
table.touch(GlobalTransform);
|
|
446
|
+
subtreeChildren(world, table, row, g, row * 12);
|
|
447
|
+
}
|
|
448
|
+
function subtreeColumns(table) {
|
|
449
|
+
const hasTransform = table.has(Transform);
|
|
450
|
+
const hasCell = table.has(GridCell);
|
|
451
|
+
return {
|
|
452
|
+
stamp: -1,
|
|
453
|
+
hasTransform,
|
|
454
|
+
hasGlobal: true,
|
|
455
|
+
translation: hasTransform ? table.column(Transform, "translation") : void 0,
|
|
456
|
+
rotation: hasTransform ? table.column(Transform, "rotation") : void 0,
|
|
457
|
+
scale: hasTransform ? table.column(Transform, "scale") : void 0,
|
|
458
|
+
changed: void 0,
|
|
459
|
+
global: table.column(GlobalTransform, "matrix"),
|
|
460
|
+
globalChanged: void 0,
|
|
461
|
+
children: void 0,
|
|
462
|
+
isGrid: false,
|
|
463
|
+
cell: hasCell ? table.column(GridCell, "cell") : void 0,
|
|
464
|
+
cellChanged: void 0
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function subtreeNode(world, table, row, parent, parentOffset) {
|
|
468
|
+
if (!table.has(GlobalTransform)) return;
|
|
469
|
+
const g = table.column(GlobalTransform, "matrix");
|
|
470
|
+
const o = row * 12;
|
|
471
|
+
if (table.has(Transform)) {
|
|
472
|
+
affine.fromTRSAt(
|
|
473
|
+
subtreeScratch,
|
|
474
|
+
0,
|
|
475
|
+
table.column(Transform, "translation"),
|
|
476
|
+
row * 3,
|
|
477
|
+
table.column(Transform, "rotation"),
|
|
478
|
+
row * 4,
|
|
479
|
+
table.column(Transform, "scale"),
|
|
480
|
+
row * 3
|
|
481
|
+
);
|
|
482
|
+
affine.multiplyAt(g, o, parent, parentOffset, subtreeScratch, 0);
|
|
483
|
+
} else {
|
|
484
|
+
affine.copyAt(g, o, parent, parentOffset);
|
|
485
|
+
}
|
|
486
|
+
table.changedTicks(GlobalTransform)[row] = world.tick;
|
|
487
|
+
table.touch(GlobalTransform);
|
|
488
|
+
subtreeChildren(world, table, row, g, o);
|
|
489
|
+
}
|
|
490
|
+
function subtreeChildren(world, table, row, matrix, offset) {
|
|
491
|
+
if (!table.has(Children)) return;
|
|
492
|
+
const list = table.column(Children, "entities")[row];
|
|
493
|
+
if (!list) return;
|
|
494
|
+
for (let k = 0; k < list.length; k++) {
|
|
495
|
+
const child = list[k];
|
|
496
|
+
if (child === null || child === void 0) continue;
|
|
497
|
+
subtreeNode(
|
|
498
|
+
world,
|
|
499
|
+
world.entityTableUnchecked(child),
|
|
500
|
+
world.entityRowUnchecked(child),
|
|
501
|
+
matrix,
|
|
502
|
+
offset
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function markTransformChanged({ entity, world }) {
|
|
507
|
+
if (!world.has(entity, Transform)) return;
|
|
508
|
+
world.entityTable(entity).markChanged(Transform, world.entityRow(entity));
|
|
509
|
+
}
|
|
510
|
+
const recenterGridCells = defineSystem({
|
|
511
|
+
name: "transform/recenter",
|
|
512
|
+
description: "Moves grid children to the neighboring cell when their translation leaves the cell.",
|
|
513
|
+
setup: (world) => ({ cells: world.query({ with: [GridCell, Transform, ChildOf] }) }),
|
|
514
|
+
run: ({ cells }, world, ctx) => {
|
|
515
|
+
const since = ctx.lastRunTick;
|
|
516
|
+
for (let t = 0; t < cells.tables.length; t++) {
|
|
517
|
+
const table = cells.tables[t];
|
|
518
|
+
if (table.count === 0 || table.lastChanged(Transform) <= since) continue;
|
|
519
|
+
const ticks = table.changedTicks(Transform);
|
|
520
|
+
const tr = table.column(Transform, "translation");
|
|
521
|
+
const cell = table.column(GridCell, "cell");
|
|
522
|
+
const parents = table.column(ChildOf, "parent");
|
|
523
|
+
for (let row = 0; row < table.count; row++) {
|
|
524
|
+
if (ticks[row] <= since) continue;
|
|
525
|
+
const parent = parents[row];
|
|
526
|
+
if (parent < 0 || !world.isAlive(parent)) continue;
|
|
527
|
+
const pt = world.entityTableUnchecked(parent);
|
|
528
|
+
if (!pt.has(Grid)) continue;
|
|
529
|
+
const pr = world.entityRowUnchecked(parent);
|
|
530
|
+
const cs = pt.column(Grid, "cellSize")[pr];
|
|
531
|
+
const limit = cs / 2 + pt.column(Grid, "hysteresis")[pr];
|
|
532
|
+
const o = row * 3;
|
|
533
|
+
let moved = false;
|
|
534
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
535
|
+
const v = tr[o + axis];
|
|
536
|
+
if (v <= limit && v >= -limit) continue;
|
|
537
|
+
const k = Math.round(v / cs);
|
|
538
|
+
cell[o + axis] = cell[o + axis] + k;
|
|
539
|
+
tr[o + axis] = v - k * cs;
|
|
540
|
+
moved = true;
|
|
541
|
+
}
|
|
542
|
+
if (moved) {
|
|
543
|
+
table.markChanged(Transform, row);
|
|
544
|
+
table.markChanged(GridCell, row);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
const TransformPlugin = definePlugin({
|
|
551
|
+
name: "core/transform",
|
|
552
|
+
provides: [
|
|
553
|
+
// transforms and large-world grids
|
|
554
|
+
GlobalTransform,
|
|
555
|
+
Transform,
|
|
556
|
+
FloatingOrigin,
|
|
557
|
+
Grid,
|
|
558
|
+
GridCell,
|
|
559
|
+
GridFramesResource,
|
|
560
|
+
OriginShift
|
|
561
|
+
],
|
|
562
|
+
build(app) {
|
|
563
|
+
app.world.initResource(GridFramesResource);
|
|
564
|
+
app.addSystems(
|
|
565
|
+
PostUpdate,
|
|
566
|
+
recenterGridCells.inSet(TransformSystems).before(propagateTransforms)
|
|
567
|
+
);
|
|
568
|
+
app.addSystems(PostUpdate, propagateTransforms.inSet(TransformSystems));
|
|
569
|
+
app.world.observe(onAdd(ChildOf), markTransformChanged);
|
|
570
|
+
app.world.observe(onSet(ChildOf), markTransformChanged);
|
|
571
|
+
app.world.observe(onRemove(ChildOf), markTransformChanged);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
export {
|
|
575
|
+
GlobalTransform,
|
|
576
|
+
Transform,
|
|
577
|
+
TransformPlugin,
|
|
578
|
+
TransformSystems,
|
|
579
|
+
lookAt,
|
|
580
|
+
propagateSubtree,
|
|
581
|
+
propagateTransforms,
|
|
582
|
+
recenterGridCells,
|
|
583
|
+
transform2d,
|
|
584
|
+
worldPosition
|
|
585
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aethervtt/shard-transform",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@aethervtt/shard-core": "0.0.1",
|
|
20
|
+
"@aethervtt/shard-runtime": "0.0.1"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/aethervtt/shard.git",
|
|
25
|
+
"directory": "packages/transform"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"provenance": true
|
|
30
|
+
},
|
|
31
|
+
"shard": {
|
|
32
|
+
"buildSha": "d6c5c6aef4675575c878b02cf2c8f738b4190229"
|
|
33
|
+
}
|
|
34
|
+
}
|