murow 0.0.73 → 0.1.3
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/README.md +15 -1
- package/dist/cjs/core/binary-codec/binary-codec.js +1 -1
- package/dist/cjs/core/clock/clock.js +1 -0
- package/dist/cjs/core/clock/index.js +1 -0
- package/dist/cjs/core/driver/driver.js +1 -1
- package/dist/cjs/core/driver/drivers/immediate.js +1 -1
- package/dist/cjs/core/driver/drivers/raf.js +1 -1
- package/dist/cjs/core/driver/drivers/timeout.js +1 -1
- package/dist/cjs/core/hitbox/hitbox-library.js +1 -0
- package/dist/cjs/core/hitbox/hitbox.js +1 -0
- package/dist/cjs/core/hitbox/index.js +1 -0
- package/dist/cjs/core/hitbox/test.js +1 -0
- package/dist/cjs/core/index.js +1 -1
- package/dist/cjs/core/input/index.js +1 -1
- package/dist/cjs/core/input/mouse-look/index.js +1 -0
- package/dist/cjs/core/input/mouse-look/mouse-look.js +1 -0
- package/dist/cjs/core/input/scroll-zoom/index.js +1 -0
- package/dist/cjs/core/input/scroll-zoom/scroll-zoom.js +1 -0
- package/dist/cjs/core/prediction/prediction.js +1 -1
- package/dist/cjs/core/ray/ray-3d.js +1 -1
- package/dist/cjs/core/raycast/hit-buffer.js +1 -0
- package/dist/cjs/core/raycast/index.js +1 -0
- package/dist/cjs/core/raycast/raycaster.js +1 -0
- package/dist/cjs/core/slot-map/index.js +1 -0
- package/dist/cjs/core/slot-map/slot-map.js +1 -0
- package/dist/cjs/core/state-machine/index.js +1 -0
- package/dist/cjs/core/state-machine/state-machine.js +1 -0
- package/dist/cjs/core/timeline/index.js +1 -0
- package/dist/cjs/core/timeline/timeline.js +1 -0
- package/dist/cjs/ecs/component.js +1 -1
- package/dist/cjs/ecs/system-builder.js +1 -1
- package/dist/cjs/ecs/world.js +1 -1
- package/dist/cjs/game/loop/loop.js +1 -1
- package/dist/cjs/game/loop/ticker-schedule.js +1 -0
- package/dist/cjs/net/adapters/bun-websocket.js +1 -1
- package/dist/cjs/renderer/index.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/index.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/specs.js +1 -1
- package/dist/cjs/renderer/raycast/index.js +1 -0
- package/dist/cjs/renderer/raycast/raycast.js +1 -0
- package/dist/esm/core/binary-codec/binary-codec.js +1 -1
- package/dist/esm/core/clock/clock.js +1 -0
- package/dist/esm/core/clock/index.js +1 -0
- package/dist/esm/core/driver/drivers/immediate.js +1 -1
- package/dist/esm/core/driver/drivers/raf.js +1 -1
- package/dist/esm/core/driver/drivers/timeout.js +1 -1
- package/dist/esm/core/hitbox/hitbox-library.js +1 -0
- package/dist/esm/core/hitbox/hitbox.js +1 -0
- package/dist/esm/core/hitbox/index.js +1 -0
- package/dist/esm/core/hitbox/test.js +1 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/core/input/index.js +1 -1
- package/dist/esm/core/input/mouse-look/index.js +1 -0
- package/dist/esm/core/input/mouse-look/mouse-look.js +1 -0
- package/dist/esm/core/input/scroll-zoom/index.js +1 -0
- package/dist/esm/core/input/scroll-zoom/scroll-zoom.js +1 -0
- package/dist/esm/core/prediction/prediction.js +1 -1
- package/dist/esm/core/ray/ray-3d.js +1 -1
- package/dist/esm/core/raycast/hit-buffer.js +1 -0
- package/dist/esm/core/raycast/index.js +1 -0
- package/dist/esm/core/raycast/raycaster.js +1 -0
- package/dist/esm/core/slot-map/index.js +1 -0
- package/dist/esm/core/slot-map/slot-map.js +1 -0
- package/dist/esm/core/state-machine/index.js +1 -0
- package/dist/esm/core/state-machine/state-machine.js +1 -0
- package/dist/esm/core/timeline/index.js +1 -0
- package/dist/esm/core/timeline/timeline.js +1 -0
- package/dist/esm/ecs/component.js +1 -1
- package/dist/esm/ecs/system-builder.js +1 -1
- package/dist/esm/ecs/world.js +1 -1
- package/dist/esm/game/loop/loop.js +1 -1
- package/dist/esm/game/loop/ticker-schedule.js +1 -0
- package/dist/esm/net/adapters/bun-websocket.js +1 -1
- package/dist/esm/renderer/index.js +1 -1
- package/dist/esm/renderer/prefab-bucket/concrete.js +1 -1
- package/dist/esm/renderer/prefab-bucket/index.js +1 -1
- package/dist/esm/renderer/prefab-bucket/parsers.js +1 -1
- package/dist/esm/renderer/raycast/index.js +1 -0
- package/dist/esm/renderer/raycast/raycast.js +1 -0
- package/dist/netcode/cjs/index.js +1556 -0
- package/dist/netcode/esm/index.js +1534 -0
- package/dist/netcode/types/client/game-client.d.ts +139 -0
- package/dist/netcode/types/client/index.d.ts +1 -0
- package/dist/netcode/types/client/strategies/snapshot-interpolation.d.ts +33 -0
- package/dist/netcode/types/client/strategies/snapshot-interpolation.test.d.ts +1 -0
- package/dist/netcode/types/codec/delta-codec.d.ts +17 -0
- package/dist/netcode/types/codec/delta-codec.test.d.ts +1 -0
- package/dist/netcode/types/codec/index.d.ts +1 -0
- package/dist/netcode/types/components/index.d.ts +1 -0
- package/dist/netcode/types/components/sync-spec.d.ts +49 -0
- package/dist/netcode/types/components/sync-spec.test.d.ts +1 -0
- package/dist/netcode/types/ctx.d.ts +105 -0
- package/dist/netcode/types/ctx.test.d.ts +1 -0
- package/dist/netcode/types/handlers/define-handlers.d.ts +47 -0
- package/dist/netcode/types/handlers/index.d.ts +1 -0
- package/dist/netcode/types/index.d.ts +11 -0
- package/dist/netcode/types/integration.test.d.ts +1 -0
- package/dist/netcode/types/intents/define-intents.d.ts +53 -0
- package/dist/netcode/types/intents/define-intents.test.d.ts +1 -0
- package/dist/netcode/types/intents/index.d.ts +1 -0
- package/dist/netcode/types/network/base.d.ts +120 -0
- package/dist/netcode/types/network/index.d.ts +2 -0
- package/dist/netcode/types/network/transport.d.ts +1 -0
- package/dist/netcode/types/packets/convergence.test.d.ts +1 -0
- package/dist/netcode/types/packets/harness.d.ts +103 -0
- package/dist/netcode/types/packets/index.d.ts +2 -0
- package/dist/netcode/types/packets/intermittent-intents.test.d.ts +1 -0
- package/dist/netcode/types/packets/pathological.test.d.ts +1 -0
- package/dist/netcode/types/packets/peer-interpolation.test.d.ts +1 -0
- package/dist/netcode/types/packets/reordering.test.d.ts +1 -0
- package/dist/netcode/types/packets/virtual-network.d.ts +65 -0
- package/dist/netcode/types/predictions/define-predictions.d.ts +45 -0
- package/dist/netcode/types/predictions/define-predictions.test.d.ts +1 -0
- package/dist/netcode/types/predictions/index.d.ts +1 -0
- package/dist/netcode/types/reconciliation.test.d.ts +1 -0
- package/dist/netcode/types/rpcs/define-rpcs.d.ts +44 -0
- package/dist/netcode/types/rpcs/define-rpcs.test.d.ts +1 -0
- package/dist/netcode/types/rpcs/index.d.ts +1 -0
- package/dist/netcode/types/server/game-server.d.ts +77 -0
- package/dist/netcode/types/server/index.d.ts +2 -0
- package/dist/netcode/types/server/plugins/aoi-grid.d.ts +34 -0
- package/dist/netcode/types/server/plugins/index.d.ts +3 -0
- package/dist/netcode/types/server/plugins/lag-compensation.d.ts +34 -0
- package/dist/netcode/types/server/plugins/plugin.d.ts +24 -0
- package/dist/netcode/types/tick-rate.test.d.ts +1 -0
- package/dist/netcode/types/transports/index.d.ts +1 -0
- package/dist/netcode/types/transports/memory-transport.d.ts +51 -0
- package/dist/netcode/types/types.test.d.ts +1 -0
- package/dist/types/core/binary-codec/binary-codec.d.ts +89 -31
- package/dist/types/core/clock/clock.d.ts +37 -0
- package/dist/types/core/clock/index.d.ts +1 -0
- package/dist/types/core/driver/driver.d.ts +8 -8
- package/dist/types/core/driver/drivers/immediate.d.ts +4 -4
- package/dist/types/core/driver/drivers/raf.d.ts +6 -6
- package/dist/types/core/driver/drivers/timeout.d.ts +4 -4
- package/dist/types/core/hitbox/hitbox-library.d.ts +29 -0
- package/dist/types/core/hitbox/hitbox.d.ts +50 -0
- package/dist/types/core/hitbox/index.d.ts +3 -0
- package/dist/types/core/hitbox/test.d.ts +44 -0
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/core/input/index.d.ts +2 -0
- package/dist/types/core/input/mouse-look/index.d.ts +1 -0
- package/dist/types/core/input/mouse-look/mouse-look.d.ts +139 -0
- package/dist/types/core/input/scroll-zoom/index.d.ts +1 -0
- package/dist/types/core/input/scroll-zoom/scroll-zoom.d.ts +38 -0
- package/dist/types/core/prediction/prediction.d.ts +35 -58
- package/dist/types/core/ray/ray-3d.d.ts +21 -1
- package/dist/types/core/raycast/hit-buffer.d.ts +43 -0
- package/dist/types/core/raycast/index.d.ts +2 -0
- package/dist/types/core/raycast/raycaster.d.ts +54 -0
- package/dist/types/core/slot-map/index.d.ts +1 -0
- package/dist/types/core/slot-map/slot-map.d.ts +109 -0
- package/dist/types/core/state-machine/index.d.ts +1 -0
- package/dist/types/core/state-machine/state-machine.d.ts +114 -0
- package/dist/types/core/timeline/index.d.ts +1 -0
- package/dist/types/core/timeline/timeline.d.ts +34 -0
- package/dist/types/ecs/component.d.ts +67 -11
- package/dist/types/ecs/entity-handle.d.ts +5 -5
- package/dist/types/ecs/system-builder.d.ts +13 -0
- package/dist/types/ecs/world.d.ts +72 -4
- package/dist/types/game/loop/loop.d.ts +51 -2
- package/dist/types/game/loop/ticker-schedule.d.ts +52 -0
- package/dist/types/net/adapters/bun-websocket.d.ts +19 -3
- package/dist/types/renderer/index.d.ts +1 -0
- package/dist/types/renderer/prefab-bucket/concrete.d.ts +16 -6
- package/dist/types/renderer/prefab-bucket/index.d.ts +11 -7
- package/dist/types/renderer/prefab-bucket/specs.d.ts +10 -0
- package/dist/types/renderer/raycast/index.d.ts +1 -0
- package/dist/types/renderer/raycast/raycast.d.ts +24 -0
- package/dist/types/renderer/types.d.ts +1 -0
- package/dist/webgpu/cjs/index.js +1897 -592
- package/dist/webgpu/esm/index.js +1889 -578
- package/dist/webgpu/types/2d/raycast.d.ts +45 -0
- package/dist/webgpu/types/2d/renderer.d.ts +11 -0
- package/dist/webgpu/types/2d/sprite-accessor.d.ts +3 -1
- package/dist/webgpu/types/3d/hitbox.d.ts +32 -0
- package/dist/webgpu/types/3d/lights.d.ts +113 -0
- package/dist/webgpu/types/3d/lights.test.d.ts +1 -0
- package/dist/webgpu/types/3d/raycast.d.ts +44 -0
- package/dist/webgpu/types/3d/renderer.d.ts +88 -1
- package/dist/webgpu/types/3d/shader.d.ts +88 -5
- package/dist/webgpu/types/core/types.d.ts +55 -0
- package/dist/webgpu/types/geometry/geometry-builder.d.ts +1 -4
- package/dist/webgpu/types/index.d.ts +1 -0
- package/dist/webgpu/types/shaders/utils.d.ts +24 -0
- package/package.json +6 -1
package/dist/webgpu/esm/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var std = { ..._std };
|
|
|
7
7
|
// src/2d/renderer.ts
|
|
8
8
|
import tgpu4 from "typegpu";
|
|
9
9
|
import * as d4 from "typegpu/data";
|
|
10
|
-
import { FreeList
|
|
10
|
+
import { FreeList } from "murow/core/free-list";
|
|
11
11
|
import { Base2DRenderer } from "murow/renderer";
|
|
12
12
|
|
|
13
13
|
// src/core/constants.ts
|
|
@@ -162,8 +162,46 @@ var MeshUniforms = d2.struct({
|
|
|
162
162
|
alpha: d2.f32,
|
|
163
163
|
lightDirX: d2.f32,
|
|
164
164
|
lightDirY: d2.f32,
|
|
165
|
-
lightDirZ: d2.f32
|
|
165
|
+
lightDirZ: d2.f32,
|
|
166
|
+
lightDirR: d2.f32,
|
|
167
|
+
lightDirG: d2.f32,
|
|
168
|
+
lightDirB: d2.f32,
|
|
169
|
+
lightDirIntensity: d2.f32,
|
|
170
|
+
ambientR: d2.f32,
|
|
171
|
+
ambientG: d2.f32,
|
|
172
|
+
ambientB: d2.f32,
|
|
173
|
+
lightCount: d2.u32
|
|
174
|
+
});
|
|
175
|
+
var MESH_UNIFORM_ALPHA_OFFSET = 16;
|
|
176
|
+
var MESH_UNIFORM_LIGHT_OFFSET = 17;
|
|
177
|
+
var MESH_UNIFORM_FLOATS = 28;
|
|
178
|
+
var Light = d2.struct({
|
|
179
|
+
kind: d2.f32,
|
|
180
|
+
currPosX: d2.f32,
|
|
181
|
+
currPosY: d2.f32,
|
|
182
|
+
currPosZ: d2.f32,
|
|
183
|
+
prevPosX: d2.f32,
|
|
184
|
+
prevPosY: d2.f32,
|
|
185
|
+
prevPosZ: d2.f32,
|
|
186
|
+
currDirX: d2.f32,
|
|
187
|
+
currDirY: d2.f32,
|
|
188
|
+
currDirZ: d2.f32,
|
|
189
|
+
prevDirX: d2.f32,
|
|
190
|
+
prevDirY: d2.f32,
|
|
191
|
+
prevDirZ: d2.f32,
|
|
192
|
+
colorR: d2.f32,
|
|
193
|
+
colorG: d2.f32,
|
|
194
|
+
colorB: d2.f32,
|
|
195
|
+
intensity: d2.f32,
|
|
196
|
+
range: d2.f32,
|
|
197
|
+
innerCos: d2.f32,
|
|
198
|
+
outerCos: d2.f32,
|
|
199
|
+
castsShadow: d2.f32,
|
|
200
|
+
shadowMapIndex: d2.f32
|
|
166
201
|
});
|
|
202
|
+
var LIGHT_FLOATS = 22;
|
|
203
|
+
var LIGHT_KIND_POINT = 1;
|
|
204
|
+
var LIGHT_KIND_SPOT = 2;
|
|
167
205
|
var InstanceAnimStateGPU = d2.struct({
|
|
168
206
|
clipId: d2.i32,
|
|
169
207
|
time: d2.f32,
|
|
@@ -186,15 +224,19 @@ import { SparseBatcher } from "murow/core/sparse-batcher";
|
|
|
186
224
|
|
|
187
225
|
// src/2d/sprite-accessor.ts
|
|
188
226
|
var SpriteAccessor = class {
|
|
189
|
-
constructor(dynamicData, staticData, slot, sheetId, onStaticDirty) {
|
|
227
|
+
constructor(dynamicData, staticData, id, slot, sheetId, onStaticDirty) {
|
|
190
228
|
this.dynamicData = dynamicData;
|
|
191
229
|
this.staticData = staticData;
|
|
230
|
+
this._id = id;
|
|
192
231
|
this._slot = slot;
|
|
193
232
|
this._sheetId = sheetId;
|
|
194
233
|
this.dynamicBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
|
|
195
234
|
this.staticBase = slot * STATIC_FLOATS_PER_SPRITE;
|
|
196
235
|
this._onStaticDirty = onStaticDirty;
|
|
197
236
|
}
|
|
237
|
+
get id() {
|
|
238
|
+
return this._id;
|
|
239
|
+
}
|
|
198
240
|
get slot() {
|
|
199
241
|
return this._slot;
|
|
200
242
|
}
|
|
@@ -318,6 +360,91 @@ var SpriteAccessor = class {
|
|
|
318
360
|
}
|
|
319
361
|
};
|
|
320
362
|
|
|
363
|
+
// src/2d/raycast.ts
|
|
364
|
+
import { HitBuffer } from "murow/core/raycast";
|
|
365
|
+
import {
|
|
366
|
+
Raycast,
|
|
367
|
+
RaycastMemo
|
|
368
|
+
} from "murow/renderer";
|
|
369
|
+
var WebGPURaycast2D = class extends Raycast {
|
|
370
|
+
constructor(renderer) {
|
|
371
|
+
super();
|
|
372
|
+
this.renderer = renderer;
|
|
373
|
+
this.state = new HitBuffer(2);
|
|
374
|
+
this.resultBuffer = [];
|
|
375
|
+
this.memos = /* @__PURE__ */ new Set();
|
|
376
|
+
}
|
|
377
|
+
update(input) {
|
|
378
|
+
this.state.reset();
|
|
379
|
+
this.renderer._collectRaycastHitsInto(input.mouse.position.x, input.mouse.position.y, this.state);
|
|
380
|
+
for (const m of this.memos)
|
|
381
|
+
m._invalidate();
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Topmost sprite under the cursor, or null. The returned object is
|
|
385
|
+
* pool-backed and valid only until the next `update()` -- copy what
|
|
386
|
+
* you need, or use `memo` for results that persist across frames.
|
|
387
|
+
*/
|
|
388
|
+
hit(opts) {
|
|
389
|
+
return this.state.nearest(opts?.filter, opts?.maxDistance ?? Infinity);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Every sprite under the cursor, topmost first. The array and its
|
|
393
|
+
* entries are reused and overwritten by the next `update()`.
|
|
394
|
+
*/
|
|
395
|
+
hitAll(opts) {
|
|
396
|
+
this.state.collectInto(this.resultBuffer, opts?.filter, opts?.maxDistance ?? Infinity);
|
|
397
|
+
return this.resultBuffer;
|
|
398
|
+
}
|
|
399
|
+
memo(opts) {
|
|
400
|
+
const m = new WebGPURaycastMemo2D(this.state, opts, () => this.memos.delete(m));
|
|
401
|
+
this.memos.add(m);
|
|
402
|
+
return m;
|
|
403
|
+
}
|
|
404
|
+
clearMemos() {
|
|
405
|
+
for (const m of this.memos)
|
|
406
|
+
m._detach();
|
|
407
|
+
this.memos.clear();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var WebGPURaycastMemo2D = class extends RaycastMemo {
|
|
411
|
+
constructor(state, opts, onDispose) {
|
|
412
|
+
super();
|
|
413
|
+
this.state = state;
|
|
414
|
+
this.opts = opts;
|
|
415
|
+
this.onDispose = onDispose;
|
|
416
|
+
this.dirty = true;
|
|
417
|
+
this.detached = false;
|
|
418
|
+
this.cached = [];
|
|
419
|
+
}
|
|
420
|
+
get hits() {
|
|
421
|
+
if (this.detached)
|
|
422
|
+
return this.cached;
|
|
423
|
+
if (this.dirty) {
|
|
424
|
+
this.state.collectInto(this.cached, this.opts.filter, this.opts.maxDistance ?? Infinity);
|
|
425
|
+
this.dirty = false;
|
|
426
|
+
}
|
|
427
|
+
return this.cached;
|
|
428
|
+
}
|
|
429
|
+
get first() {
|
|
430
|
+
const arr = this.hits;
|
|
431
|
+
return arr.length > 0 ? arr[0] : null;
|
|
432
|
+
}
|
|
433
|
+
dispose() {
|
|
434
|
+
if (this.detached)
|
|
435
|
+
return;
|
|
436
|
+
this.onDispose();
|
|
437
|
+
this._detach();
|
|
438
|
+
}
|
|
439
|
+
_invalidate() {
|
|
440
|
+
this.dirty = true;
|
|
441
|
+
}
|
|
442
|
+
_detach() {
|
|
443
|
+
this.detached = true;
|
|
444
|
+
this.cached.length = 0;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
321
448
|
// src/camera/camera-2d.ts
|
|
322
449
|
import { lerp } from "murow/core/lerp";
|
|
323
450
|
var Camera2D = class {
|
|
@@ -1084,7 +1211,7 @@ function resolveBuiltInGeometry(name) {
|
|
|
1084
1211
|
}
|
|
1085
1212
|
|
|
1086
1213
|
// src/geometry/geometry-builder.ts
|
|
1087
|
-
import {
|
|
1214
|
+
import { SlotMap } from "murow/core/slot-map";
|
|
1088
1215
|
|
|
1089
1216
|
// src/shaders/runtime-transpile.ts
|
|
1090
1217
|
import * as acorn from "acorn";
|
|
@@ -1262,7 +1389,6 @@ var CustomGeometry = class {
|
|
|
1262
1389
|
this._uniformDirty = true;
|
|
1263
1390
|
this._isComputeSourced = false;
|
|
1264
1391
|
this._drawCount = 0;
|
|
1265
|
-
this.activeCount = 0;
|
|
1266
1392
|
this.name = name;
|
|
1267
1393
|
this.root = root;
|
|
1268
1394
|
this.maxInstances = maxInstances;
|
|
@@ -1276,7 +1402,7 @@ var CustomGeometry = class {
|
|
|
1276
1402
|
this.staticFloatsPerInstance = 0;
|
|
1277
1403
|
for (const n of this.staticFieldNames)
|
|
1278
1404
|
this.staticFloatsPerInstance += getFieldFloats(layoutConfig.static[n]);
|
|
1279
|
-
this.
|
|
1405
|
+
this.slots = new SlotMap(maxInstances);
|
|
1280
1406
|
this.dynamicData = new Float32Array(maxInstances * this.dynamicFloatsPerInstance);
|
|
1281
1407
|
this.staticData = new Float32Array(maxInstances * this.staticFloatsPerInstance);
|
|
1282
1408
|
this.uniformValues = { ...uniformValues };
|
|
@@ -1287,8 +1413,6 @@ var CustomGeometry = class {
|
|
|
1287
1413
|
this.dataBindGroup = dataBindGroup;
|
|
1288
1414
|
this.canvas = canvas;
|
|
1289
1415
|
this.clearColor = clearColor;
|
|
1290
|
-
this.activeSlots = new Uint32Array(maxInstances);
|
|
1291
|
-
this.slotToActive = new Int32Array(maxInstances).fill(-1);
|
|
1292
1416
|
this._ctx = new InstanceContext();
|
|
1293
1417
|
this._isComputeSourced = isComputeSourced;
|
|
1294
1418
|
if (isComputeSourced)
|
|
@@ -1302,34 +1426,20 @@ var CustomGeometry = class {
|
|
|
1302
1426
|
this._drawCount = count;
|
|
1303
1427
|
}
|
|
1304
1428
|
addInstance(data) {
|
|
1305
|
-
const slot = this.
|
|
1429
|
+
const slot = this.slots.add();
|
|
1306
1430
|
if (slot === -1)
|
|
1307
1431
|
throw new Error(`Max instances (${this.maxInstances}) reached for "${this.name}"`);
|
|
1308
1432
|
this.setInstanceData(slot, data);
|
|
1309
|
-
this.activeSlots[this.activeCount] = slot;
|
|
1310
|
-
this.slotToActive[slot] = this.activeCount;
|
|
1311
|
-
this.activeCount++;
|
|
1312
1433
|
return slot;
|
|
1313
1434
|
}
|
|
1314
1435
|
removeInstance(slot) {
|
|
1315
|
-
this.freeList.free(slot);
|
|
1316
1436
|
const dynBase = slot * this.dynamicFloatsPerInstance;
|
|
1317
1437
|
const statBase = slot * this.staticFloatsPerInstance;
|
|
1318
1438
|
this.dynamicData.fill(0, dynBase, dynBase + this.dynamicFloatsPerInstance);
|
|
1319
1439
|
this.staticData.fill(0, statBase, statBase + this.staticFloatsPerInstance);
|
|
1320
1440
|
this._dynamicDirty = true;
|
|
1321
1441
|
this._staticDirty = true;
|
|
1322
|
-
|
|
1323
|
-
if (activeIdx !== -1) {
|
|
1324
|
-
const lastIdx = this.activeCount - 1;
|
|
1325
|
-
if (activeIdx !== lastIdx) {
|
|
1326
|
-
const lastSlot = this.activeSlots[lastIdx];
|
|
1327
|
-
this.activeSlots[activeIdx] = lastSlot;
|
|
1328
|
-
this.slotToActive[lastSlot] = activeIdx;
|
|
1329
|
-
}
|
|
1330
|
-
this.slotToActive[slot] = -1;
|
|
1331
|
-
this.activeCount--;
|
|
1332
|
-
}
|
|
1442
|
+
this.slots.remove(slot);
|
|
1333
1443
|
}
|
|
1334
1444
|
setInstanceData(slot, data) {
|
|
1335
1445
|
const rawData = data;
|
|
@@ -1402,7 +1512,7 @@ var CustomGeometry = class {
|
|
|
1402
1512
|
this._uniformDirty = true;
|
|
1403
1513
|
}
|
|
1404
1514
|
getActiveCount() {
|
|
1405
|
-
return this.
|
|
1515
|
+
return this.slots.size;
|
|
1406
1516
|
}
|
|
1407
1517
|
updateAll(callback) {
|
|
1408
1518
|
this._ctx._bind(
|
|
@@ -1414,8 +1524,8 @@ var CustomGeometry = class {
|
|
|
1414
1524
|
this.staticFieldNames,
|
|
1415
1525
|
this.layoutConfig
|
|
1416
1526
|
);
|
|
1417
|
-
const count = this.
|
|
1418
|
-
const slots = this.activeSlots;
|
|
1527
|
+
const count = this.slots.size;
|
|
1528
|
+
const slots = this.slots.activeSlots;
|
|
1419
1529
|
for (let i = 0; i < count; i++) {
|
|
1420
1530
|
const slot = slots[i];
|
|
1421
1531
|
this._ctx._setSlot(slot);
|
|
@@ -1426,7 +1536,7 @@ var CustomGeometry = class {
|
|
|
1426
1536
|
}
|
|
1427
1537
|
render() {
|
|
1428
1538
|
const device = this.root.device;
|
|
1429
|
-
const count = this._isComputeSourced ? this._drawCount : this.
|
|
1539
|
+
const count = this._isComputeSourced ? this._drawCount : this.slots.size;
|
|
1430
1540
|
if (count === 0)
|
|
1431
1541
|
return;
|
|
1432
1542
|
const context = this.canvas.getContext("webgpu");
|
|
@@ -2051,6 +2161,7 @@ var ComputeBuilder = class {
|
|
|
2051
2161
|
};
|
|
2052
2162
|
|
|
2053
2163
|
// src/2d/renderer.ts
|
|
2164
|
+
import { testHitbox2D, pointInQuad2D } from "murow/core/hitbox";
|
|
2054
2165
|
var prefab2DHandles = /* @__PURE__ */ new WeakMap();
|
|
2055
2166
|
function isPrefab2D(value) {
|
|
2056
2167
|
return value.type === "spritesheet";
|
|
@@ -2074,15 +2185,19 @@ var WebGPU2DRenderer = class extends Base2DRenderer {
|
|
|
2074
2185
|
this.sheets = /* @__PURE__ */ new Map();
|
|
2075
2186
|
this.nextSheetId = 0;
|
|
2076
2187
|
this.uniformData = new Float32Array(20);
|
|
2188
|
+
this.nextSpriteId = 0;
|
|
2077
2189
|
this.resizeObserver = null;
|
|
2078
2190
|
this.resizeCallbacks = [];
|
|
2079
2191
|
this._prefabs = options.prefabs ?? null;
|
|
2080
2192
|
this.camera = new Camera2D(canvas.width || 800, canvas.height || 600);
|
|
2081
|
-
this.
|
|
2193
|
+
this.raycast = new WebGPURaycast2D(this);
|
|
2194
|
+
this.freeList = new FreeList(resolvedMaxSprites);
|
|
2082
2195
|
this.batcher = new SparseBatcher(resolvedMaxSprites);
|
|
2083
2196
|
this.dynamicData = new Float32Array(resolvedMaxSprites * DYNAMIC_FLOATS_PER_SPRITE);
|
|
2084
2197
|
this.staticData = new Float32Array(resolvedMaxSprites * STATIC_FLOATS_PER_SPRITE);
|
|
2085
2198
|
this.slotIndexData = new Uint32Array(resolvedMaxSprites);
|
|
2199
|
+
this.spriteHandles = new Array(resolvedMaxSprites).fill(null);
|
|
2200
|
+
this.spriteHitboxes = new Array(resolvedMaxSprites).fill(null);
|
|
2086
2201
|
}
|
|
2087
2202
|
get device() {
|
|
2088
2203
|
return this._device;
|
|
@@ -2242,6 +2357,9 @@ var WebGPU2DRenderer = class extends Base2DRenderer {
|
|
|
2242
2357
|
const dynBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
|
|
2243
2358
|
const statBase = slot * STATIC_FLOATS_PER_SPRITE;
|
|
2244
2359
|
const sheet = isPrefab2D(opts.sheet) ? resolveSpritePrefabHandle(opts.sheet) : opts.sheet;
|
|
2360
|
+
const hitboxName = isPrefab2D(opts.sheet) ? opts.sheet.hitbox : void 0;
|
|
2361
|
+
const lib = this._prefabs?.hitboxLibrary ?? null;
|
|
2362
|
+
const hitbox = hitboxName && lib ? lib.get(hitboxName) : null;
|
|
2245
2363
|
const [px, py] = opts.position ?? [0, 0];
|
|
2246
2364
|
this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_X] = px;
|
|
2247
2365
|
this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_Y] = py;
|
|
@@ -2270,15 +2388,19 @@ var WebGPU2DRenderer = class extends Base2DRenderer {
|
|
|
2270
2388
|
this.staticData[statBase + STATIC_OFFSET_TINT_A] = tint[3];
|
|
2271
2389
|
this.staticDirty = true;
|
|
2272
2390
|
this.batcher.add(opts.layer ?? 0, sheet.id, slot);
|
|
2273
|
-
|
|
2391
|
+
const accessor = new SpriteAccessor(
|
|
2274
2392
|
this.dynamicData,
|
|
2275
2393
|
this.staticData,
|
|
2394
|
+
this.nextSpriteId++,
|
|
2276
2395
|
slot,
|
|
2277
2396
|
sheet.id,
|
|
2278
2397
|
() => {
|
|
2279
2398
|
this.staticDirty = true;
|
|
2280
2399
|
}
|
|
2281
2400
|
);
|
|
2401
|
+
this.spriteHandles[slot] = accessor;
|
|
2402
|
+
this.spriteHitboxes[slot] = hitbox;
|
|
2403
|
+
return accessor;
|
|
2282
2404
|
}
|
|
2283
2405
|
removeSprite(sprite) {
|
|
2284
2406
|
const accessor = sprite;
|
|
@@ -2288,8 +2410,47 @@ var WebGPU2DRenderer = class extends Base2DRenderer {
|
|
|
2288
2410
|
const statBase = accessor.slot * STATIC_FLOATS_PER_SPRITE;
|
|
2289
2411
|
this.dynamicData.fill(0, dynBase, dynBase + DYNAMIC_FLOATS_PER_SPRITE);
|
|
2290
2412
|
this.staticData.fill(0, statBase, statBase + STATIC_FLOATS_PER_SPRITE);
|
|
2413
|
+
this.spriteHandles[accessor.slot] = null;
|
|
2414
|
+
this.spriteHitboxes[accessor.slot] = void 0;
|
|
2291
2415
|
this.staticDirty = true;
|
|
2292
2416
|
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Point-test every sprite against the unprojected cursor and push the
|
|
2419
|
+
* hits into the buffer. Sort key is `-layer` so the topmost sprite is
|
|
2420
|
+
* "nearest". A declared hitbox overrides the default rendered quad.
|
|
2421
|
+
*/
|
|
2422
|
+
_collectRaycastHitsInto(screenX, screenY, rc) {
|
|
2423
|
+
const [wx, wy] = this.camera.screenToWorld(screenX, screenY);
|
|
2424
|
+
const dyn = this.dynamicData;
|
|
2425
|
+
const stat = this.staticData;
|
|
2426
|
+
this.batcher.each((_sheetId, instances, count) => {
|
|
2427
|
+
for (let i = 0; i < count; i++) {
|
|
2428
|
+
const slot = instances[i];
|
|
2429
|
+
const handle = this.spriteHandles[slot];
|
|
2430
|
+
if (handle === null)
|
|
2431
|
+
continue;
|
|
2432
|
+
const dynBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
|
|
2433
|
+
const statBase = slot * STATIC_FLOATS_PER_SPRITE;
|
|
2434
|
+
const cx = dyn[dynBase + DYNAMIC_OFFSET_CURR_X];
|
|
2435
|
+
const cy = dyn[dynBase + DYNAMIC_OFFSET_CURR_Y];
|
|
2436
|
+
const rot = dyn[dynBase + DYNAMIC_OFFSET_CURR_ROTATION];
|
|
2437
|
+
const sx = stat[statBase + STATIC_OFFSET_SCALE_X];
|
|
2438
|
+
const sy = stat[statBase + STATIC_OFFSET_SCALE_Y];
|
|
2439
|
+
const layer = stat[statBase + STATIC_OFFSET_LAYER];
|
|
2440
|
+
const hb = this.spriteHitboxes[slot];
|
|
2441
|
+
let part = null;
|
|
2442
|
+
if (hb) {
|
|
2443
|
+
const hit = testHitbox2D(hb, cx, cy, sx, sy, rot, wx, wy);
|
|
2444
|
+
if (!hit)
|
|
2445
|
+
continue;
|
|
2446
|
+
part = hit.part;
|
|
2447
|
+
} else if (!pointInQuad2D(cx, cy, sx, sy, rot, wx, wy)) {
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
rc.push(handle, -layer, wx, wy, 0, layer, part);
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2293
2454
|
storePreviousState() {
|
|
2294
2455
|
this.camera.storePrevious();
|
|
2295
2456
|
const dyn = this.dynamicData;
|
|
@@ -2500,311 +2661,127 @@ var AnimationController = class {
|
|
|
2500
2661
|
};
|
|
2501
2662
|
|
|
2502
2663
|
// src/3d/renderer.ts
|
|
2503
|
-
import
|
|
2664
|
+
import tgpu7 from "typegpu";
|
|
2504
2665
|
import { Base3DRenderer } from "murow/renderer";
|
|
2505
|
-
import { FreeList as
|
|
2666
|
+
import { FreeList as FreeList2 } from "murow/core/free-list";
|
|
2506
2667
|
import { SparseBatcher as SparseBatcher2 } from "murow/core/sparse-batcher";
|
|
2507
2668
|
|
|
2508
|
-
// src/
|
|
2509
|
-
import
|
|
2510
|
-
import
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
this._renderTarget = [0, 0, 0];
|
|
2527
|
-
this._viewMatrix = new Float32Array(16);
|
|
2528
|
-
this._projMatrix = new Float32Array(16);
|
|
2529
|
-
this._vpMatrix = new Float32Array(16);
|
|
2530
|
-
this._ray = new Ray3D();
|
|
2531
|
-
this._width = 1;
|
|
2532
|
-
this._height = 1;
|
|
2533
|
-
}
|
|
2534
|
-
/**
|
|
2535
|
-
* Store current position/target as previous. Call before each tick.
|
|
2536
|
-
*/
|
|
2537
|
-
storePrevious() {
|
|
2538
|
-
this._prevPosition[0] = this.position[0];
|
|
2539
|
-
this._prevPosition[1] = this.position[1];
|
|
2540
|
-
this._prevPosition[2] = this.position[2];
|
|
2541
|
-
this._prevTarget[0] = this.target[0];
|
|
2542
|
-
this._prevTarget[1] = this.target[1];
|
|
2543
|
-
this._prevTarget[2] = this.target[2];
|
|
2544
|
-
}
|
|
2545
|
-
/**
|
|
2546
|
-
* Smoothly move the camera toward a target point.
|
|
2547
|
-
* Call each tick. The camera and its look-at target lerp toward the given position.
|
|
2548
|
-
* @param targetX World X to follow
|
|
2549
|
-
* @param targetY World Y to follow
|
|
2550
|
-
* @param targetZ World Z to follow
|
|
2551
|
-
* @param smoothing 0-1. 1 = snap instantly, 0.1 = lazy follow. Default 1.
|
|
2552
|
-
*/
|
|
2553
|
-
follow(targetX, targetY, targetZ, smoothing = 1) {
|
|
2554
|
-
const dx = targetX - this.target[0];
|
|
2555
|
-
const dy = targetY - this.target[1];
|
|
2556
|
-
const dz = targetZ - this.target[2];
|
|
2557
|
-
const mx = dx * smoothing;
|
|
2558
|
-
const my = dy * smoothing;
|
|
2559
|
-
const mz = dz * smoothing;
|
|
2560
|
-
this.target[0] += mx;
|
|
2561
|
-
this.target[1] += my;
|
|
2562
|
-
this.target[2] += mz;
|
|
2563
|
-
this.position[0] += mx;
|
|
2564
|
-
this.position[1] += my;
|
|
2565
|
-
this.position[2] += mz;
|
|
2566
|
-
}
|
|
2567
|
-
/**
|
|
2568
|
-
* Interpolate between previous and current state. Call before rendering.
|
|
2569
|
-
*/
|
|
2570
|
-
interpolate(alpha) {
|
|
2571
|
-
this._renderPosition[0] = lerp2(this._prevPosition[0], this.position[0], alpha);
|
|
2572
|
-
this._renderPosition[1] = lerp2(this._prevPosition[1], this.position[1], alpha);
|
|
2573
|
-
this._renderPosition[2] = lerp2(this._prevPosition[2], this.position[2], alpha);
|
|
2574
|
-
this._renderTarget[0] = lerp2(this._prevTarget[0], this.target[0], alpha);
|
|
2575
|
-
this._renderTarget[1] = lerp2(this._prevTarget[1], this.target[1], alpha);
|
|
2576
|
-
this._renderTarget[2] = lerp2(this._prevTarget[2], this.target[2], alpha);
|
|
2577
|
-
}
|
|
2578
|
-
/**
|
|
2579
|
-
* Build the view matrix (lookAt) using interpolated state.
|
|
2580
|
-
*/
|
|
2581
|
-
getViewMatrix() {
|
|
2582
|
-
lookAt(this._viewMatrix, this._renderPosition, this._renderTarget, this.up);
|
|
2583
|
-
return this._viewMatrix;
|
|
2584
|
-
}
|
|
2585
|
-
/**
|
|
2586
|
-
* Build the perspective projection matrix.
|
|
2587
|
-
*/
|
|
2588
|
-
getProjectionMatrix() {
|
|
2589
|
-
perspective(this._projMatrix, this.fov * (Math.PI / 180), this.aspect, this.near, this.far);
|
|
2590
|
-
return this._projMatrix;
|
|
2669
|
+
// src/3d/shader.ts
|
|
2670
|
+
import tgpu6 from "typegpu";
|
|
2671
|
+
import * as d6 from "typegpu/data";
|
|
2672
|
+
import * as std4 from "typegpu/std";
|
|
2673
|
+
|
|
2674
|
+
// src/shaders/utils.ts
|
|
2675
|
+
import tgpu5 from "typegpu";
|
|
2676
|
+
import * as d5 from "typegpu/data";
|
|
2677
|
+
import * as std3 from "typegpu/std";
|
|
2678
|
+
var rotate2d = tgpu5.fn([d5.vec2f, d5.f32], d5.vec2f)(
|
|
2679
|
+
function rotate2d2(point, angle) {
|
|
2680
|
+
"use gpu";
|
|
2681
|
+
const c = std3.cos(angle);
|
|
2682
|
+
const s = std3.sin(angle);
|
|
2683
|
+
return d5.vec2f(
|
|
2684
|
+
point.x * c - point.y * s,
|
|
2685
|
+
point.x * s + point.y * c
|
|
2686
|
+
);
|
|
2591
2687
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
mat4Multiply(this._vpMatrix, this._projMatrix, this._viewMatrix);
|
|
2599
|
-
return this._vpMatrix;
|
|
2688
|
+
);
|
|
2689
|
+
var worldToClip2d = tgpu5.fn([d5.vec2f, d5.mat3x3f], d5.vec4f)(
|
|
2690
|
+
function worldToClip2d2(worldPos, cameraMatrix) {
|
|
2691
|
+
"use gpu";
|
|
2692
|
+
const clip = cameraMatrix * d5.vec3f(worldPos.x, worldPos.y, 1);
|
|
2693
|
+
return d5.vec4f(clip.x, clip.y, 0, 1);
|
|
2600
2694
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2695
|
+
);
|
|
2696
|
+
var worldToClip3d = tgpu5.fn([d5.vec3f, d5.mat4x4f], d5.vec4f)(
|
|
2697
|
+
function worldToClip3d2(worldPos, vpMatrix) {
|
|
2698
|
+
"use gpu";
|
|
2699
|
+
return vpMatrix * d5.vec4f(worldPos.x, worldPos.y, worldPos.z, 1);
|
|
2605
2700
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2701
|
+
);
|
|
2702
|
+
var remap = tgpu5.fn([d5.f32, d5.f32, d5.f32, d5.f32, d5.f32], d5.f32)(
|
|
2703
|
+
function remap2(value, inMin, inMax, outMin, outMax) {
|
|
2704
|
+
"use gpu";
|
|
2705
|
+
const t = (value - inMin) / (inMax - inMin);
|
|
2706
|
+
return outMin + t * (outMax - outMin);
|
|
2610
2707
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
const upX = m[1], upY = m[5], upZ = m[9];
|
|
2624
|
-
const fwdX = -m[2], fwdY = -m[6], fwdZ = -m[10];
|
|
2625
|
-
const dx = fwdX + rightX * ndcX * t * this.aspect + upX * ndcY * t;
|
|
2626
|
-
const dy = fwdY + rightY * ndcX * t * this.aspect + upY * ndcY * t;
|
|
2627
|
-
const dz = fwdZ + rightZ * ndcX * t * this.aspect + upZ * ndcY * t;
|
|
2628
|
-
this._ray.set(this.position[0], this.position[1], this.position[2], dx, dy, dz);
|
|
2629
|
-
return this._ray;
|
|
2708
|
+
);
|
|
2709
|
+
var scaleRotate2d = tgpu5.fn([d5.vec2f, d5.f32], d5.mat2x2f)(
|
|
2710
|
+
function scaleRotate2d2(scale, angle) {
|
|
2711
|
+
"use gpu";
|
|
2712
|
+
const c = std3.cos(angle);
|
|
2713
|
+
const s = std3.sin(angle);
|
|
2714
|
+
return d5.mat2x2f(
|
|
2715
|
+
scale.x * c,
|
|
2716
|
+
scale.x * s,
|
|
2717
|
+
-(scale.y * s),
|
|
2718
|
+
scale.y * c
|
|
2719
|
+
);
|
|
2630
2720
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2721
|
+
);
|
|
2722
|
+
var inverseLerp = tgpu5.fn([d5.f32, d5.f32, d5.f32], d5.f32)(
|
|
2723
|
+
function inverseLerp2(min, max3, value) {
|
|
2724
|
+
"use gpu";
|
|
2725
|
+
return std3.saturate((value - min) / (max3 - min));
|
|
2635
2726
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2727
|
+
);
|
|
2728
|
+
var lightContribution = tgpu5.fn(
|
|
2729
|
+
[d5.vec3f, d5.vec3f, d5.vec3f, d5.vec4f, d5.vec3f, d5.vec3f],
|
|
2730
|
+
d5.vec3f
|
|
2731
|
+
)(
|
|
2732
|
+
function lightContribution2(pos, axis, color, params, normal, worldPos) {
|
|
2733
|
+
"use gpu";
|
|
2734
|
+
const intensity = params.x;
|
|
2735
|
+
const range = params.y;
|
|
2736
|
+
const innerCos = params.z;
|
|
2737
|
+
const outerCos = params.w;
|
|
2738
|
+
const toLight = d5.vec3f(pos.x - worldPos.x, pos.y - worldPos.y, pos.z - worldPos.z);
|
|
2739
|
+
const dist = std3.length(toLight);
|
|
2740
|
+
const dir = std3.normalize(toLight);
|
|
2741
|
+
const lambert = std3.max(std3.dot(normal, dir), 0);
|
|
2742
|
+
const atten = std3.saturate(1 - dist / std3.max(range, 1e-4));
|
|
2743
|
+
const falloff = atten * atten;
|
|
2744
|
+
const axisLen = std3.length(axis);
|
|
2745
|
+
const isSpot = axisLen > 1e-4;
|
|
2746
|
+
const safeAxis = std3.select(d5.vec3f(0, 0, 1), axis, isSpot);
|
|
2747
|
+
const cosAngle = std3.dot(std3.normalize(safeAxis), d5.vec3f(-dir.x, -dir.y, -dir.z));
|
|
2748
|
+
const spotCone = std3.smoothstep(outerCos, innerCos, cosAngle);
|
|
2749
|
+
const cone = std3.select(1, spotCone, isSpot);
|
|
2750
|
+
const shadowFactor = 1;
|
|
2751
|
+
const scale = lambert * falloff * cone * intensity * shadowFactor;
|
|
2752
|
+
return d5.vec3f(color.x * scale, color.y * scale, color.z * scale);
|
|
2649
2753
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
const dz = m[8] * right + m[9] * up - m[10] * forward;
|
|
2656
|
-
this.position[0] += dx;
|
|
2657
|
-
this.position[1] += dy;
|
|
2658
|
-
this.position[2] += dz;
|
|
2659
|
-
this.target[0] += dx;
|
|
2660
|
-
this.target[1] += dy;
|
|
2661
|
-
this.target[2] += dz;
|
|
2754
|
+
);
|
|
2755
|
+
var tonemap = tgpu5.fn([d5.vec3f], d5.vec3f)(
|
|
2756
|
+
function tonemap2(c) {
|
|
2757
|
+
"use gpu";
|
|
2758
|
+
return d5.vec3f(c.x / (1 + c.x), c.y / (1 + c.y), c.z / (1 + c.z));
|
|
2662
2759
|
}
|
|
2663
|
-
|
|
2664
|
-
const yaw = Math.atan2(this.target[0] - this.position[0], this.target[2] - this.position[2]);
|
|
2665
|
-
const dx = Math.sin(yaw) * forward + Math.cos(yaw) * right;
|
|
2666
|
-
const dz = Math.cos(yaw) * forward - Math.sin(yaw) * right;
|
|
2667
|
-
this.position[0] += dx;
|
|
2668
|
-
this.position[1] += up;
|
|
2669
|
-
this.position[2] += dz;
|
|
2670
|
-
this.target[0] += dx;
|
|
2671
|
-
this.target[1] += up;
|
|
2672
|
-
this.target[2] += dz;
|
|
2673
|
-
}
|
|
2674
|
-
_moveGlobal(right, up, forward) {
|
|
2675
|
-
this.position[0] += right;
|
|
2676
|
-
this.position[1] += up;
|
|
2677
|
-
this.position[2] += forward;
|
|
2678
|
-
this.target[0] += right;
|
|
2679
|
-
this.target[1] += up;
|
|
2680
|
-
this.target[2] += forward;
|
|
2681
|
-
}
|
|
2682
|
-
/**
|
|
2683
|
-
* Orbit around the target point. Zero allocations.
|
|
2684
|
-
* @param yawDelta Horizontal rotation in radians (positive = rotate right)
|
|
2685
|
-
* @param pitchDelta Vertical rotation in radians (positive = rotate up)
|
|
2686
|
-
*/
|
|
2687
|
-
orbit(yawDelta, pitchDelta) {
|
|
2688
|
-
let ox = this.position[0] - this.target[0];
|
|
2689
|
-
let oy = this.position[1] - this.target[1];
|
|
2690
|
-
let oz = this.position[2] - this.target[2];
|
|
2691
|
-
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
2692
|
-
let yaw = Math.atan2(ox, oz);
|
|
2693
|
-
let pitch = Math.asin(oy / dist);
|
|
2694
|
-
yaw += yawDelta;
|
|
2695
|
-
pitch += pitchDelta;
|
|
2696
|
-
pitch = Math.max(-Math.PI * 0.49, Math.min(Math.PI * 0.49, pitch));
|
|
2697
|
-
this.position[0] = this.target[0] + Math.sin(yaw) * Math.cos(pitch) * dist;
|
|
2698
|
-
this.position[1] = this.target[1] + Math.sin(pitch) * dist;
|
|
2699
|
-
this.position[2] = this.target[2] + Math.cos(yaw) * Math.cos(pitch) * dist;
|
|
2700
|
-
}
|
|
2701
|
-
/**
|
|
2702
|
-
* Zoom by adjusting distance to target. Zero allocations.
|
|
2703
|
-
* @param delta Positive = zoom in, negative = zoom out
|
|
2704
|
-
*/
|
|
2705
|
-
zoom(delta) {
|
|
2706
|
-
let ox = this.position[0] - this.target[0];
|
|
2707
|
-
let oy = this.position[1] - this.target[1];
|
|
2708
|
-
let oz = this.position[2] - this.target[2];
|
|
2709
|
-
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
2710
|
-
const newDist = Math.max(0.1, dist - delta);
|
|
2711
|
-
const scale = newDist / dist;
|
|
2712
|
-
this.position[0] = this.target[0] + ox * scale;
|
|
2713
|
-
this.position[1] = this.target[1] + oy * scale;
|
|
2714
|
-
this.position[2] = this.target[2] + oz * scale;
|
|
2715
|
-
}
|
|
2716
|
-
};
|
|
2717
|
-
function lookAt(out, eye, center, up) {
|
|
2718
|
-
let fx = center[0] - eye[0];
|
|
2719
|
-
let fy = center[1] - eye[1];
|
|
2720
|
-
let fz = center[2] - eye[2];
|
|
2721
|
-
let len = 1 / Math.sqrt(fx * fx + fy * fy + fz * fz);
|
|
2722
|
-
fx *= len;
|
|
2723
|
-
fy *= len;
|
|
2724
|
-
fz *= len;
|
|
2725
|
-
let sx = fy * up[2] - fz * up[1];
|
|
2726
|
-
let sy = fz * up[0] - fx * up[2];
|
|
2727
|
-
let sz = fx * up[1] - fy * up[0];
|
|
2728
|
-
len = Math.sqrt(sx * sx + sy * sy + sz * sz);
|
|
2729
|
-
if (len > 0) {
|
|
2730
|
-
len = 1 / len;
|
|
2731
|
-
sx *= len;
|
|
2732
|
-
sy *= len;
|
|
2733
|
-
sz *= len;
|
|
2734
|
-
}
|
|
2735
|
-
const ux = sy * fz - sz * fy;
|
|
2736
|
-
const uy = sz * fx - sx * fz;
|
|
2737
|
-
const uz = sx * fy - sy * fx;
|
|
2738
|
-
out[0] = sx;
|
|
2739
|
-
out[1] = ux;
|
|
2740
|
-
out[2] = -fx;
|
|
2741
|
-
out[3] = 0;
|
|
2742
|
-
out[4] = sy;
|
|
2743
|
-
out[5] = uy;
|
|
2744
|
-
out[6] = -fy;
|
|
2745
|
-
out[7] = 0;
|
|
2746
|
-
out[8] = sz;
|
|
2747
|
-
out[9] = uz;
|
|
2748
|
-
out[10] = -fz;
|
|
2749
|
-
out[11] = 0;
|
|
2750
|
-
out[12] = -(sx * eye[0] + sy * eye[1] + sz * eye[2]);
|
|
2751
|
-
out[13] = -(ux * eye[0] + uy * eye[1] + uz * eye[2]);
|
|
2752
|
-
out[14] = fx * eye[0] + fy * eye[1] + fz * eye[2];
|
|
2753
|
-
out[15] = 1;
|
|
2754
|
-
}
|
|
2755
|
-
function perspective(out, fovRad, aspect, near, far) {
|
|
2756
|
-
const f = 1 / Math.tan(fovRad * 0.5);
|
|
2757
|
-
const rangeInv = 1 / (near - far);
|
|
2758
|
-
out[0] = f / aspect;
|
|
2759
|
-
out[1] = 0;
|
|
2760
|
-
out[2] = 0;
|
|
2761
|
-
out[3] = 0;
|
|
2762
|
-
out[4] = 0;
|
|
2763
|
-
out[5] = f;
|
|
2764
|
-
out[6] = 0;
|
|
2765
|
-
out[7] = 0;
|
|
2766
|
-
out[8] = 0;
|
|
2767
|
-
out[9] = 0;
|
|
2768
|
-
out[10] = (near + far) * rangeInv;
|
|
2769
|
-
out[11] = -1;
|
|
2770
|
-
out[12] = 0;
|
|
2771
|
-
out[13] = 0;
|
|
2772
|
-
out[14] = 2 * near * far * rangeInv;
|
|
2773
|
-
out[15] = 0;
|
|
2774
|
-
}
|
|
2775
|
-
function mat4Multiply(out, a, b) {
|
|
2776
|
-
for (let i = 0; i < 4; i++) {
|
|
2777
|
-
const ai0 = a[i], ai1 = a[i + 4], ai2 = a[i + 8], ai3 = a[i + 12];
|
|
2778
|
-
out[i] = ai0 * b[0] + ai1 * b[1] + ai2 * b[2] + ai3 * b[3];
|
|
2779
|
-
out[i + 4] = ai0 * b[4] + ai1 * b[5] + ai2 * b[6] + ai3 * b[7];
|
|
2780
|
-
out[i + 8] = ai0 * b[8] + ai1 * b[9] + ai2 * b[10] + ai3 * b[11];
|
|
2781
|
-
out[i + 12] = ai0 * b[12] + ai1 * b[13] + ai2 * b[14] + ai3 * b[15];
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2760
|
+
);
|
|
2784
2761
|
|
|
2785
2762
|
// src/3d/shader.ts
|
|
2786
|
-
|
|
2787
|
-
import * as d5 from "typegpu/data";
|
|
2788
|
-
import * as std3 from "typegpu/std";
|
|
2763
|
+
var MAX_LIGHTS = 64;
|
|
2789
2764
|
function createMeshLayout(maxInstances) {
|
|
2790
|
-
return
|
|
2765
|
+
return tgpu6.bindGroupLayout({
|
|
2791
2766
|
uniforms: { uniform: MeshUniforms },
|
|
2792
|
-
dynamicInstances: { storage:
|
|
2793
|
-
staticInstances: { storage:
|
|
2794
|
-
slotIndices: { storage:
|
|
2767
|
+
dynamicInstances: { storage: d6.arrayOf(DynamicMesh, maxInstances) },
|
|
2768
|
+
staticInstances: { storage: d6.arrayOf(StaticMesh, maxInstances) },
|
|
2769
|
+
slotIndices: { storage: d6.arrayOf(d6.u32, maxInstances) },
|
|
2770
|
+
lights: { storage: d6.arrayOf(Light, MAX_LIGHTS) }
|
|
2795
2771
|
});
|
|
2796
2772
|
}
|
|
2797
2773
|
function createMeshVertex(meshLayout) {
|
|
2798
|
-
return
|
|
2774
|
+
return tgpu6.vertexFn({
|
|
2799
2775
|
in: {
|
|
2800
|
-
position:
|
|
2801
|
-
normal:
|
|
2802
|
-
instanceIndex:
|
|
2776
|
+
position: d6.location(0, d6.vec3f),
|
|
2777
|
+
normal: d6.location(1, d6.vec3f),
|
|
2778
|
+
instanceIndex: d6.builtin.instanceIndex
|
|
2803
2779
|
},
|
|
2804
2780
|
out: {
|
|
2805
|
-
pos:
|
|
2806
|
-
vNormal:
|
|
2807
|
-
vColor:
|
|
2781
|
+
pos: d6.builtin.position,
|
|
2782
|
+
vNormal: d6.vec3f,
|
|
2783
|
+
vColor: d6.vec3f,
|
|
2784
|
+
vWorldPos: d6.vec3f
|
|
2808
2785
|
}
|
|
2809
2786
|
})(function(input) {
|
|
2810
2787
|
const instanceIndex = input.instanceIndex;
|
|
@@ -2812,115 +2789,144 @@ function createMeshVertex(meshLayout) {
|
|
|
2812
2789
|
const dyn = meshLayout.$.dynamicInstances[slot];
|
|
2813
2790
|
const stat = meshLayout.$.staticInstances[slot];
|
|
2814
2791
|
const alpha = meshLayout.$.uniforms.alpha;
|
|
2815
|
-
const px =
|
|
2816
|
-
const py =
|
|
2817
|
-
const pz =
|
|
2818
|
-
const rx =
|
|
2819
|
-
const ry =
|
|
2820
|
-
const rz =
|
|
2792
|
+
const px = std4.mix(dyn.prevPosX, dyn.currPosX, alpha);
|
|
2793
|
+
const py = std4.mix(dyn.prevPosY, dyn.currPosY, alpha);
|
|
2794
|
+
const pz = std4.mix(dyn.prevPosZ, dyn.currPosZ, alpha);
|
|
2795
|
+
const rx = std4.mix(dyn.prevRotX, dyn.currRotX, alpha);
|
|
2796
|
+
const ry = std4.mix(dyn.prevRotY, dyn.currRotY, alpha);
|
|
2797
|
+
const rz = std4.mix(dyn.prevRotZ, dyn.currRotZ, alpha);
|
|
2821
2798
|
const sx = stat.scaleX;
|
|
2822
2799
|
const sy = stat.scaleY;
|
|
2823
2800
|
const sz = stat.scaleZ;
|
|
2824
|
-
const scaled =
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2801
|
+
const scaled = d6.vec3f(
|
|
2802
|
+
std4.mul(input.position.x, sx),
|
|
2803
|
+
std4.mul(input.position.y, sy),
|
|
2804
|
+
std4.mul(input.position.z, sz)
|
|
2828
2805
|
);
|
|
2829
|
-
const czr =
|
|
2830
|
-
const szr =
|
|
2831
|
-
const rz1 =
|
|
2832
|
-
|
|
2833
|
-
|
|
2806
|
+
const czr = std4.cos(rz);
|
|
2807
|
+
const szr = std4.sin(rz);
|
|
2808
|
+
const rz1 = d6.vec3f(
|
|
2809
|
+
std4.sub(std4.mul(scaled.x, czr), std4.mul(scaled.y, szr)),
|
|
2810
|
+
std4.add(std4.mul(scaled.x, szr), std4.mul(scaled.y, czr)),
|
|
2834
2811
|
scaled.z
|
|
2835
2812
|
);
|
|
2836
|
-
const cyr =
|
|
2837
|
-
const syr =
|
|
2838
|
-
const ry1 =
|
|
2839
|
-
|
|
2813
|
+
const cyr = std4.cos(ry);
|
|
2814
|
+
const syr = std4.sin(ry);
|
|
2815
|
+
const ry1 = d6.vec3f(
|
|
2816
|
+
std4.add(std4.mul(rz1.x, cyr), std4.mul(rz1.z, syr)),
|
|
2840
2817
|
rz1.y,
|
|
2841
|
-
|
|
2818
|
+
std4.sub(std4.mul(rz1.z, cyr), std4.mul(rz1.x, syr))
|
|
2842
2819
|
);
|
|
2843
|
-
const cxr =
|
|
2844
|
-
const sxr =
|
|
2845
|
-
const rx1 =
|
|
2820
|
+
const cxr = std4.cos(rx);
|
|
2821
|
+
const sxr = std4.sin(rx);
|
|
2822
|
+
const rx1 = d6.vec3f(
|
|
2846
2823
|
ry1.x,
|
|
2847
|
-
|
|
2848
|
-
|
|
2824
|
+
std4.sub(std4.mul(ry1.y, cxr), std4.mul(ry1.z, sxr)),
|
|
2825
|
+
std4.add(std4.mul(ry1.y, sxr), std4.mul(ry1.z, cxr))
|
|
2849
2826
|
);
|
|
2850
|
-
const worldPos =
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2827
|
+
const worldPos = d6.vec4f(
|
|
2828
|
+
std4.add(rx1.x, px),
|
|
2829
|
+
std4.add(rx1.y, py),
|
|
2830
|
+
std4.add(rx1.z, pz),
|
|
2854
2831
|
1
|
|
2855
2832
|
);
|
|
2856
2833
|
const nScaled = input.normal;
|
|
2857
|
-
const nRz =
|
|
2858
|
-
|
|
2859
|
-
|
|
2834
|
+
const nRz = d6.vec3f(
|
|
2835
|
+
std4.sub(std4.mul(nScaled.x, czr), std4.mul(nScaled.y, szr)),
|
|
2836
|
+
std4.add(std4.mul(nScaled.x, szr), std4.mul(nScaled.y, czr)),
|
|
2860
2837
|
nScaled.z
|
|
2861
2838
|
);
|
|
2862
|
-
const nRy =
|
|
2863
|
-
|
|
2839
|
+
const nRy = d6.vec3f(
|
|
2840
|
+
std4.add(std4.mul(nRz.x, cyr), std4.mul(nRz.z, syr)),
|
|
2864
2841
|
nRz.y,
|
|
2865
|
-
|
|
2842
|
+
std4.sub(std4.mul(nRz.z, cyr), std4.mul(nRz.x, syr))
|
|
2866
2843
|
);
|
|
2867
|
-
const nRx =
|
|
2844
|
+
const nRx = d6.vec3f(
|
|
2868
2845
|
nRy.x,
|
|
2869
|
-
|
|
2870
|
-
|
|
2846
|
+
std4.sub(std4.mul(nRy.y, cxr), std4.mul(nRy.z, sxr)),
|
|
2847
|
+
std4.add(std4.mul(nRy.y, sxr), std4.mul(nRy.z, cxr))
|
|
2871
2848
|
);
|
|
2872
|
-
const clipPos =
|
|
2849
|
+
const clipPos = std4.mul(meshLayout.$.uniforms.viewProjection, worldPos);
|
|
2873
2850
|
return {
|
|
2874
2851
|
pos: clipPos,
|
|
2875
2852
|
vNormal: nRx,
|
|
2876
|
-
vColor:
|
|
2853
|
+
vColor: d6.vec3f(stat.colorR, stat.colorG, stat.colorB),
|
|
2854
|
+
vWorldPos: d6.vec3f(worldPos.x, worldPos.y, worldPos.z)
|
|
2877
2855
|
};
|
|
2878
2856
|
});
|
|
2879
2857
|
}
|
|
2880
2858
|
function createMeshFragment(meshLayout) {
|
|
2881
|
-
return
|
|
2859
|
+
return tgpu6.fragmentFn({
|
|
2882
2860
|
in: {
|
|
2883
|
-
vNormal:
|
|
2884
|
-
vColor:
|
|
2861
|
+
vNormal: d6.vec3f,
|
|
2862
|
+
vColor: d6.vec3f,
|
|
2863
|
+
vWorldPos: d6.vec3f
|
|
2885
2864
|
},
|
|
2886
|
-
out:
|
|
2865
|
+
out: d6.vec4f
|
|
2887
2866
|
})(function(input) {
|
|
2888
|
-
const
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
));
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
std3.add(ambient.x, diffuse.x),
|
|
2899
|
-
std3.add(ambient.y, diffuse.y),
|
|
2900
|
-
std3.add(ambient.z, diffuse.z),
|
|
2901
|
-
1
|
|
2867
|
+
const u = meshLayout.$.uniforms;
|
|
2868
|
+
const baseColor = input.vColor;
|
|
2869
|
+
const worldPos = input.vWorldPos;
|
|
2870
|
+
const normal = std4.normalize(input.vNormal);
|
|
2871
|
+
const lightDir = std4.normalize(d6.vec3f(u.lightDirX, u.lightDirY, u.lightDirZ));
|
|
2872
|
+
const diff = std4.max(std4.dot(normal, lightDir), 0) * u.lightDirIntensity;
|
|
2873
|
+
let acc = d6.vec3f(
|
|
2874
|
+
baseColor.x * (u.ambientR + u.lightDirR * diff),
|
|
2875
|
+
baseColor.y * (u.ambientG + u.lightDirG * diff),
|
|
2876
|
+
baseColor.z * (u.ambientB + u.lightDirB * diff)
|
|
2902
2877
|
);
|
|
2878
|
+
const count = u.lightCount;
|
|
2879
|
+
const a = u.alpha;
|
|
2880
|
+
for (let i = d6.u32(0); i < count; i++) {
|
|
2881
|
+
const L = meshLayout.$.lights[i];
|
|
2882
|
+
const pos = d6.vec3f(
|
|
2883
|
+
std4.mix(L.prevPosX, L.currPosX, a),
|
|
2884
|
+
std4.mix(L.prevPosY, L.currPosY, a),
|
|
2885
|
+
std4.mix(L.prevPosZ, L.currPosZ, a)
|
|
2886
|
+
);
|
|
2887
|
+
const axis = d6.vec3f(
|
|
2888
|
+
std4.mix(L.prevDirX, L.currDirX, a),
|
|
2889
|
+
std4.mix(L.prevDirY, L.currDirY, a),
|
|
2890
|
+
std4.mix(L.prevDirZ, L.currDirZ, a)
|
|
2891
|
+
);
|
|
2892
|
+
const c = lightContribution(
|
|
2893
|
+
pos,
|
|
2894
|
+
axis,
|
|
2895
|
+
d6.vec3f(L.colorR, L.colorG, L.colorB),
|
|
2896
|
+
d6.vec4f(L.intensity, L.range, L.innerCos, L.outerCos),
|
|
2897
|
+
normal,
|
|
2898
|
+
worldPos
|
|
2899
|
+
);
|
|
2900
|
+
acc = d6.vec3f(
|
|
2901
|
+
acc.x + baseColor.x * c.x,
|
|
2902
|
+
acc.y + baseColor.y * c.y,
|
|
2903
|
+
acc.z + baseColor.z * c.z
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
const mapped = tonemap(acc);
|
|
2907
|
+
return d6.vec4f(mapped.x, mapped.y, mapped.z, 1);
|
|
2903
2908
|
});
|
|
2904
2909
|
}
|
|
2905
2910
|
function createTextureBindGroupLayout() {
|
|
2906
|
-
return
|
|
2911
|
+
return tgpu6.bindGroupLayout({
|
|
2907
2912
|
modelTexture: { texture: "float" },
|
|
2908
2913
|
modelSampler: { sampler: "filtering" }
|
|
2909
2914
|
});
|
|
2910
2915
|
}
|
|
2911
2916
|
function createTexturedMeshVertex(meshLayout) {
|
|
2912
|
-
return
|
|
2917
|
+
return tgpu6.vertexFn({
|
|
2913
2918
|
in: {
|
|
2914
|
-
position:
|
|
2915
|
-
normal:
|
|
2916
|
-
uv:
|
|
2917
|
-
instanceIndex:
|
|
2919
|
+
position: d6.location(0, d6.vec3f),
|
|
2920
|
+
normal: d6.location(1, d6.vec3f),
|
|
2921
|
+
uv: d6.location(2, d6.vec2f),
|
|
2922
|
+
instanceIndex: d6.builtin.instanceIndex
|
|
2918
2923
|
},
|
|
2919
2924
|
out: {
|
|
2920
|
-
pos:
|
|
2921
|
-
vNormal:
|
|
2922
|
-
vColor:
|
|
2923
|
-
vUV:
|
|
2925
|
+
pos: d6.builtin.position,
|
|
2926
|
+
vNormal: d6.vec3f,
|
|
2927
|
+
vColor: d6.vec3f,
|
|
2928
|
+
vUV: d6.vec2f,
|
|
2929
|
+
vWorldPos: d6.vec3f
|
|
2924
2930
|
}
|
|
2925
2931
|
})(function(input) {
|
|
2926
2932
|
const instanceIndex = input.instanceIndex;
|
|
@@ -2928,128 +2934,157 @@ function createTexturedMeshVertex(meshLayout) {
|
|
|
2928
2934
|
const dyn = meshLayout.$.dynamicInstances[slot];
|
|
2929
2935
|
const stat = meshLayout.$.staticInstances[slot];
|
|
2930
2936
|
const alpha = meshLayout.$.uniforms.alpha;
|
|
2931
|
-
const px =
|
|
2932
|
-
const py =
|
|
2933
|
-
const pz =
|
|
2934
|
-
const rx =
|
|
2935
|
-
const ry =
|
|
2936
|
-
const rz =
|
|
2937
|
+
const px = std4.mix(dyn.prevPosX, dyn.currPosX, alpha);
|
|
2938
|
+
const py = std4.mix(dyn.prevPosY, dyn.currPosY, alpha);
|
|
2939
|
+
const pz = std4.mix(dyn.prevPosZ, dyn.currPosZ, alpha);
|
|
2940
|
+
const rx = std4.mix(dyn.prevRotX, dyn.currRotX, alpha);
|
|
2941
|
+
const ry = std4.mix(dyn.prevRotY, dyn.currRotY, alpha);
|
|
2942
|
+
const rz = std4.mix(dyn.prevRotZ, dyn.currRotZ, alpha);
|
|
2937
2943
|
const sx = stat.scaleX;
|
|
2938
2944
|
const sy = stat.scaleY;
|
|
2939
2945
|
const sz = stat.scaleZ;
|
|
2940
|
-
const scaled =
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2946
|
+
const scaled = d6.vec3f(
|
|
2947
|
+
std4.mul(input.position.x, sx),
|
|
2948
|
+
std4.mul(input.position.y, sy),
|
|
2949
|
+
std4.mul(input.position.z, sz)
|
|
2944
2950
|
);
|
|
2945
|
-
const czr =
|
|
2946
|
-
const szr =
|
|
2947
|
-
const rz1 =
|
|
2948
|
-
|
|
2949
|
-
|
|
2951
|
+
const czr = std4.cos(rz);
|
|
2952
|
+
const szr = std4.sin(rz);
|
|
2953
|
+
const rz1 = d6.vec3f(
|
|
2954
|
+
std4.sub(std4.mul(scaled.x, czr), std4.mul(scaled.y, szr)),
|
|
2955
|
+
std4.add(std4.mul(scaled.x, szr), std4.mul(scaled.y, czr)),
|
|
2950
2956
|
scaled.z
|
|
2951
2957
|
);
|
|
2952
|
-
const cyr =
|
|
2953
|
-
const syr =
|
|
2954
|
-
const ry1 =
|
|
2955
|
-
|
|
2958
|
+
const cyr = std4.cos(ry);
|
|
2959
|
+
const syr = std4.sin(ry);
|
|
2960
|
+
const ry1 = d6.vec3f(
|
|
2961
|
+
std4.add(std4.mul(rz1.x, cyr), std4.mul(rz1.z, syr)),
|
|
2956
2962
|
rz1.y,
|
|
2957
|
-
|
|
2963
|
+
std4.sub(std4.mul(rz1.z, cyr), std4.mul(rz1.x, syr))
|
|
2958
2964
|
);
|
|
2959
|
-
const cxr =
|
|
2960
|
-
const sxr =
|
|
2961
|
-
const rx1 =
|
|
2965
|
+
const cxr = std4.cos(rx);
|
|
2966
|
+
const sxr = std4.sin(rx);
|
|
2967
|
+
const rx1 = d6.vec3f(
|
|
2962
2968
|
ry1.x,
|
|
2963
|
-
|
|
2964
|
-
|
|
2969
|
+
std4.sub(std4.mul(ry1.y, cxr), std4.mul(ry1.z, sxr)),
|
|
2970
|
+
std4.add(std4.mul(ry1.y, sxr), std4.mul(ry1.z, cxr))
|
|
2965
2971
|
);
|
|
2966
|
-
const worldPos =
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2972
|
+
const worldPos = d6.vec4f(
|
|
2973
|
+
std4.add(rx1.x, px),
|
|
2974
|
+
std4.add(rx1.y, py),
|
|
2975
|
+
std4.add(rx1.z, pz),
|
|
2970
2976
|
1
|
|
2971
2977
|
);
|
|
2972
2978
|
const nScaled = input.normal;
|
|
2973
|
-
const nRz =
|
|
2974
|
-
|
|
2975
|
-
|
|
2979
|
+
const nRz = d6.vec3f(
|
|
2980
|
+
std4.sub(std4.mul(nScaled.x, czr), std4.mul(nScaled.y, szr)),
|
|
2981
|
+
std4.add(std4.mul(nScaled.x, szr), std4.mul(nScaled.y, czr)),
|
|
2976
2982
|
nScaled.z
|
|
2977
2983
|
);
|
|
2978
|
-
const nRy =
|
|
2979
|
-
|
|
2984
|
+
const nRy = d6.vec3f(
|
|
2985
|
+
std4.add(std4.mul(nRz.x, cyr), std4.mul(nRz.z, syr)),
|
|
2980
2986
|
nRz.y,
|
|
2981
|
-
|
|
2987
|
+
std4.sub(std4.mul(nRz.z, cyr), std4.mul(nRz.x, syr))
|
|
2982
2988
|
);
|
|
2983
|
-
const nRx =
|
|
2989
|
+
const nRx = d6.vec3f(
|
|
2984
2990
|
nRy.x,
|
|
2985
|
-
|
|
2986
|
-
|
|
2991
|
+
std4.sub(std4.mul(nRy.y, cxr), std4.mul(nRy.z, sxr)),
|
|
2992
|
+
std4.add(std4.mul(nRy.y, sxr), std4.mul(nRy.z, cxr))
|
|
2987
2993
|
);
|
|
2988
|
-
const clipPos =
|
|
2994
|
+
const clipPos = std4.mul(meshLayout.$.uniforms.viewProjection, worldPos);
|
|
2989
2995
|
return {
|
|
2990
2996
|
pos: clipPos,
|
|
2991
2997
|
vNormal: nRx,
|
|
2992
|
-
vColor:
|
|
2993
|
-
vUV: input.uv
|
|
2998
|
+
vColor: d6.vec3f(stat.colorR, stat.colorG, stat.colorB),
|
|
2999
|
+
vUV: input.uv,
|
|
3000
|
+
vWorldPos: d6.vec3f(worldPos.x, worldPos.y, worldPos.z)
|
|
2994
3001
|
};
|
|
2995
3002
|
});
|
|
2996
3003
|
}
|
|
2997
3004
|
function createTexturedMeshFragment(meshLayout, texLayout) {
|
|
2998
|
-
return
|
|
3005
|
+
return tgpu6.fragmentFn({
|
|
2999
3006
|
in: {
|
|
3000
|
-
vNormal:
|
|
3001
|
-
vColor:
|
|
3002
|
-
vUV:
|
|
3007
|
+
vNormal: d6.vec3f,
|
|
3008
|
+
vColor: d6.vec3f,
|
|
3009
|
+
vUV: d6.vec2f,
|
|
3010
|
+
vWorldPos: d6.vec3f
|
|
3003
3011
|
},
|
|
3004
|
-
out:
|
|
3012
|
+
out: d6.vec4f
|
|
3005
3013
|
})(function(input) {
|
|
3006
|
-
const
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
std3.mul(texColor.x, input.vColor.x),
|
|
3015
|
-
std3.mul(texColor.y, input.vColor.y),
|
|
3016
|
-
std3.mul(texColor.z, input.vColor.z)
|
|
3014
|
+
const u = meshLayout.$.uniforms;
|
|
3015
|
+
const worldPos = input.vWorldPos;
|
|
3016
|
+
const normal = std4.normalize(input.vNormal);
|
|
3017
|
+
const texColor = std4.textureSample(texLayout.$.modelTexture, texLayout.$.modelSampler, input.vUV);
|
|
3018
|
+
const baseColor = d6.vec3f(
|
|
3019
|
+
std4.mul(texColor.x, input.vColor.x),
|
|
3020
|
+
std4.mul(texColor.y, input.vColor.y),
|
|
3021
|
+
std4.mul(texColor.z, input.vColor.z)
|
|
3017
3022
|
);
|
|
3018
|
-
const
|
|
3019
|
-
const
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
std3.add(ambient.z, diffuse.z),
|
|
3025
|
-
std3.mul(texColor.w, 1)
|
|
3023
|
+
const lightDir = std4.normalize(d6.vec3f(u.lightDirX, u.lightDirY, u.lightDirZ));
|
|
3024
|
+
const diff = std4.max(std4.dot(normal, lightDir), 0) * u.lightDirIntensity;
|
|
3025
|
+
let acc = d6.vec3f(
|
|
3026
|
+
baseColor.x * (u.ambientR + u.lightDirR * diff),
|
|
3027
|
+
baseColor.y * (u.ambientG + u.lightDirG * diff),
|
|
3028
|
+
baseColor.z * (u.ambientB + u.lightDirB * diff)
|
|
3026
3029
|
);
|
|
3030
|
+
const count = u.lightCount;
|
|
3031
|
+
const a = u.alpha;
|
|
3032
|
+
for (let i = d6.u32(0); i < count; i++) {
|
|
3033
|
+
const L = meshLayout.$.lights[i];
|
|
3034
|
+
const pos = d6.vec3f(
|
|
3035
|
+
std4.mix(L.prevPosX, L.currPosX, a),
|
|
3036
|
+
std4.mix(L.prevPosY, L.currPosY, a),
|
|
3037
|
+
std4.mix(L.prevPosZ, L.currPosZ, a)
|
|
3038
|
+
);
|
|
3039
|
+
const axis = d6.vec3f(
|
|
3040
|
+
std4.mix(L.prevDirX, L.currDirX, a),
|
|
3041
|
+
std4.mix(L.prevDirY, L.currDirY, a),
|
|
3042
|
+
std4.mix(L.prevDirZ, L.currDirZ, a)
|
|
3043
|
+
);
|
|
3044
|
+
const c = lightContribution(
|
|
3045
|
+
pos,
|
|
3046
|
+
axis,
|
|
3047
|
+
d6.vec3f(L.colorR, L.colorG, L.colorB),
|
|
3048
|
+
d6.vec4f(L.intensity, L.range, L.innerCos, L.outerCos),
|
|
3049
|
+
normal,
|
|
3050
|
+
worldPos
|
|
3051
|
+
);
|
|
3052
|
+
acc = d6.vec3f(
|
|
3053
|
+
acc.x + baseColor.x * c.x,
|
|
3054
|
+
acc.y + baseColor.y * c.y,
|
|
3055
|
+
acc.z + baseColor.z * c.z
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
const mapped = tonemap(acc);
|
|
3059
|
+
return d6.vec4f(mapped.x, mapped.y, mapped.z, texColor.w);
|
|
3027
3060
|
});
|
|
3028
3061
|
}
|
|
3029
3062
|
function createSkinnedMeshLayout(maxInstances, maxBones) {
|
|
3030
|
-
return
|
|
3063
|
+
return tgpu6.bindGroupLayout({
|
|
3031
3064
|
uniforms: { uniform: MeshUniforms },
|
|
3032
|
-
dynamicInstances: { storage:
|
|
3033
|
-
staticInstances: { storage:
|
|
3034
|
-
slotIndices: { storage:
|
|
3035
|
-
boneMatrices: { storage:
|
|
3065
|
+
dynamicInstances: { storage: d6.arrayOf(DynamicMesh, maxInstances) },
|
|
3066
|
+
staticInstances: { storage: d6.arrayOf(SkinnedStaticMesh, maxInstances) },
|
|
3067
|
+
slotIndices: { storage: d6.arrayOf(d6.u32, maxInstances) },
|
|
3068
|
+
boneMatrices: { storage: d6.arrayOf(d6.mat4x4f, maxBones) },
|
|
3069
|
+
lights: { storage: d6.arrayOf(Light, MAX_LIGHTS) }
|
|
3036
3070
|
});
|
|
3037
3071
|
}
|
|
3038
3072
|
function createSkinnedMeshVertex(layout) {
|
|
3039
|
-
return
|
|
3073
|
+
return tgpu6.vertexFn({
|
|
3040
3074
|
in: {
|
|
3041
|
-
position:
|
|
3042
|
-
normal:
|
|
3043
|
-
uv:
|
|
3044
|
-
joints:
|
|
3045
|
-
weights:
|
|
3046
|
-
instanceIndex:
|
|
3075
|
+
position: d6.location(0, d6.vec3f),
|
|
3076
|
+
normal: d6.location(1, d6.vec3f),
|
|
3077
|
+
uv: d6.location(2, d6.vec2f),
|
|
3078
|
+
joints: d6.location(3, d6.vec4u),
|
|
3079
|
+
weights: d6.location(4, d6.vec4f),
|
|
3080
|
+
instanceIndex: d6.builtin.instanceIndex
|
|
3047
3081
|
},
|
|
3048
3082
|
out: {
|
|
3049
|
-
pos:
|
|
3050
|
-
vNormal:
|
|
3051
|
-
vColor:
|
|
3052
|
-
vUV:
|
|
3083
|
+
pos: d6.builtin.position,
|
|
3084
|
+
vNormal: d6.vec3f,
|
|
3085
|
+
vColor: d6.vec3f,
|
|
3086
|
+
vUV: d6.vec2f,
|
|
3087
|
+
vWorldPos: d6.vec3f
|
|
3053
3088
|
}
|
|
3054
3089
|
})(function(input) {
|
|
3055
3090
|
const slot = layout.$.slotIndices[input.instanceIndex];
|
|
@@ -3069,69 +3104,69 @@ function createSkinnedMeshVertex(layout) {
|
|
|
3069
3104
|
const m1 = layout.$.boneMatrices[boneOffset + j1];
|
|
3070
3105
|
const m2 = layout.$.boneMatrices[boneOffset + j2];
|
|
3071
3106
|
const m3 = layout.$.boneMatrices[boneOffset + j3];
|
|
3072
|
-
const p =
|
|
3107
|
+
const p = d6.vec4f(input.position.x, input.position.y, input.position.z, 1);
|
|
3073
3108
|
const sp0 = m0 * p;
|
|
3074
3109
|
const sp1 = m1 * p;
|
|
3075
3110
|
const sp2 = m2 * p;
|
|
3076
3111
|
const sp3 = m3 * p;
|
|
3077
|
-
const skinnedPos =
|
|
3112
|
+
const skinnedPos = d6.vec3f(
|
|
3078
3113
|
sp0.x * w0 + sp1.x * w1 + sp2.x * w2 + sp3.x * w3,
|
|
3079
3114
|
sp0.y * w0 + sp1.y * w1 + sp2.y * w2 + sp3.y * w3,
|
|
3080
3115
|
sp0.z * w0 + sp1.z * w1 + sp2.z * w2 + sp3.z * w3
|
|
3081
3116
|
);
|
|
3082
|
-
const n =
|
|
3117
|
+
const n = d6.vec4f(input.normal.x, input.normal.y, input.normal.z, 0);
|
|
3083
3118
|
const sn0 = m0 * n;
|
|
3084
3119
|
const sn1 = m1 * n;
|
|
3085
3120
|
const sn2 = m2 * n;
|
|
3086
3121
|
const sn3 = m3 * n;
|
|
3087
|
-
const skinnedNormal =
|
|
3122
|
+
const skinnedNormal = d6.vec3f(
|
|
3088
3123
|
sn0.x * w0 + sn1.x * w1 + sn2.x * w2 + sn3.x * w3,
|
|
3089
3124
|
sn0.y * w0 + sn1.y * w1 + sn2.y * w2 + sn3.y * w3,
|
|
3090
3125
|
sn0.z * w0 + sn1.z * w1 + sn2.z * w2 + sn3.z * w3
|
|
3091
3126
|
);
|
|
3092
|
-
const px =
|
|
3093
|
-
const py =
|
|
3094
|
-
const pz =
|
|
3095
|
-
const rx =
|
|
3096
|
-
const ry =
|
|
3097
|
-
const rz =
|
|
3127
|
+
const px = std4.mix(dyn.prevPosX, dyn.currPosX, alpha);
|
|
3128
|
+
const py = std4.mix(dyn.prevPosY, dyn.currPosY, alpha);
|
|
3129
|
+
const pz = std4.mix(dyn.prevPosZ, dyn.currPosZ, alpha);
|
|
3130
|
+
const rx = std4.mix(dyn.prevRotX, dyn.currRotX, alpha);
|
|
3131
|
+
const ry = std4.mix(dyn.prevRotY, dyn.currRotY, alpha);
|
|
3132
|
+
const rz = std4.mix(dyn.prevRotZ, dyn.currRotZ, alpha);
|
|
3098
3133
|
const sx = stat.scaleX;
|
|
3099
3134
|
const sy = stat.scaleY;
|
|
3100
3135
|
const sz = stat.scaleZ;
|
|
3101
|
-
const scaled =
|
|
3102
|
-
const czr =
|
|
3103
|
-
const szr =
|
|
3104
|
-
const rz1 =
|
|
3136
|
+
const scaled = d6.vec3f(skinnedPos.x * sx, skinnedPos.y * sy, skinnedPos.z * sz);
|
|
3137
|
+
const czr = std4.cos(rz);
|
|
3138
|
+
const szr = std4.sin(rz);
|
|
3139
|
+
const rz1 = d6.vec3f(
|
|
3105
3140
|
scaled.x * czr - scaled.y * szr,
|
|
3106
3141
|
scaled.x * szr + scaled.y * czr,
|
|
3107
3142
|
scaled.z
|
|
3108
3143
|
);
|
|
3109
|
-
const cyr =
|
|
3110
|
-
const syr =
|
|
3111
|
-
const ry1 =
|
|
3144
|
+
const cyr = std4.cos(ry);
|
|
3145
|
+
const syr = std4.sin(ry);
|
|
3146
|
+
const ry1 = d6.vec3f(
|
|
3112
3147
|
rz1.x * cyr + rz1.z * syr,
|
|
3113
3148
|
rz1.y,
|
|
3114
3149
|
rz1.z * cyr - rz1.x * syr
|
|
3115
3150
|
);
|
|
3116
|
-
const cxr =
|
|
3117
|
-
const sxr =
|
|
3118
|
-
const rx1 =
|
|
3151
|
+
const cxr = std4.cos(rx);
|
|
3152
|
+
const sxr = std4.sin(rx);
|
|
3153
|
+
const rx1 = d6.vec3f(
|
|
3119
3154
|
ry1.x,
|
|
3120
3155
|
ry1.y * cxr - ry1.z * sxr,
|
|
3121
3156
|
ry1.y * sxr + ry1.z * cxr
|
|
3122
3157
|
);
|
|
3123
|
-
const worldPos =
|
|
3124
|
-
const nRz =
|
|
3158
|
+
const worldPos = d6.vec4f(rx1.x + px, rx1.y + py, rx1.z + pz, 1);
|
|
3159
|
+
const nRz = d6.vec3f(
|
|
3125
3160
|
skinnedNormal.x * czr - skinnedNormal.y * szr,
|
|
3126
3161
|
skinnedNormal.x * szr + skinnedNormal.y * czr,
|
|
3127
3162
|
skinnedNormal.z
|
|
3128
3163
|
);
|
|
3129
|
-
const nRy =
|
|
3164
|
+
const nRy = d6.vec3f(
|
|
3130
3165
|
nRz.x * cyr + nRz.z * syr,
|
|
3131
3166
|
nRz.y,
|
|
3132
3167
|
nRz.z * cyr - nRz.x * syr
|
|
3133
3168
|
);
|
|
3134
|
-
const nRx =
|
|
3169
|
+
const nRx = d6.vec3f(
|
|
3135
3170
|
nRy.x,
|
|
3136
3171
|
nRy.y * cxr - nRy.z * sxr,
|
|
3137
3172
|
nRy.y * sxr + nRy.z * cxr
|
|
@@ -3140,11 +3175,625 @@ function createSkinnedMeshVertex(layout) {
|
|
|
3140
3175
|
return {
|
|
3141
3176
|
pos: clipPos,
|
|
3142
3177
|
vNormal: nRx,
|
|
3143
|
-
vColor:
|
|
3144
|
-
vUV: input.uv
|
|
3178
|
+
vColor: d6.vec3f(stat.colorR, stat.colorG, stat.colorB),
|
|
3179
|
+
vUV: input.uv,
|
|
3180
|
+
vWorldPos: d6.vec3f(worldPos.x, worldPos.y, worldPos.z)
|
|
3145
3181
|
};
|
|
3146
3182
|
});
|
|
3147
3183
|
}
|
|
3184
|
+
function createSkinnedMeshFragment(meshLayout) {
|
|
3185
|
+
return tgpu6.fragmentFn({
|
|
3186
|
+
in: {
|
|
3187
|
+
vNormal: d6.vec3f,
|
|
3188
|
+
vColor: d6.vec3f,
|
|
3189
|
+
vUV: d6.vec2f,
|
|
3190
|
+
vWorldPos: d6.vec3f
|
|
3191
|
+
},
|
|
3192
|
+
out: d6.vec4f
|
|
3193
|
+
})(function(input) {
|
|
3194
|
+
const u = meshLayout.$.uniforms;
|
|
3195
|
+
const baseColor = input.vColor;
|
|
3196
|
+
const worldPos = input.vWorldPos;
|
|
3197
|
+
const normal = std4.normalize(input.vNormal);
|
|
3198
|
+
const lightDir = std4.normalize(d6.vec3f(u.lightDirX, u.lightDirY, u.lightDirZ));
|
|
3199
|
+
const diff = std4.max(std4.dot(normal, lightDir), 0) * u.lightDirIntensity;
|
|
3200
|
+
let acc = d6.vec3f(
|
|
3201
|
+
baseColor.x * (u.ambientR + u.lightDirR * diff),
|
|
3202
|
+
baseColor.y * (u.ambientG + u.lightDirG * diff),
|
|
3203
|
+
baseColor.z * (u.ambientB + u.lightDirB * diff)
|
|
3204
|
+
);
|
|
3205
|
+
const count = u.lightCount;
|
|
3206
|
+
const a = u.alpha;
|
|
3207
|
+
for (let i = d6.u32(0); i < count; i++) {
|
|
3208
|
+
const L = meshLayout.$.lights[i];
|
|
3209
|
+
const pos = d6.vec3f(
|
|
3210
|
+
std4.mix(L.prevPosX, L.currPosX, a),
|
|
3211
|
+
std4.mix(L.prevPosY, L.currPosY, a),
|
|
3212
|
+
std4.mix(L.prevPosZ, L.currPosZ, a)
|
|
3213
|
+
);
|
|
3214
|
+
const axis = d6.vec3f(
|
|
3215
|
+
std4.mix(L.prevDirX, L.currDirX, a),
|
|
3216
|
+
std4.mix(L.prevDirY, L.currDirY, a),
|
|
3217
|
+
std4.mix(L.prevDirZ, L.currDirZ, a)
|
|
3218
|
+
);
|
|
3219
|
+
const c = lightContribution(
|
|
3220
|
+
pos,
|
|
3221
|
+
axis,
|
|
3222
|
+
d6.vec3f(L.colorR, L.colorG, L.colorB),
|
|
3223
|
+
d6.vec4f(L.intensity, L.range, L.innerCos, L.outerCos),
|
|
3224
|
+
normal,
|
|
3225
|
+
worldPos
|
|
3226
|
+
);
|
|
3227
|
+
acc = d6.vec3f(
|
|
3228
|
+
acc.x + baseColor.x * c.x,
|
|
3229
|
+
acc.y + baseColor.y * c.y,
|
|
3230
|
+
acc.z + baseColor.z * c.z
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
const mapped = tonemap(acc);
|
|
3234
|
+
return d6.vec4f(mapped.x, mapped.y, mapped.z, 1);
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
// src/3d/lights.ts
|
|
3239
|
+
import { SlotMap as SlotMap2 } from "murow/core/slot-map";
|
|
3240
|
+
var KIND = 0;
|
|
3241
|
+
var CURR_POS_X = 1;
|
|
3242
|
+
var CURR_POS_Y = 2;
|
|
3243
|
+
var CURR_POS_Z = 3;
|
|
3244
|
+
var PREV_POS_X = 4;
|
|
3245
|
+
var PREV_POS_Y = 5;
|
|
3246
|
+
var PREV_POS_Z = 6;
|
|
3247
|
+
var CURR_DIR_X = 7;
|
|
3248
|
+
var CURR_DIR_Y = 8;
|
|
3249
|
+
var CURR_DIR_Z = 9;
|
|
3250
|
+
var PREV_DIR_X = 10;
|
|
3251
|
+
var PREV_DIR_Y = 11;
|
|
3252
|
+
var PREV_DIR_Z = 12;
|
|
3253
|
+
var COL_R = 13;
|
|
3254
|
+
var COL_G = 14;
|
|
3255
|
+
var COL_B = 15;
|
|
3256
|
+
var INTENSITY = 16;
|
|
3257
|
+
var RANGE = 17;
|
|
3258
|
+
var INNER_COS = 18;
|
|
3259
|
+
var OUTER_COS = 19;
|
|
3260
|
+
var CASTS_SHADOW = 20;
|
|
3261
|
+
var SHADOW_INDEX = 21;
|
|
3262
|
+
var LightSystem = class {
|
|
3263
|
+
constructor(maxLights) {
|
|
3264
|
+
this.maxLights = maxLights;
|
|
3265
|
+
// Global directional + ambient terms (the classic fixed look; now configurable).
|
|
3266
|
+
this.dirDir = [0.3, 0.8, 0.5];
|
|
3267
|
+
this.dirColor = [1, 1, 1];
|
|
3268
|
+
this.dirIntensity = 1;
|
|
3269
|
+
this.ambient = [0.3, 0.3, 0.3];
|
|
3270
|
+
this.data = new Float32Array(maxLights * LIGHT_FLOATS);
|
|
3271
|
+
this.slots = new SlotMap2(maxLights);
|
|
3272
|
+
this.enabled = new Uint8Array(maxLights).fill(1);
|
|
3273
|
+
this.handles = new Array(maxLights).fill(null);
|
|
3274
|
+
this.uploadData = new Float32Array(maxLights * LIGHT_FLOATS);
|
|
3275
|
+
this.angle = new Float32Array(maxLights);
|
|
3276
|
+
this.smoothness = new Float32Array(maxLights);
|
|
3277
|
+
}
|
|
3278
|
+
/** Add a dynamic point or spot light. Throws past `maxLights`. */
|
|
3279
|
+
add(spec) {
|
|
3280
|
+
const slot = this.slots.add();
|
|
3281
|
+
if (slot === -1)
|
|
3282
|
+
throw new Error(`Max lights (${this.maxLights}) reached`);
|
|
3283
|
+
this.enabled[slot] = 1;
|
|
3284
|
+
this.writeSlot(slot, spec);
|
|
3285
|
+
const data = this.data;
|
|
3286
|
+
const enabledArr = this.enabled;
|
|
3287
|
+
const slots = this.slots;
|
|
3288
|
+
const handles = this.handles;
|
|
3289
|
+
const angleArr = this.angle;
|
|
3290
|
+
const smoothArr = this.smoothness;
|
|
3291
|
+
const base = slot * LIGHT_FLOATS;
|
|
3292
|
+
let destroyed = false;
|
|
3293
|
+
const posOut = [0, 0, 0];
|
|
3294
|
+
const dirOut = [0, 0, 0];
|
|
3295
|
+
const colOut = [0, 0, 0];
|
|
3296
|
+
const handle = {
|
|
3297
|
+
slot,
|
|
3298
|
+
setPosition(x, y, z) {
|
|
3299
|
+
data[base + CURR_POS_X] = x;
|
|
3300
|
+
data[base + CURR_POS_Y] = y;
|
|
3301
|
+
data[base + CURR_POS_Z] = z;
|
|
3302
|
+
},
|
|
3303
|
+
setDirection(x, y, z) {
|
|
3304
|
+
data[base + CURR_DIR_X] = x;
|
|
3305
|
+
data[base + CURR_DIR_Y] = y;
|
|
3306
|
+
data[base + CURR_DIR_Z] = z;
|
|
3307
|
+
},
|
|
3308
|
+
teleport(x, y, z) {
|
|
3309
|
+
data[base + CURR_POS_X] = x;
|
|
3310
|
+
data[base + CURR_POS_Y] = y;
|
|
3311
|
+
data[base + CURR_POS_Z] = z;
|
|
3312
|
+
data[base + PREV_POS_X] = x;
|
|
3313
|
+
data[base + PREV_POS_Y] = y;
|
|
3314
|
+
data[base + PREV_POS_Z] = z;
|
|
3315
|
+
},
|
|
3316
|
+
setColor(r, g, b) {
|
|
3317
|
+
data[base + COL_R] = r;
|
|
3318
|
+
data[base + COL_G] = g;
|
|
3319
|
+
data[base + COL_B] = b;
|
|
3320
|
+
},
|
|
3321
|
+
get position() {
|
|
3322
|
+
posOut[0] = data[base + CURR_POS_X];
|
|
3323
|
+
posOut[1] = data[base + CURR_POS_Y];
|
|
3324
|
+
posOut[2] = data[base + CURR_POS_Z];
|
|
3325
|
+
return posOut;
|
|
3326
|
+
},
|
|
3327
|
+
get direction() {
|
|
3328
|
+
dirOut[0] = data[base + CURR_DIR_X];
|
|
3329
|
+
dirOut[1] = data[base + CURR_DIR_Y];
|
|
3330
|
+
dirOut[2] = data[base + CURR_DIR_Z];
|
|
3331
|
+
return dirOut;
|
|
3332
|
+
},
|
|
3333
|
+
get color() {
|
|
3334
|
+
colOut[0] = data[base + COL_R];
|
|
3335
|
+
colOut[1] = data[base + COL_G];
|
|
3336
|
+
colOut[2] = data[base + COL_B];
|
|
3337
|
+
return colOut;
|
|
3338
|
+
},
|
|
3339
|
+
get intensity() {
|
|
3340
|
+
return data[base + INTENSITY];
|
|
3341
|
+
},
|
|
3342
|
+
set intensity(v) {
|
|
3343
|
+
data[base + INTENSITY] = v;
|
|
3344
|
+
},
|
|
3345
|
+
get range() {
|
|
3346
|
+
return data[base + RANGE];
|
|
3347
|
+
},
|
|
3348
|
+
set range(v) {
|
|
3349
|
+
data[base + RANGE] = v;
|
|
3350
|
+
},
|
|
3351
|
+
get angle() {
|
|
3352
|
+
return angleArr[slot];
|
|
3353
|
+
},
|
|
3354
|
+
set angle(v) {
|
|
3355
|
+
angleArr[slot] = v;
|
|
3356
|
+
const { innerCos, outerCos } = coneCosines(v, smoothArr[slot]);
|
|
3357
|
+
data[base + INNER_COS] = innerCos;
|
|
3358
|
+
data[base + OUTER_COS] = outerCos;
|
|
3359
|
+
},
|
|
3360
|
+
get smoothness() {
|
|
3361
|
+
return smoothArr[slot];
|
|
3362
|
+
},
|
|
3363
|
+
set smoothness(v) {
|
|
3364
|
+
const s = v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3365
|
+
smoothArr[slot] = s;
|
|
3366
|
+
const { innerCos, outerCos } = coneCosines(angleArr[slot], s);
|
|
3367
|
+
data[base + INNER_COS] = innerCos;
|
|
3368
|
+
data[base + OUTER_COS] = outerCos;
|
|
3369
|
+
},
|
|
3370
|
+
get enabled() {
|
|
3371
|
+
return enabledArr[slot] === 1;
|
|
3372
|
+
},
|
|
3373
|
+
set enabled(v) {
|
|
3374
|
+
enabledArr[slot] = v ? 1 : 0;
|
|
3375
|
+
},
|
|
3376
|
+
destroy() {
|
|
3377
|
+
if (destroyed)
|
|
3378
|
+
return;
|
|
3379
|
+
destroyed = true;
|
|
3380
|
+
data.fill(0, base, base + LIGHT_FLOATS);
|
|
3381
|
+
handles[slot] = null;
|
|
3382
|
+
slots.remove(slot);
|
|
3383
|
+
}
|
|
3384
|
+
};
|
|
3385
|
+
this.handles[slot] = handle;
|
|
3386
|
+
return handle;
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Set the global directional light (the "sun"). `direction` points from the
|
|
3390
|
+
* surface toward the light. Defaults to `(0.3, 0.8, 0.5)`, white, intensity 1.
|
|
3391
|
+
*/
|
|
3392
|
+
setDirectional(direction, color = [1, 1, 1], intensity = 1) {
|
|
3393
|
+
this.dirDir = [direction[0], direction[1], direction[2]];
|
|
3394
|
+
this.dirColor = [color[0], color[1], color[2]];
|
|
3395
|
+
this.dirIntensity = intensity;
|
|
3396
|
+
}
|
|
3397
|
+
/** Set the global ambient term. Defaults to `(0.3, 0.3, 0.3)`. */
|
|
3398
|
+
setAmbient(color) {
|
|
3399
|
+
this.ambient = [color[0], color[1], color[2]];
|
|
3400
|
+
}
|
|
3401
|
+
/** Number of live dynamic lights. */
|
|
3402
|
+
get count() {
|
|
3403
|
+
return this.slots.size;
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* Pack enabled lights into a dense run for upload. Disabled lights are
|
|
3407
|
+
* skipped so the shader loop only walks contributing lights. Returns the
|
|
3408
|
+
* shared scratch buffer, the light count, and the byte length to upload
|
|
3409
|
+
* (so the caller never needs the record layout).
|
|
3410
|
+
*/
|
|
3411
|
+
pack() {
|
|
3412
|
+
const active = this.slots.activeSlots;
|
|
3413
|
+
const size = this.slots.size;
|
|
3414
|
+
const src = this.data;
|
|
3415
|
+
const dst = this.uploadData;
|
|
3416
|
+
let count = 0;
|
|
3417
|
+
for (let i = 0; i < size; i++) {
|
|
3418
|
+
const slot = active[i];
|
|
3419
|
+
if (this.enabled[slot] === 0)
|
|
3420
|
+
continue;
|
|
3421
|
+
const sBase = slot * LIGHT_FLOATS;
|
|
3422
|
+
dst.set(src.subarray(sBase, sBase + LIGHT_FLOATS), count * LIGHT_FLOATS);
|
|
3423
|
+
count++;
|
|
3424
|
+
}
|
|
3425
|
+
return { data: dst, count, byteLength: count * LIGHT_FLOATS * 4 };
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Stamp the directional + ambient terms and the light count into the
|
|
3429
|
+
* renderer's uniform array, starting at `offset` (the float index after the
|
|
3430
|
+
* VP matrix + alpha). Layout: lightDir(3), dirColor(3), dirIntensity(1),
|
|
3431
|
+
* ambient(3), then lightCount as a u32 reinterpret at `offset + 10`.
|
|
3432
|
+
*/
|
|
3433
|
+
writeUniforms(uniformData, offset, count) {
|
|
3434
|
+
uniformData[offset + 0] = this.dirDir[0];
|
|
3435
|
+
uniformData[offset + 1] = this.dirDir[1];
|
|
3436
|
+
uniformData[offset + 2] = this.dirDir[2];
|
|
3437
|
+
uniformData[offset + 3] = this.dirColor[0];
|
|
3438
|
+
uniformData[offset + 4] = this.dirColor[1];
|
|
3439
|
+
uniformData[offset + 5] = this.dirColor[2];
|
|
3440
|
+
uniformData[offset + 6] = this.dirIntensity;
|
|
3441
|
+
uniformData[offset + 7] = this.ambient[0];
|
|
3442
|
+
uniformData[offset + 8] = this.ambient[1];
|
|
3443
|
+
uniformData[offset + 9] = this.ambient[2];
|
|
3444
|
+
new Uint32Array(uniformData.buffer)[offset + 10] = count;
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Snapshot every live light's current position/direction into its prev
|
|
3448
|
+
* slot. Called from the renderer's `storePreviousState()` in `pre-tick`, so
|
|
3449
|
+
* the shader can `mix(prev, curr, alpha)` and moving lights interpolate at
|
|
3450
|
+
* render rate instead of snapping at the tick boundary.
|
|
3451
|
+
*/
|
|
3452
|
+
storePrevious() {
|
|
3453
|
+
const active = this.slots.activeSlots;
|
|
3454
|
+
const size = this.slots.size;
|
|
3455
|
+
const data = this.data;
|
|
3456
|
+
for (let i = 0; i < size; i++) {
|
|
3457
|
+
const base = active[i] * LIGHT_FLOATS;
|
|
3458
|
+
data[base + PREV_POS_X] = data[base + CURR_POS_X];
|
|
3459
|
+
data[base + PREV_POS_Y] = data[base + CURR_POS_Y];
|
|
3460
|
+
data[base + PREV_POS_Z] = data[base + CURR_POS_Z];
|
|
3461
|
+
data[base + PREV_DIR_X] = data[base + CURR_DIR_X];
|
|
3462
|
+
data[base + PREV_DIR_Y] = data[base + CURR_DIR_Y];
|
|
3463
|
+
data[base + PREV_DIR_Z] = data[base + CURR_DIR_Z];
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
/** Write a light spec into its CPU slot. Prev is seeded to curr (no spawn lerp). */
|
|
3467
|
+
writeSlot(slot, spec) {
|
|
3468
|
+
const base = slot * LIGHT_FLOATS;
|
|
3469
|
+
const data = this.data;
|
|
3470
|
+
const color = spec.color ?? [1, 1, 1];
|
|
3471
|
+
const [px, py, pz] = spec.position;
|
|
3472
|
+
data[base + KIND] = spec.type === "spot" ? LIGHT_KIND_SPOT : LIGHT_KIND_POINT;
|
|
3473
|
+
data[base + CURR_POS_X] = px;
|
|
3474
|
+
data[base + PREV_POS_X] = px;
|
|
3475
|
+
data[base + CURR_POS_Y] = py;
|
|
3476
|
+
data[base + PREV_POS_Y] = py;
|
|
3477
|
+
data[base + CURR_POS_Z] = pz;
|
|
3478
|
+
data[base + PREV_POS_Z] = pz;
|
|
3479
|
+
data[base + COL_R] = color[0];
|
|
3480
|
+
data[base + COL_G] = color[1];
|
|
3481
|
+
data[base + COL_B] = color[2];
|
|
3482
|
+
data[base + INTENSITY] = spec.intensity ?? 1;
|
|
3483
|
+
data[base + RANGE] = spec.range ?? 10;
|
|
3484
|
+
data[base + CASTS_SHADOW] = 0;
|
|
3485
|
+
data[base + SHADOW_INDEX] = -1;
|
|
3486
|
+
if (spec.type === "spot") {
|
|
3487
|
+
const [dx, dy, dz] = spec.direction;
|
|
3488
|
+
data[base + CURR_DIR_X] = dx;
|
|
3489
|
+
data[base + PREV_DIR_X] = dx;
|
|
3490
|
+
data[base + CURR_DIR_Y] = dy;
|
|
3491
|
+
data[base + PREV_DIR_Y] = dy;
|
|
3492
|
+
data[base + CURR_DIR_Z] = dz;
|
|
3493
|
+
data[base + PREV_DIR_Z] = dz;
|
|
3494
|
+
const angle = spec.angle ?? 0.5;
|
|
3495
|
+
const smoothness = Math.min(1, Math.max(0, spec.smoothness ?? 0.5));
|
|
3496
|
+
this.angle[slot] = angle;
|
|
3497
|
+
this.smoothness[slot] = smoothness;
|
|
3498
|
+
const { innerCos, outerCos } = coneCosines(angle, smoothness);
|
|
3499
|
+
data[base + INNER_COS] = innerCos;
|
|
3500
|
+
data[base + OUTER_COS] = outerCos;
|
|
3501
|
+
} else {
|
|
3502
|
+
data[base + CURR_DIR_X] = 0;
|
|
3503
|
+
data[base + PREV_DIR_X] = 0;
|
|
3504
|
+
data[base + CURR_DIR_Y] = 0;
|
|
3505
|
+
data[base + PREV_DIR_Y] = 0;
|
|
3506
|
+
data[base + CURR_DIR_Z] = 0;
|
|
3507
|
+
data[base + PREV_DIR_Z] = 0;
|
|
3508
|
+
this.angle[slot] = 0;
|
|
3509
|
+
this.smoothness[slot] = 0;
|
|
3510
|
+
data[base + INNER_COS] = 1;
|
|
3511
|
+
data[base + OUTER_COS] = -1;
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
};
|
|
3515
|
+
function coneCosines(angle, smoothness) {
|
|
3516
|
+
const outerCos = Math.cos(angle);
|
|
3517
|
+
const innerCos = Math.cos(angle * (1 - smoothness));
|
|
3518
|
+
return { innerCos, outerCos };
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
// src/camera/camera-3d.ts
|
|
3522
|
+
import { lerp as lerp2 } from "murow/core/lerp";
|
|
3523
|
+
import { Ray3D } from "murow/core/ray";
|
|
3524
|
+
var Camera3D = class {
|
|
3525
|
+
constructor() {
|
|
3526
|
+
this.position = [0, 5, -10];
|
|
3527
|
+
this.target = [0, 0, 0];
|
|
3528
|
+
this.up = [0, 1, 0];
|
|
3529
|
+
this.fov = 60;
|
|
3530
|
+
this.near = 0.1;
|
|
3531
|
+
this.far = 1e3;
|
|
3532
|
+
this.aspect = 1;
|
|
3533
|
+
this.movement = "local";
|
|
3534
|
+
// Previous state for interpolation (stored before each tick)
|
|
3535
|
+
this._prevPosition = [0, 5, -10];
|
|
3536
|
+
this._prevTarget = [0, 0, 0];
|
|
3537
|
+
// Interpolated state used for rendering
|
|
3538
|
+
this._renderPosition = [0, 5, -10];
|
|
3539
|
+
this._renderTarget = [0, 0, 0];
|
|
3540
|
+
this._viewMatrix = new Float32Array(16);
|
|
3541
|
+
this._projMatrix = new Float32Array(16);
|
|
3542
|
+
this._vpMatrix = new Float32Array(16);
|
|
3543
|
+
this._ray = new Ray3D();
|
|
3544
|
+
this._width = 1;
|
|
3545
|
+
this._height = 1;
|
|
3546
|
+
}
|
|
3547
|
+
/**
|
|
3548
|
+
* Store current position/target as previous. Call before each tick.
|
|
3549
|
+
*/
|
|
3550
|
+
storePrevious() {
|
|
3551
|
+
this._prevPosition[0] = this.position[0];
|
|
3552
|
+
this._prevPosition[1] = this.position[1];
|
|
3553
|
+
this._prevPosition[2] = this.position[2];
|
|
3554
|
+
this._prevTarget[0] = this.target[0];
|
|
3555
|
+
this._prevTarget[1] = this.target[1];
|
|
3556
|
+
this._prevTarget[2] = this.target[2];
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* Smoothly move the camera toward a target point.
|
|
3560
|
+
* Call each tick. The camera and its look-at target lerp toward the given position.
|
|
3561
|
+
* @param targetX World X to follow
|
|
3562
|
+
* @param targetY World Y to follow
|
|
3563
|
+
* @param targetZ World Z to follow
|
|
3564
|
+
* @param smoothing 0-1. 1 = snap instantly, 0.1 = lazy follow. Default 1.
|
|
3565
|
+
*/
|
|
3566
|
+
follow(targetX, targetY, targetZ, smoothing = 1) {
|
|
3567
|
+
const dx = targetX - this.target[0];
|
|
3568
|
+
const dy = targetY - this.target[1];
|
|
3569
|
+
const dz = targetZ - this.target[2];
|
|
3570
|
+
const mx = dx * smoothing;
|
|
3571
|
+
const my = dy * smoothing;
|
|
3572
|
+
const mz = dz * smoothing;
|
|
3573
|
+
this.target[0] += mx;
|
|
3574
|
+
this.target[1] += my;
|
|
3575
|
+
this.target[2] += mz;
|
|
3576
|
+
this.position[0] += mx;
|
|
3577
|
+
this.position[1] += my;
|
|
3578
|
+
this.position[2] += mz;
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* Interpolate between previous and current state. Call before rendering.
|
|
3582
|
+
*/
|
|
3583
|
+
interpolate(alpha) {
|
|
3584
|
+
this._renderPosition[0] = lerp2(this._prevPosition[0], this.position[0], alpha);
|
|
3585
|
+
this._renderPosition[1] = lerp2(this._prevPosition[1], this.position[1], alpha);
|
|
3586
|
+
this._renderPosition[2] = lerp2(this._prevPosition[2], this.position[2], alpha);
|
|
3587
|
+
this._renderTarget[0] = lerp2(this._prevTarget[0], this.target[0], alpha);
|
|
3588
|
+
this._renderTarget[1] = lerp2(this._prevTarget[1], this.target[1], alpha);
|
|
3589
|
+
this._renderTarget[2] = lerp2(this._prevTarget[2], this.target[2], alpha);
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Build the view matrix (lookAt) using interpolated state.
|
|
3593
|
+
*/
|
|
3594
|
+
getViewMatrix() {
|
|
3595
|
+
lookAt(this._viewMatrix, this._renderPosition, this._renderTarget, this.up);
|
|
3596
|
+
return this._viewMatrix;
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Build the perspective projection matrix.
|
|
3600
|
+
*/
|
|
3601
|
+
getProjectionMatrix() {
|
|
3602
|
+
perspective(this._projMatrix, this.fov * (Math.PI / 180), this.aspect, this.near, this.far);
|
|
3603
|
+
return this._projMatrix;
|
|
3604
|
+
}
|
|
3605
|
+
/**
|
|
3606
|
+
* Build the combined view-projection matrix.
|
|
3607
|
+
*/
|
|
3608
|
+
getViewProjectionMatrix() {
|
|
3609
|
+
this.getViewMatrix();
|
|
3610
|
+
this.getProjectionMatrix();
|
|
3611
|
+
mat4Multiply(this._vpMatrix, this._projMatrix, this._viewMatrix);
|
|
3612
|
+
return this._vpMatrix;
|
|
3613
|
+
}
|
|
3614
|
+
setAspect(width, height) {
|
|
3615
|
+
this.aspect = width / height;
|
|
3616
|
+
this._width = width;
|
|
3617
|
+
this._height = height;
|
|
3618
|
+
}
|
|
3619
|
+
setPosition(x, y, z) {
|
|
3620
|
+
this.position[0] = x;
|
|
3621
|
+
this.position[1] = y;
|
|
3622
|
+
this.position[2] = z;
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Unproject a screen coordinate into a world-space ray.
|
|
3626
|
+
* Requires `setAspect(width, height)` to have been called first.
|
|
3627
|
+
* Returns a pre-allocated Ray3D — copy origin/direction if you need to store it.
|
|
3628
|
+
*/
|
|
3629
|
+
screenToRay(screenX, screenY) {
|
|
3630
|
+
this.getViewMatrix();
|
|
3631
|
+
const m = this._viewMatrix;
|
|
3632
|
+
const ndcX = 2 * screenX / this._width - 1;
|
|
3633
|
+
const ndcY = 1 - 2 * screenY / this._height;
|
|
3634
|
+
const t = Math.tan(this.fov * Math.PI / 180 * 0.5);
|
|
3635
|
+
const rightX = m[0], rightY = m[4], rightZ = m[8];
|
|
3636
|
+
const upX = m[1], upY = m[5], upZ = m[9];
|
|
3637
|
+
const fwdX = -m[2], fwdY = -m[6], fwdZ = -m[10];
|
|
3638
|
+
const dx = fwdX + rightX * ndcX * t * this.aspect + upX * ndcY * t;
|
|
3639
|
+
const dy = fwdY + rightY * ndcX * t * this.aspect + upY * ndcY * t;
|
|
3640
|
+
const dz = fwdZ + rightZ * ndcX * t * this.aspect + upZ * ndcY * t;
|
|
3641
|
+
this._ray.set(this.position[0], this.position[1], this.position[2], dx, dy, dz);
|
|
3642
|
+
return this._ray;
|
|
3643
|
+
}
|
|
3644
|
+
setTarget(x, y, z) {
|
|
3645
|
+
this.target[0] = x;
|
|
3646
|
+
this.target[1] = y;
|
|
3647
|
+
this.target[2] = z;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Move the camera. Behaviour is determined by the `movement` property:
|
|
3651
|
+
* - `'local'` — along camera axes, pitch included (free-fly / spectator)
|
|
3652
|
+
* - `'grounded'` — yaw-projected XZ + world Y (FPS)
|
|
3653
|
+
* - `'global'` — world axes directly (isometric / platformer)
|
|
3654
|
+
*/
|
|
3655
|
+
move(right, up, forward) {
|
|
3656
|
+
if (this.movement === "grounded")
|
|
3657
|
+
this._moveGrounded(right, up, forward);
|
|
3658
|
+
else if (this.movement === "global")
|
|
3659
|
+
this._moveGlobal(right, up, forward);
|
|
3660
|
+
else
|
|
3661
|
+
this._moveLocal(right, up, forward);
|
|
3662
|
+
}
|
|
3663
|
+
_moveLocal(right, up, forward) {
|
|
3664
|
+
this.getViewMatrix();
|
|
3665
|
+
const m = this._viewMatrix;
|
|
3666
|
+
const dx = m[0] * right + m[1] * up - m[2] * forward;
|
|
3667
|
+
const dy = m[4] * right + m[5] * up - m[6] * forward;
|
|
3668
|
+
const dz = m[8] * right + m[9] * up - m[10] * forward;
|
|
3669
|
+
this.position[0] += dx;
|
|
3670
|
+
this.position[1] += dy;
|
|
3671
|
+
this.position[2] += dz;
|
|
3672
|
+
this.target[0] += dx;
|
|
3673
|
+
this.target[1] += dy;
|
|
3674
|
+
this.target[2] += dz;
|
|
3675
|
+
}
|
|
3676
|
+
_moveGrounded(right, up, forward) {
|
|
3677
|
+
const yaw = Math.atan2(this.target[0] - this.position[0], this.target[2] - this.position[2]);
|
|
3678
|
+
const dx = Math.sin(yaw) * forward + Math.cos(yaw) * right;
|
|
3679
|
+
const dz = Math.cos(yaw) * forward - Math.sin(yaw) * right;
|
|
3680
|
+
this.position[0] += dx;
|
|
3681
|
+
this.position[1] += up;
|
|
3682
|
+
this.position[2] += dz;
|
|
3683
|
+
this.target[0] += dx;
|
|
3684
|
+
this.target[1] += up;
|
|
3685
|
+
this.target[2] += dz;
|
|
3686
|
+
}
|
|
3687
|
+
_moveGlobal(right, up, forward) {
|
|
3688
|
+
this.position[0] += right;
|
|
3689
|
+
this.position[1] += up;
|
|
3690
|
+
this.position[2] += forward;
|
|
3691
|
+
this.target[0] += right;
|
|
3692
|
+
this.target[1] += up;
|
|
3693
|
+
this.target[2] += forward;
|
|
3694
|
+
}
|
|
3695
|
+
/**
|
|
3696
|
+
* Orbit around the target point. Zero allocations.
|
|
3697
|
+
* @param yawDelta Horizontal rotation in radians (positive = rotate right)
|
|
3698
|
+
* @param pitchDelta Vertical rotation in radians (positive = rotate up)
|
|
3699
|
+
*/
|
|
3700
|
+
orbit(yawDelta, pitchDelta) {
|
|
3701
|
+
let ox = this.position[0] - this.target[0];
|
|
3702
|
+
let oy = this.position[1] - this.target[1];
|
|
3703
|
+
let oz = this.position[2] - this.target[2];
|
|
3704
|
+
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
3705
|
+
let yaw = Math.atan2(ox, oz);
|
|
3706
|
+
let pitch = Math.asin(oy / dist);
|
|
3707
|
+
yaw += yawDelta;
|
|
3708
|
+
pitch += pitchDelta;
|
|
3709
|
+
pitch = Math.max(-Math.PI * 0.49, Math.min(Math.PI * 0.49, pitch));
|
|
3710
|
+
this.position[0] = this.target[0] + Math.sin(yaw) * Math.cos(pitch) * dist;
|
|
3711
|
+
this.position[1] = this.target[1] + Math.sin(pitch) * dist;
|
|
3712
|
+
this.position[2] = this.target[2] + Math.cos(yaw) * Math.cos(pitch) * dist;
|
|
3713
|
+
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Zoom by adjusting distance to target. Zero allocations.
|
|
3716
|
+
* @param delta Positive = zoom in, negative = zoom out
|
|
3717
|
+
*/
|
|
3718
|
+
zoom(delta) {
|
|
3719
|
+
let ox = this.position[0] - this.target[0];
|
|
3720
|
+
let oy = this.position[1] - this.target[1];
|
|
3721
|
+
let oz = this.position[2] - this.target[2];
|
|
3722
|
+
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
3723
|
+
const newDist = Math.max(0.1, dist - delta);
|
|
3724
|
+
const scale = newDist / dist;
|
|
3725
|
+
this.position[0] = this.target[0] + ox * scale;
|
|
3726
|
+
this.position[1] = this.target[1] + oy * scale;
|
|
3727
|
+
this.position[2] = this.target[2] + oz * scale;
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
function lookAt(out, eye, center, up) {
|
|
3731
|
+
let fx = center[0] - eye[0];
|
|
3732
|
+
let fy = center[1] - eye[1];
|
|
3733
|
+
let fz = center[2] - eye[2];
|
|
3734
|
+
let len = 1 / Math.sqrt(fx * fx + fy * fy + fz * fz);
|
|
3735
|
+
fx *= len;
|
|
3736
|
+
fy *= len;
|
|
3737
|
+
fz *= len;
|
|
3738
|
+
let sx = fy * up[2] - fz * up[1];
|
|
3739
|
+
let sy = fz * up[0] - fx * up[2];
|
|
3740
|
+
let sz = fx * up[1] - fy * up[0];
|
|
3741
|
+
len = Math.sqrt(sx * sx + sy * sy + sz * sz);
|
|
3742
|
+
if (len > 0) {
|
|
3743
|
+
len = 1 / len;
|
|
3744
|
+
sx *= len;
|
|
3745
|
+
sy *= len;
|
|
3746
|
+
sz *= len;
|
|
3747
|
+
}
|
|
3748
|
+
const ux = sy * fz - sz * fy;
|
|
3749
|
+
const uy = sz * fx - sx * fz;
|
|
3750
|
+
const uz = sx * fy - sy * fx;
|
|
3751
|
+
out[0] = sx;
|
|
3752
|
+
out[1] = ux;
|
|
3753
|
+
out[2] = -fx;
|
|
3754
|
+
out[3] = 0;
|
|
3755
|
+
out[4] = sy;
|
|
3756
|
+
out[5] = uy;
|
|
3757
|
+
out[6] = -fy;
|
|
3758
|
+
out[7] = 0;
|
|
3759
|
+
out[8] = sz;
|
|
3760
|
+
out[9] = uz;
|
|
3761
|
+
out[10] = -fz;
|
|
3762
|
+
out[11] = 0;
|
|
3763
|
+
out[12] = -(sx * eye[0] + sy * eye[1] + sz * eye[2]);
|
|
3764
|
+
out[13] = -(ux * eye[0] + uy * eye[1] + uz * eye[2]);
|
|
3765
|
+
out[14] = fx * eye[0] + fy * eye[1] + fz * eye[2];
|
|
3766
|
+
out[15] = 1;
|
|
3767
|
+
}
|
|
3768
|
+
function perspective(out, fovRad, aspect, near, far) {
|
|
3769
|
+
const f = 1 / Math.tan(fovRad * 0.5);
|
|
3770
|
+
const rangeInv = 1 / (near - far);
|
|
3771
|
+
out[0] = f / aspect;
|
|
3772
|
+
out[1] = 0;
|
|
3773
|
+
out[2] = 0;
|
|
3774
|
+
out[3] = 0;
|
|
3775
|
+
out[4] = 0;
|
|
3776
|
+
out[5] = f;
|
|
3777
|
+
out[6] = 0;
|
|
3778
|
+
out[7] = 0;
|
|
3779
|
+
out[8] = 0;
|
|
3780
|
+
out[9] = 0;
|
|
3781
|
+
out[10] = (near + far) * rangeInv;
|
|
3782
|
+
out[11] = -1;
|
|
3783
|
+
out[12] = 0;
|
|
3784
|
+
out[13] = 0;
|
|
3785
|
+
out[14] = 2 * near * far * rangeInv;
|
|
3786
|
+
out[15] = 0;
|
|
3787
|
+
}
|
|
3788
|
+
function mat4Multiply(out, a, b) {
|
|
3789
|
+
for (let i = 0; i < 4; i++) {
|
|
3790
|
+
const ai0 = a[i], ai1 = a[i + 4], ai2 = a[i + 8], ai3 = a[i + 12];
|
|
3791
|
+
out[i] = ai0 * b[0] + ai1 * b[1] + ai2 * b[2] + ai3 * b[3];
|
|
3792
|
+
out[i + 4] = ai0 * b[4] + ai1 * b[5] + ai2 * b[6] + ai3 * b[7];
|
|
3793
|
+
out[i + 8] = ai0 * b[8] + ai1 * b[9] + ai2 * b[10] + ai3 * b[11];
|
|
3794
|
+
out[i + 12] = ai0 * b[12] + ai1 * b[13] + ai2 * b[14] + ai3 * b[15];
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3148
3797
|
|
|
3149
3798
|
// src/3d/renderer.ts
|
|
3150
3799
|
import {
|
|
@@ -3153,6 +3802,7 @@ import {
|
|
|
3153
3802
|
parseGltf,
|
|
3154
3803
|
SkeletalAnimation
|
|
3155
3804
|
} from "murow/renderer";
|
|
3805
|
+
import { testHitbox3D } from "murow/core/hitbox";
|
|
3156
3806
|
|
|
3157
3807
|
// src/3d/skeletal-animation-compute/packer.ts
|
|
3158
3808
|
function packAnimationData(packed) {
|
|
@@ -3352,8 +4002,8 @@ function buildAnimationKernel(root, packed, maxInstances, maxTotalBones, budgets
|
|
|
3352
4002
|
let by = animF32[offB + 1];
|
|
3353
4003
|
let bz = animF32[offB + 2];
|
|
3354
4004
|
let bw = animF32[offB + 3];
|
|
3355
|
-
const
|
|
3356
|
-
if (
|
|
4005
|
+
const dot3 = ax * bx + ay * by + az * bz + aw * bw;
|
|
4006
|
+
if (dot3 < 0) {
|
|
3357
4007
|
bx = -bx;
|
|
3358
4008
|
by = -by;
|
|
3359
4009
|
bz = -bz;
|
|
@@ -3628,6 +4278,330 @@ var GltfClipResyncCoordinator = class {
|
|
|
3628
4278
|
}
|
|
3629
4279
|
};
|
|
3630
4280
|
|
|
4281
|
+
// src/3d/raycast.ts
|
|
4282
|
+
import { HitBuffer as HitBuffer2 } from "murow/core/raycast";
|
|
4283
|
+
import {
|
|
4284
|
+
Raycast as Raycast2,
|
|
4285
|
+
RaycastMemo as RaycastMemo2
|
|
4286
|
+
} from "murow/renderer";
|
|
4287
|
+
var WebGPURaycast3D = class extends Raycast2 {
|
|
4288
|
+
constructor(renderer) {
|
|
4289
|
+
super();
|
|
4290
|
+
this.renderer = renderer;
|
|
4291
|
+
this.state = new HitBuffer2(3);
|
|
4292
|
+
this.resultBuffer = [];
|
|
4293
|
+
this.memos = /* @__PURE__ */ new Set();
|
|
4294
|
+
}
|
|
4295
|
+
update(input) {
|
|
4296
|
+
this.state.reset();
|
|
4297
|
+
this.renderer._collectRaycastHitsInto(input.mouse.position.x, input.mouse.position.y, this.state);
|
|
4298
|
+
for (const m of this.memos)
|
|
4299
|
+
m._invalidate();
|
|
4300
|
+
}
|
|
4301
|
+
/**
|
|
4302
|
+
* Nearest hit, or null. The returned object is pool-backed and valid
|
|
4303
|
+
* only until the next `update()` -- copy what you need, or use `memo`
|
|
4304
|
+
* for results that persist across frames.
|
|
4305
|
+
*/
|
|
4306
|
+
hit(opts) {
|
|
4307
|
+
return this.state.nearest(opts?.filter, opts?.maxDistance ?? Infinity);
|
|
4308
|
+
}
|
|
4309
|
+
/**
|
|
4310
|
+
* All hits, nearest first. The array and its entries are reused across
|
|
4311
|
+
* calls and overwritten by the next `update()`; do not retain them.
|
|
4312
|
+
*/
|
|
4313
|
+
hitAll(opts) {
|
|
4314
|
+
this.state.collectInto(this.resultBuffer, opts?.filter, opts?.maxDistance ?? Infinity);
|
|
4315
|
+
return this.resultBuffer;
|
|
4316
|
+
}
|
|
4317
|
+
memo(opts) {
|
|
4318
|
+
const m = new WebGPURaycastMemo3D(this.state, opts, () => this.memos.delete(m));
|
|
4319
|
+
this.memos.add(m);
|
|
4320
|
+
return m;
|
|
4321
|
+
}
|
|
4322
|
+
clearMemos() {
|
|
4323
|
+
for (const m of this.memos)
|
|
4324
|
+
m._detach();
|
|
4325
|
+
this.memos.clear();
|
|
4326
|
+
}
|
|
4327
|
+
};
|
|
4328
|
+
var WebGPURaycastMemo3D = class extends RaycastMemo2 {
|
|
4329
|
+
constructor(state, opts, onDispose) {
|
|
4330
|
+
super();
|
|
4331
|
+
this.state = state;
|
|
4332
|
+
this.opts = opts;
|
|
4333
|
+
this.onDispose = onDispose;
|
|
4334
|
+
this.dirty = true;
|
|
4335
|
+
this.detached = false;
|
|
4336
|
+
this.cached = [];
|
|
4337
|
+
}
|
|
4338
|
+
get hits() {
|
|
4339
|
+
if (this.detached)
|
|
4340
|
+
return this.cached;
|
|
4341
|
+
if (this.dirty) {
|
|
4342
|
+
this.state.collectInto(this.cached, this.opts.filter, this.opts.maxDistance ?? Infinity);
|
|
4343
|
+
this.dirty = false;
|
|
4344
|
+
}
|
|
4345
|
+
return this.cached;
|
|
4346
|
+
}
|
|
4347
|
+
get first() {
|
|
4348
|
+
const arr = this.hits;
|
|
4349
|
+
return arr.length > 0 ? arr[0] : null;
|
|
4350
|
+
}
|
|
4351
|
+
dispose() {
|
|
4352
|
+
if (this.detached)
|
|
4353
|
+
return;
|
|
4354
|
+
this.onDispose();
|
|
4355
|
+
this._detach();
|
|
4356
|
+
}
|
|
4357
|
+
_invalidate() {
|
|
4358
|
+
this.dirty = true;
|
|
4359
|
+
}
|
|
4360
|
+
_detach() {
|
|
4361
|
+
this.detached = true;
|
|
4362
|
+
this.cached.length = 0;
|
|
4363
|
+
}
|
|
4364
|
+
};
|
|
4365
|
+
|
|
4366
|
+
// src/3d/hitbox.ts
|
|
4367
|
+
import { placePart3D } from "murow/core/hitbox";
|
|
4368
|
+
function buildUnitSphereWireframe(segments = 16) {
|
|
4369
|
+
const out = [];
|
|
4370
|
+
const step2 = Math.PI * 2 / segments;
|
|
4371
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
4372
|
+
for (let i = 0; i < segments; i++) {
|
|
4373
|
+
const a = i * step2;
|
|
4374
|
+
const b = (i + 1) * step2;
|
|
4375
|
+
const ca = Math.cos(a), sa = Math.sin(a);
|
|
4376
|
+
const cb = Math.cos(b), sb = Math.sin(b);
|
|
4377
|
+
if (axis === 0) {
|
|
4378
|
+
out.push(0, ca, sa, 0, cb, sb);
|
|
4379
|
+
} else if (axis === 1) {
|
|
4380
|
+
out.push(ca, 0, sa, cb, 0, sb);
|
|
4381
|
+
} else {
|
|
4382
|
+
out.push(ca, sa, 0, cb, sb, 0);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
return new Float32Array(out);
|
|
4387
|
+
}
|
|
4388
|
+
function buildUnitBoxWireframe() {
|
|
4389
|
+
const h = 0.5;
|
|
4390
|
+
const corners = [
|
|
4391
|
+
[-h, -h, -h],
|
|
4392
|
+
[h, -h, -h],
|
|
4393
|
+
[h, h, -h],
|
|
4394
|
+
[-h, h, -h],
|
|
4395
|
+
[-h, -h, h],
|
|
4396
|
+
[h, -h, h],
|
|
4397
|
+
[h, h, h],
|
|
4398
|
+
[-h, h, h]
|
|
4399
|
+
];
|
|
4400
|
+
const edges = [
|
|
4401
|
+
[0, 1],
|
|
4402
|
+
[1, 2],
|
|
4403
|
+
[2, 3],
|
|
4404
|
+
[3, 0],
|
|
4405
|
+
[4, 5],
|
|
4406
|
+
[5, 6],
|
|
4407
|
+
[6, 7],
|
|
4408
|
+
[7, 4],
|
|
4409
|
+
[0, 4],
|
|
4410
|
+
[1, 5],
|
|
4411
|
+
[2, 6],
|
|
4412
|
+
[3, 7]
|
|
4413
|
+
];
|
|
4414
|
+
const out = [];
|
|
4415
|
+
for (const [a, b] of edges) {
|
|
4416
|
+
out.push(...corners[a], ...corners[b]);
|
|
4417
|
+
}
|
|
4418
|
+
return new Float32Array(out);
|
|
4419
|
+
}
|
|
4420
|
+
function buildUnitCylinderWireframe(segments = 24) {
|
|
4421
|
+
const h = 0.5;
|
|
4422
|
+
const step2 = Math.PI * 2 / segments;
|
|
4423
|
+
const out = [];
|
|
4424
|
+
for (let i = 0; i < segments; i++) {
|
|
4425
|
+
const a = i * step2, b = (i + 1) * step2;
|
|
4426
|
+
const ca = Math.cos(a), sa = Math.sin(a);
|
|
4427
|
+
const cb = Math.cos(b), sb = Math.sin(b);
|
|
4428
|
+
out.push(ca, -h, sa, cb, -h, sb);
|
|
4429
|
+
out.push(ca, h, sa, cb, h, sb);
|
|
4430
|
+
}
|
|
4431
|
+
for (let i = 0; i < 4; i++) {
|
|
4432
|
+
const a = i * (Math.PI * 0.5);
|
|
4433
|
+
const ca = Math.cos(a), sa = Math.sin(a);
|
|
4434
|
+
out.push(ca, -h, sa, ca, h, sa);
|
|
4435
|
+
}
|
|
4436
|
+
return new Float32Array(out);
|
|
4437
|
+
}
|
|
4438
|
+
var IDLE = [1, 0, 1, 1];
|
|
4439
|
+
var HOVERED = [0.2, 1, 0.4, 1];
|
|
4440
|
+
var UNIFORM_STRIDE = 256;
|
|
4441
|
+
var MIN_BINDING_SIZE = 80;
|
|
4442
|
+
var CAPACITY = 4096;
|
|
4443
|
+
var HitboxDebugRenderer = class {
|
|
4444
|
+
constructor() {
|
|
4445
|
+
this.device = null;
|
|
4446
|
+
this.pipeline = null;
|
|
4447
|
+
this.bindGroup = null;
|
|
4448
|
+
this.uniformBuffer = null;
|
|
4449
|
+
this.sphereBuffer = null;
|
|
4450
|
+
this.sphereVertexCount = 0;
|
|
4451
|
+
this.boxBuffer = null;
|
|
4452
|
+
this.boxVertexCount = 0;
|
|
4453
|
+
this.cylinderBuffer = null;
|
|
4454
|
+
this.cylinderVertexCount = 0;
|
|
4455
|
+
this.stage = new Float32Array(0);
|
|
4456
|
+
this.entries = [];
|
|
4457
|
+
this.vp = new Float32Array(16);
|
|
4458
|
+
}
|
|
4459
|
+
init(device, format) {
|
|
4460
|
+
this.device = device;
|
|
4461
|
+
const upload = (data) => {
|
|
4462
|
+
const buf = device.createBuffer({
|
|
4463
|
+
size: data.byteLength,
|
|
4464
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
4465
|
+
});
|
|
4466
|
+
device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
|
|
4467
|
+
return buf;
|
|
4468
|
+
};
|
|
4469
|
+
const sphereData = buildUnitSphereWireframe();
|
|
4470
|
+
const boxData = buildUnitBoxWireframe();
|
|
4471
|
+
const cylinderData = buildUnitCylinderWireframe();
|
|
4472
|
+
this.sphereBuffer = upload(sphereData);
|
|
4473
|
+
this.sphereVertexCount = sphereData.length / 3;
|
|
4474
|
+
this.boxBuffer = upload(boxData);
|
|
4475
|
+
this.boxVertexCount = boxData.length / 3;
|
|
4476
|
+
this.cylinderBuffer = upload(cylinderData);
|
|
4477
|
+
this.cylinderVertexCount = cylinderData.length / 3;
|
|
4478
|
+
this.uniformBuffer = device.createBuffer({
|
|
4479
|
+
size: UNIFORM_STRIDE * CAPACITY,
|
|
4480
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
4481
|
+
});
|
|
4482
|
+
this.stage = new Float32Array(UNIFORM_STRIDE * CAPACITY / 4);
|
|
4483
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
4484
|
+
entries: [{
|
|
4485
|
+
binding: 0,
|
|
4486
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
4487
|
+
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: MIN_BINDING_SIZE }
|
|
4488
|
+
}]
|
|
4489
|
+
});
|
|
4490
|
+
this.bindGroup = device.createBindGroup({
|
|
4491
|
+
layout: bindGroupLayout,
|
|
4492
|
+
entries: [{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: MIN_BINDING_SIZE } }]
|
|
4493
|
+
});
|
|
4494
|
+
const shaderModule = device.createShaderModule({
|
|
4495
|
+
code: `
|
|
4496
|
+
struct Uniforms {
|
|
4497
|
+
mvp: mat4x4<f32>,
|
|
4498
|
+
color: vec4<f32>,
|
|
4499
|
+
};
|
|
4500
|
+
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
4501
|
+
@vertex
|
|
4502
|
+
fn vs(@location(0) p: vec3<f32>) -> @builtin(position) vec4<f32> {
|
|
4503
|
+
return u.mvp * vec4<f32>(p, 1.0);
|
|
4504
|
+
}
|
|
4505
|
+
@fragment
|
|
4506
|
+
fn fs() -> @location(0) vec4<f32> {
|
|
4507
|
+
return u.color;
|
|
4508
|
+
}
|
|
4509
|
+
`
|
|
4510
|
+
});
|
|
4511
|
+
this.pipeline = device.createRenderPipeline({
|
|
4512
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
|
|
4513
|
+
vertex: {
|
|
4514
|
+
module: shaderModule,
|
|
4515
|
+
entryPoint: "vs",
|
|
4516
|
+
buffers: [{
|
|
4517
|
+
arrayStride: 12,
|
|
4518
|
+
attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }]
|
|
4519
|
+
}]
|
|
4520
|
+
},
|
|
4521
|
+
fragment: { module: shaderModule, entryPoint: "fs", targets: [{ format }] },
|
|
4522
|
+
primitive: { topology: "line-list" },
|
|
4523
|
+
depthStencil: {
|
|
4524
|
+
format: "depth24plus",
|
|
4525
|
+
depthWriteEnabled: false,
|
|
4526
|
+
depthCompare: "less-equal"
|
|
4527
|
+
}
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
begin(vp) {
|
|
4531
|
+
this.entries.length = 0;
|
|
4532
|
+
this.vp = vp;
|
|
4533
|
+
}
|
|
4534
|
+
emit(hb, hovered, px, py, pz, sx, sy, sz) {
|
|
4535
|
+
const p = placePart3D(hb, px, py, pz, sx, sy, sz);
|
|
4536
|
+
const color = hovered ? HOVERED : IDLE;
|
|
4537
|
+
if (hb.shape === "sphere") {
|
|
4538
|
+
this.collect(this.sphereBuffer, this.sphereVertexCount, p.cx, p.cy, p.cz, p.hx, p.hx, p.hx, color);
|
|
4539
|
+
} else if (hb.shape === "box") {
|
|
4540
|
+
this.collect(this.boxBuffer, this.boxVertexCount, p.cx, p.cy, p.cz, p.hx * 2, p.hy * 2, p.hz * 2, color);
|
|
4541
|
+
} else {
|
|
4542
|
+
this.collect(this.cylinderBuffer, this.cylinderVertexCount, p.cx, p.cy, p.cz, p.hx, p.hy * 2, p.hz, color);
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
flush(pass) {
|
|
4546
|
+
const { pipeline, bindGroup, uniformBuffer, entries } = this;
|
|
4547
|
+
if (!pipeline || !bindGroup || !uniformBuffer || entries.length === 0)
|
|
4548
|
+
return;
|
|
4549
|
+
this.device.queue.writeBuffer(
|
|
4550
|
+
uniformBuffer,
|
|
4551
|
+
0,
|
|
4552
|
+
this.stage.buffer,
|
|
4553
|
+
0,
|
|
4554
|
+
entries.length * UNIFORM_STRIDE
|
|
4555
|
+
);
|
|
4556
|
+
pass.setPipeline(pipeline);
|
|
4557
|
+
let currentVbo = null;
|
|
4558
|
+
for (const e of entries) {
|
|
4559
|
+
if (e.vbo !== currentVbo) {
|
|
4560
|
+
pass.setVertexBuffer(0, e.vbo);
|
|
4561
|
+
currentVbo = e.vbo;
|
|
4562
|
+
}
|
|
4563
|
+
pass.setBindGroup(0, bindGroup, [e.offset]);
|
|
4564
|
+
pass.draw(e.vertexCount, 1, 0, 0);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
/**
|
|
4568
|
+
* The model matrix is pure scale-then-translate, so `MVP = VP * M`
|
|
4569
|
+
* collapses to scaling VP's first three columns by the extents and
|
|
4570
|
+
* replacing the fourth with `VP * (center, 1)` -- no matrix multiply.
|
|
4571
|
+
*/
|
|
4572
|
+
collect(vbo, vertexCount, cx, cy, cz, ex, ey, ez, color) {
|
|
4573
|
+
if (!vbo || vertexCount === 0)
|
|
4574
|
+
return;
|
|
4575
|
+
if (this.entries.length >= CAPACITY)
|
|
4576
|
+
return;
|
|
4577
|
+
const idx = this.entries.length;
|
|
4578
|
+
const base = idx * UNIFORM_STRIDE >>> 2;
|
|
4579
|
+
const f324 = this.stage;
|
|
4580
|
+
const vp = this.vp;
|
|
4581
|
+
f324[base + 0] = vp[0] * ex;
|
|
4582
|
+
f324[base + 1] = vp[1] * ex;
|
|
4583
|
+
f324[base + 2] = vp[2] * ex;
|
|
4584
|
+
f324[base + 3] = vp[3] * ex;
|
|
4585
|
+
f324[base + 4] = vp[4] * ey;
|
|
4586
|
+
f324[base + 5] = vp[5] * ey;
|
|
4587
|
+
f324[base + 6] = vp[6] * ey;
|
|
4588
|
+
f324[base + 7] = vp[7] * ey;
|
|
4589
|
+
f324[base + 8] = vp[8] * ez;
|
|
4590
|
+
f324[base + 9] = vp[9] * ez;
|
|
4591
|
+
f324[base + 10] = vp[10] * ez;
|
|
4592
|
+
f324[base + 11] = vp[11] * ez;
|
|
4593
|
+
f324[base + 12] = vp[0] * cx + vp[4] * cy + vp[8] * cz + vp[12];
|
|
4594
|
+
f324[base + 13] = vp[1] * cx + vp[5] * cy + vp[9] * cz + vp[13];
|
|
4595
|
+
f324[base + 14] = vp[2] * cx + vp[6] * cy + vp[10] * cz + vp[14];
|
|
4596
|
+
f324[base + 15] = vp[3] * cx + vp[7] * cy + vp[11] * cz + vp[15];
|
|
4597
|
+
f324[base + 16] = color[0];
|
|
4598
|
+
f324[base + 17] = color[1];
|
|
4599
|
+
f324[base + 18] = color[2];
|
|
4600
|
+
f324[base + 19] = color[3];
|
|
4601
|
+
this.entries.push({ vbo, vertexCount, offset: idx * UNIFORM_STRIDE });
|
|
4602
|
+
}
|
|
4603
|
+
};
|
|
4604
|
+
|
|
3631
4605
|
// src/3d/renderer.ts
|
|
3632
4606
|
var DYN_PREV_PX = 0;
|
|
3633
4607
|
var DYN_PREV_PY = 1;
|
|
@@ -3697,6 +4671,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3697
4671
|
this.resizeCallbacks = [];
|
|
3698
4672
|
// layer=0, sheetId=modelId
|
|
3699
4673
|
this.staticDirty = false;
|
|
4674
|
+
this.nextInstanceId = 0;
|
|
3700
4675
|
// Models (vertex + index buffers)
|
|
3701
4676
|
this.models = [];
|
|
3702
4677
|
this.nextModelId = 0;
|
|
@@ -3722,10 +4697,15 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3722
4697
|
this.freedBoneOffsets = /* @__PURE__ */ new Map();
|
|
3723
4698
|
// Frustum planes (6 planes × 4 floats each), extracted from VP matrix
|
|
3724
4699
|
this.frustumPlanes = new Float32Array(24);
|
|
3725
|
-
|
|
3726
|
-
//
|
|
4700
|
+
// Dynamic lights — CPU state (SoA, slots, globals) lives in LightSystem;
|
|
4701
|
+
// the renderer owns only the GPU buffer it packs into each frame.
|
|
4702
|
+
this.lights = new LightSystem(MAX_LIGHTS);
|
|
4703
|
+
this.uniformData = new Float32Array(MESH_UNIFORM_FLOATS);
|
|
3727
4704
|
this.lastRenderTime = 0;
|
|
4705
|
+
this.debug = { hitboxes: false };
|
|
4706
|
+
this.hitboxDebug = new HitboxDebugRenderer();
|
|
3728
4707
|
this.camera = new Camera3D();
|
|
4708
|
+
this.raycast = new WebGPURaycast3D(this);
|
|
3729
4709
|
this._prefabs = options.prefabs ?? null;
|
|
3730
4710
|
const SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP = 3;
|
|
3731
4711
|
const bucketStats = this._prefabs ? computeBucketStats(this._prefabs) : null;
|
|
@@ -3737,14 +4717,15 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3737
4717
|
this.updatedBoneOffsets = new Uint8Array(this.maxTotalBones);
|
|
3738
4718
|
this.boneOffsetRefcount = new Uint32Array(this.maxTotalBones);
|
|
3739
4719
|
this.boneOffsetSkinIndex = new Uint32Array(this.maxTotalBones);
|
|
3740
|
-
this.freeList = new
|
|
4720
|
+
this.freeList = new FreeList2(resolvedMaxInstances);
|
|
3741
4721
|
this.batcher = new SparseBatcher2(resolvedMaxInstances);
|
|
3742
4722
|
this.dynamicData = new Float32Array(resolvedMaxInstances * DYNAMIC_MESH_FLOATS);
|
|
3743
4723
|
this.staticData = new Float32Array(resolvedMaxInstances * STATIC_MESH_FLOATS);
|
|
3744
4724
|
this.slotIndexData = new Uint32Array(resolvedMaxInstances);
|
|
3745
4725
|
this.instanceModelIds = new Uint8Array(resolvedMaxInstances);
|
|
4726
|
+
this.instanceHandles = new Array(resolvedMaxInstances).fill(null);
|
|
3746
4727
|
const msi = this.maxSkinnedInstances;
|
|
3747
|
-
this.skinnedFreeList = new
|
|
4728
|
+
this.skinnedFreeList = new FreeList2(msi);
|
|
3748
4729
|
this.skinnedBatcher = new SparseBatcher2(msi);
|
|
3749
4730
|
this.skinnedDynamicData = new Float32Array(msi * DYNAMIC_MESH_FLOATS);
|
|
3750
4731
|
this.skinnedStaticData = new Float32Array(msi * SKINNED_STATIC_MESH_FLOATS);
|
|
@@ -3753,6 +4734,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3753
4734
|
this.skinnedInstanceModelIds = new Uint8Array(msi);
|
|
3754
4735
|
this.skinnedInstanceBoneOffsets = new Uint32Array(msi);
|
|
3755
4736
|
this.skinnedAnimStates = new Array(msi).fill(null);
|
|
4737
|
+
this.skinnedInstanceHandles = new Array(msi).fill(null);
|
|
3756
4738
|
this.boneMatrixData = new Float32Array(this.maxTotalBones * 16);
|
|
3757
4739
|
const instBufSize = msi * 8;
|
|
3758
4740
|
this.gpuInstData = new Float32Array(instBufSize);
|
|
@@ -3771,7 +4753,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3771
4753
|
maxComputeInvocationsPerWorkgroup: a.maxComputeInvocationsPerWorkgroup
|
|
3772
4754
|
};
|
|
3773
4755
|
const device = await adapter.requestDevice({ requiredLimits });
|
|
3774
|
-
this.root =
|
|
4756
|
+
this.root = tgpu7.initFromDevice({ device });
|
|
3775
4757
|
this.device = this.root.device;
|
|
3776
4758
|
this.context = this.canvas.getContext("webgpu");
|
|
3777
4759
|
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
@@ -3782,7 +4764,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3782
4764
|
});
|
|
3783
4765
|
this._width = this.canvas.width;
|
|
3784
4766
|
this._height = this.canvas.height;
|
|
3785
|
-
this.camera.
|
|
4767
|
+
this.camera.setAspect(this.canvas.clientWidth || this._width, this.canvas.clientHeight || this._height);
|
|
3786
4768
|
this.depthTexture = this.device.createTexture({
|
|
3787
4769
|
size: [this._width, this._height],
|
|
3788
4770
|
format: "depth24plus",
|
|
@@ -3812,7 +4794,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3812
4794
|
};
|
|
3813
4795
|
const vertex = createMeshVertex(this.meshLayout);
|
|
3814
4796
|
const fragment = createMeshFragment(this.meshLayout);
|
|
3815
|
-
const { code: wgslCode } =
|
|
4797
|
+
const { code: wgslCode } = tgpu7.resolveWithContext([vertex, fragment]);
|
|
3816
4798
|
const shaderModule = this.device.createShaderModule({ code: wgslCode });
|
|
3817
4799
|
const rawBGL = this.root.unwrap(this.meshLayout);
|
|
3818
4800
|
this.rawPipeline = this.device.createRenderPipeline({
|
|
@@ -3825,7 +4807,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3825
4807
|
const texLayout = createTextureBindGroupLayout();
|
|
3826
4808
|
const texVertex = createTexturedMeshVertex(this.meshLayout);
|
|
3827
4809
|
const texFragment = createTexturedMeshFragment(this.meshLayout, texLayout);
|
|
3828
|
-
const { code: texWgslCode } =
|
|
4810
|
+
const { code: texWgslCode } = tgpu7.resolveWithContext([texVertex, texFragment]);
|
|
3829
4811
|
const texShaderModule = this.device.createShaderModule({ code: texWgslCode });
|
|
3830
4812
|
const rawTexBGL = this.root.unwrap(texLayout);
|
|
3831
4813
|
this.rawTexturedPipeline = this.device.createRenderPipeline({
|
|
@@ -3839,17 +4821,20 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3839
4821
|
this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.maxInstances)).$usage("storage");
|
|
3840
4822
|
this.uniformBuffer = this.root.createBuffer(MeshUniforms).$usage("uniform");
|
|
3841
4823
|
this.slotIndexBuffer = this.root.createBuffer(d.arrayOf(d.u32, this.maxInstances)).$usage("storage");
|
|
4824
|
+
this.lightBuffer = this.root.createBuffer(d.arrayOf(Light, MAX_LIGHTS)).$usage("storage");
|
|
3842
4825
|
this.rawDynamicBuffer = this.root.unwrap(this.dynamicBuffer);
|
|
3843
4826
|
this.rawStaticBuffer = this.root.unwrap(this.staticBuffer);
|
|
3844
4827
|
this.rawUniformBuffer = this.root.unwrap(this.uniformBuffer);
|
|
3845
4828
|
this.rawSlotIndexBuffer = this.root.unwrap(this.slotIndexBuffer);
|
|
4829
|
+
this.rawLightBuffer = this.root.unwrap(this.lightBuffer);
|
|
3846
4830
|
this.rawBindGroup = this.device.createBindGroup({
|
|
3847
4831
|
layout: rawBGL,
|
|
3848
4832
|
entries: [
|
|
3849
4833
|
{ binding: 0, resource: { buffer: this.rawUniformBuffer } },
|
|
3850
4834
|
{ binding: 1, resource: { buffer: this.rawDynamicBuffer } },
|
|
3851
4835
|
{ binding: 2, resource: { buffer: this.rawStaticBuffer } },
|
|
3852
|
-
{ binding: 3, resource: { buffer: this.rawSlotIndexBuffer } }
|
|
4836
|
+
{ binding: 3, resource: { buffer: this.rawSlotIndexBuffer } },
|
|
4837
|
+
{ binding: 4, resource: { buffer: this.rawLightBuffer } }
|
|
3853
4838
|
]
|
|
3854
4839
|
});
|
|
3855
4840
|
const msi = this.maxSkinnedInstances;
|
|
@@ -3871,8 +4856,8 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3871
4856
|
]
|
|
3872
4857
|
};
|
|
3873
4858
|
const skinnedVertex = createSkinnedMeshVertex(this.skinnedMeshLayout);
|
|
3874
|
-
const skinnedFragment =
|
|
3875
|
-
const { code: skinnedWgsl } =
|
|
4859
|
+
const skinnedFragment = createSkinnedMeshFragment(this.skinnedMeshLayout);
|
|
4860
|
+
const { code: skinnedWgsl } = tgpu7.resolveWithContext([skinnedVertex, skinnedFragment]);
|
|
3876
4861
|
const skinnedShaderModule = this.device.createShaderModule({ code: skinnedWgsl });
|
|
3877
4862
|
const rawSkinnedBGL = this.root.unwrap(this.skinnedMeshLayout);
|
|
3878
4863
|
this.rawSkinnedPipeline = this.device.createRenderPipeline({
|
|
@@ -3884,7 +4869,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3884
4869
|
});
|
|
3885
4870
|
const skinnedTexVertex = createSkinnedMeshVertex(this.skinnedMeshLayout);
|
|
3886
4871
|
const skinnedTexFragment = createTexturedMeshFragment(this.skinnedMeshLayout, texLayout);
|
|
3887
|
-
const { code: skinnedTexWgsl } =
|
|
4872
|
+
const { code: skinnedTexWgsl } = tgpu7.resolveWithContext([skinnedTexVertex, skinnedTexFragment]);
|
|
3888
4873
|
const skinnedTexShaderModule = this.device.createShaderModule({ code: skinnedTexWgsl });
|
|
3889
4874
|
this.rawSkinnedTexturedPipeline = this.device.createRenderPipeline({
|
|
3890
4875
|
layout: this.device.createPipelineLayout({ bindGroupLayouts: [rawSkinnedBGL, rawTexBGL] }),
|
|
@@ -3908,12 +4893,14 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3908
4893
|
{ binding: 1, resource: { buffer: this.rawSkinnedDynamicBuffer } },
|
|
3909
4894
|
{ binding: 2, resource: { buffer: this.rawSkinnedStaticBuffer } },
|
|
3910
4895
|
{ binding: 3, resource: { buffer: this.rawSkinnedSlotIndexBuffer } },
|
|
3911
|
-
{ binding: 4, resource: { buffer: this.rawBoneMatrixBuffer } }
|
|
4896
|
+
{ binding: 4, resource: { buffer: this.rawBoneMatrixBuffer } },
|
|
4897
|
+
{ binding: 5, resource: { buffer: this.rawLightBuffer } }
|
|
3912
4898
|
]
|
|
3913
4899
|
});
|
|
3914
4900
|
if (this._prefabs) {
|
|
3915
4901
|
this.uploadPrefabBucket(this._prefabs);
|
|
3916
4902
|
}
|
|
4903
|
+
this.hitboxDebug.init(this.device, this.format);
|
|
3917
4904
|
this.setupResizeObserver();
|
|
3918
4905
|
this._initialized = true;
|
|
3919
4906
|
}
|
|
@@ -3983,7 +4970,10 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
3983
4970
|
alphaMode: "premultiplied"
|
|
3984
4971
|
});
|
|
3985
4972
|
}
|
|
3986
|
-
|
|
4973
|
+
const cssBox = entry.contentBoxSize?.[0];
|
|
4974
|
+
const cssW = cssBox ? cssBox.inlineSize : w;
|
|
4975
|
+
const cssH = cssBox ? cssBox.blockSize : h;
|
|
4976
|
+
this.camera.setAspect(cssW, cssH);
|
|
3987
4977
|
this.depthTexture.destroy();
|
|
3988
4978
|
this.depthTexture = this.device.createTexture({
|
|
3989
4979
|
size: [w, h],
|
|
@@ -4037,6 +5027,33 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4037
5027
|
get maxSkinned() {
|
|
4038
5028
|
return this.maxSkinnedInstances;
|
|
4039
5029
|
}
|
|
5030
|
+
/**
|
|
5031
|
+
* Add a dynamic point or spot light. Returns a live handle whose position,
|
|
5032
|
+
* color, intensity, range, and enabled state can all be changed every frame.
|
|
5033
|
+
* Up to `MAX_LIGHTS` (64) lights may be live at once; throws past that.
|
|
5034
|
+
*
|
|
5035
|
+
* The global directional + ambient terms are separate — see
|
|
5036
|
+
* `setDirectionalLight` / `setAmbient`.
|
|
5037
|
+
*/
|
|
5038
|
+
addLight(spec) {
|
|
5039
|
+
return this.lights.add(spec);
|
|
5040
|
+
}
|
|
5041
|
+
/**
|
|
5042
|
+
* Set the global directional light (the "sun"). `direction` points from the
|
|
5043
|
+
* surface toward the light. Defaults to `(0.3, 0.8, 0.5)`, white, intensity 1
|
|
5044
|
+
* — the engine's classic fixed look.
|
|
5045
|
+
*/
|
|
5046
|
+
setDirectionalLight(direction, color = [1, 1, 1], intensity = 1) {
|
|
5047
|
+
this.lights.setDirectional(direction, color, intensity);
|
|
5048
|
+
}
|
|
5049
|
+
/** Set the global ambient term. Defaults to `(0.3, 0.3, 0.3)`. */
|
|
5050
|
+
setAmbient(color) {
|
|
5051
|
+
this.lights.setAmbient(color);
|
|
5052
|
+
}
|
|
5053
|
+
/** Number of live dynamic lights. */
|
|
5054
|
+
get lightCount() {
|
|
5055
|
+
return this.lights.count;
|
|
5056
|
+
}
|
|
4040
5057
|
/**
|
|
4041
5058
|
* Create a flat grid mesh on the XZ plane at Y=0.
|
|
4042
5059
|
*
|
|
@@ -4327,13 +5344,30 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4327
5344
|
const uvs = data.uvs ?? new Float32Array(vertexCount * 2);
|
|
4328
5345
|
const hasTexture = !!texture;
|
|
4329
5346
|
let maxRadiusSq = 0;
|
|
5347
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
5348
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
4330
5349
|
for (let i = 0; i < vertexCount; i++) {
|
|
4331
5350
|
const px = positions[i * 3], py = positions[i * 3 + 1], pz = positions[i * 3 + 2];
|
|
4332
5351
|
const rSq = px * px + py * py + pz * pz;
|
|
4333
5352
|
if (rSq > maxRadiusSq)
|
|
4334
5353
|
maxRadiusSq = rSq;
|
|
5354
|
+
if (px < minX)
|
|
5355
|
+
minX = px;
|
|
5356
|
+
if (px > maxX)
|
|
5357
|
+
maxX = px;
|
|
5358
|
+
if (py < minY)
|
|
5359
|
+
minY = py;
|
|
5360
|
+
if (py > maxY)
|
|
5361
|
+
maxY = py;
|
|
5362
|
+
if (pz < minZ)
|
|
5363
|
+
minZ = pz;
|
|
5364
|
+
if (pz > maxZ)
|
|
5365
|
+
maxZ = pz;
|
|
4335
5366
|
}
|
|
4336
5367
|
const boundingRadius = Math.sqrt(maxRadiusSq);
|
|
5368
|
+
const halfX = vertexCount ? (maxX - minX) * 0.5 : 0;
|
|
5369
|
+
const halfY = vertexCount ? (maxY - minY) * 0.5 : 0;
|
|
5370
|
+
const halfZ = vertexCount ? (maxZ - minZ) * 0.5 : 0;
|
|
4337
5371
|
const interleaved = new Float32Array(vertexCount * 8);
|
|
4338
5372
|
for (let i = 0; i < vertexCount; i++) {
|
|
4339
5373
|
const o = i * 8;
|
|
@@ -4394,6 +5428,9 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4394
5428
|
indexCount,
|
|
4395
5429
|
indexFormat: indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
4396
5430
|
boundingRadius,
|
|
5431
|
+
halfX,
|
|
5432
|
+
halfY,
|
|
5433
|
+
halfZ,
|
|
4397
5434
|
hasTexture,
|
|
4398
5435
|
textureBindGroup,
|
|
4399
5436
|
skinned: false,
|
|
@@ -4412,13 +5449,30 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4412
5449
|
const uvs = data.uvs ?? new Float32Array(vertexCount * 2);
|
|
4413
5450
|
const hasTexture = !!texture;
|
|
4414
5451
|
let maxRadiusSq = 0;
|
|
5452
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
5453
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
4415
5454
|
for (let i = 0; i < vertexCount; i++) {
|
|
4416
5455
|
const px = positions[i * 3], py = positions[i * 3 + 1], pz = positions[i * 3 + 2];
|
|
4417
5456
|
const rSq = px * px + py * py + pz * pz;
|
|
4418
5457
|
if (rSq > maxRadiusSq)
|
|
4419
5458
|
maxRadiusSq = rSq;
|
|
5459
|
+
if (px < minX)
|
|
5460
|
+
minX = px;
|
|
5461
|
+
if (px > maxX)
|
|
5462
|
+
maxX = px;
|
|
5463
|
+
if (py < minY)
|
|
5464
|
+
minY = py;
|
|
5465
|
+
if (py > maxY)
|
|
5466
|
+
maxY = py;
|
|
5467
|
+
if (pz < minZ)
|
|
5468
|
+
minZ = pz;
|
|
5469
|
+
if (pz > maxZ)
|
|
5470
|
+
maxZ = pz;
|
|
4420
5471
|
}
|
|
4421
5472
|
const boundingRadius = Math.sqrt(maxRadiusSq);
|
|
5473
|
+
const halfX = vertexCount ? (maxX - minX) * 0.5 : 0;
|
|
5474
|
+
const halfY = vertexCount ? (maxY - minY) * 0.5 : 0;
|
|
5475
|
+
const halfZ = vertexCount ? (maxZ - minZ) * 0.5 : 0;
|
|
4422
5476
|
const buf = new ArrayBuffer(vertexCount * 56);
|
|
4423
5477
|
const floatView = new Float32Array(buf);
|
|
4424
5478
|
const u16View = new Uint16Array(buf);
|
|
@@ -4490,6 +5544,9 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4490
5544
|
indexCount,
|
|
4491
5545
|
indexFormat: indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
4492
5546
|
boundingRadius,
|
|
5547
|
+
halfX,
|
|
5548
|
+
halfY,
|
|
5549
|
+
halfZ,
|
|
4493
5550
|
hasTexture,
|
|
4494
5551
|
textureBindGroup,
|
|
4495
5552
|
skinned: true,
|
|
@@ -4622,17 +5679,18 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4622
5679
|
* with another instance (e.g., when spawning all parts of a character).
|
|
4623
5680
|
*/
|
|
4624
5681
|
addInstance(opts) {
|
|
5682
|
+
const userPrefabId = isPrefab3D(opts.model) ? opts.model.id : null;
|
|
4625
5683
|
if (isPrefab3D(opts.model) && opts.model.type === "composite") {
|
|
4626
5684
|
return this.addCompositeInstance(opts, opts.model);
|
|
4627
5685
|
}
|
|
4628
5686
|
const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
|
|
4629
5687
|
if ("parts" in modelOrGltf) {
|
|
4630
|
-
return this.addGltfInstance(opts, modelOrGltf);
|
|
5688
|
+
return this.addGltfInstance(opts, modelOrGltf, userPrefabId);
|
|
4631
5689
|
}
|
|
4632
5690
|
const modelHandle = modelOrGltf;
|
|
4633
5691
|
const model = this.models[modelHandle.id];
|
|
4634
5692
|
if (model?.skinned) {
|
|
4635
|
-
return this.addSkinnedInstance(opts, modelHandle, model.skinIndex);
|
|
5693
|
+
return this.addSkinnedInstance(opts, modelHandle, model.skinIndex, void 0, userPrefabId);
|
|
4636
5694
|
}
|
|
4637
5695
|
const slot = this.freeList.allocate();
|
|
4638
5696
|
if (slot === -1)
|
|
@@ -4665,10 +5723,16 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4665
5723
|
const staticData = this.staticData;
|
|
4666
5724
|
const self = this;
|
|
4667
5725
|
let destroyed = false;
|
|
5726
|
+
const posOut = [0, 0, 0];
|
|
5727
|
+
const rotOut = [0, 0, 0];
|
|
5728
|
+
const sclOut = [0, 0, 0];
|
|
5729
|
+
const id = ++this.nextInstanceId;
|
|
4668
5730
|
const handle = {
|
|
5731
|
+
id,
|
|
4669
5732
|
slot,
|
|
4670
5733
|
modelId: modelHandle.id,
|
|
4671
5734
|
skinned: false,
|
|
5735
|
+
prefabId: userPrefabId,
|
|
4672
5736
|
setPosition(nx, ny, nz) {
|
|
4673
5737
|
dynamicData[dynBase + DYN_CURR_PX] = nx;
|
|
4674
5738
|
dynamicData[dynBase + DYN_CURR_PY] = ny;
|
|
@@ -4684,6 +5748,32 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4684
5748
|
staticData[statBase + STAT_SY] = ny;
|
|
4685
5749
|
staticData[statBase + STAT_SZ] = nz;
|
|
4686
5750
|
},
|
|
5751
|
+
teleport(nx, ny, nz) {
|
|
5752
|
+
dynamicData[dynBase + DYN_PREV_PX] = nx;
|
|
5753
|
+
dynamicData[dynBase + DYN_PREV_PY] = ny;
|
|
5754
|
+
dynamicData[dynBase + DYN_PREV_PZ] = nz;
|
|
5755
|
+
dynamicData[dynBase + DYN_CURR_PX] = nx;
|
|
5756
|
+
dynamicData[dynBase + DYN_CURR_PY] = ny;
|
|
5757
|
+
dynamicData[dynBase + DYN_CURR_PZ] = nz;
|
|
5758
|
+
},
|
|
5759
|
+
get position() {
|
|
5760
|
+
posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
|
|
5761
|
+
posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
|
|
5762
|
+
posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
|
|
5763
|
+
return posOut;
|
|
5764
|
+
},
|
|
5765
|
+
get rotation() {
|
|
5766
|
+
rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
|
|
5767
|
+
rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
|
|
5768
|
+
rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
|
|
5769
|
+
return rotOut;
|
|
5770
|
+
},
|
|
5771
|
+
get scale() {
|
|
5772
|
+
sclOut[0] = staticData[statBase + STAT_SX];
|
|
5773
|
+
sclOut[1] = staticData[statBase + STAT_SY];
|
|
5774
|
+
sclOut[2] = staticData[statBase + STAT_SZ];
|
|
5775
|
+
return sclOut;
|
|
5776
|
+
},
|
|
4687
5777
|
destroy() {
|
|
4688
5778
|
if (destroyed)
|
|
4689
5779
|
return;
|
|
@@ -4692,12 +5782,14 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4692
5782
|
self.freeList.free(slot);
|
|
4693
5783
|
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4694
5784
|
staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
|
|
5785
|
+
self.instanceHandles[slot] = null;
|
|
4695
5786
|
self.staticDirty = true;
|
|
4696
5787
|
}
|
|
4697
5788
|
};
|
|
5789
|
+
this.instanceHandles[slot] = handle;
|
|
4698
5790
|
return handle;
|
|
4699
5791
|
}
|
|
4700
|
-
addGltfInstance(opts, gltf) {
|
|
5792
|
+
addGltfInstance(opts, gltf, prefabId) {
|
|
4701
5793
|
const childHandles = [];
|
|
4702
5794
|
let firstSkinnedSlot;
|
|
4703
5795
|
for (const part of gltf.parts) {
|
|
@@ -4705,7 +5797,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4705
5797
|
const model = this.models[part.id];
|
|
4706
5798
|
let handle;
|
|
4707
5799
|
if (model?.skinned) {
|
|
4708
|
-
handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot);
|
|
5800
|
+
handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot, prefabId);
|
|
4709
5801
|
if (firstSkinnedSlot === void 0)
|
|
4710
5802
|
firstSkinnedSlot = handle.slot;
|
|
4711
5803
|
} else {
|
|
@@ -4714,8 +5806,11 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4714
5806
|
childHandles.push(handle);
|
|
4715
5807
|
}
|
|
4716
5808
|
const skinnedHandle = childHandles.find((h) => h.skinned);
|
|
5809
|
+
const lead = childHandles[0];
|
|
4717
5810
|
return {
|
|
5811
|
+
id: lead.id,
|
|
4718
5812
|
skinned: gltf.skinned,
|
|
5813
|
+
prefabId,
|
|
4719
5814
|
setPosition(x, y, z) {
|
|
4720
5815
|
for (const h of childHandles)
|
|
4721
5816
|
h.setPosition(x, y, z);
|
|
@@ -4728,6 +5823,19 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4728
5823
|
for (const h of childHandles)
|
|
4729
5824
|
h.setScale(x, y, z);
|
|
4730
5825
|
},
|
|
5826
|
+
teleport(x, y, z) {
|
|
5827
|
+
for (const h of childHandles)
|
|
5828
|
+
h.teleport(x, y, z);
|
|
5829
|
+
},
|
|
5830
|
+
get position() {
|
|
5831
|
+
return lead.position;
|
|
5832
|
+
},
|
|
5833
|
+
get rotation() {
|
|
5834
|
+
return lead.rotation;
|
|
5835
|
+
},
|
|
5836
|
+
get scale() {
|
|
5837
|
+
return lead.scale;
|
|
5838
|
+
},
|
|
4731
5839
|
play: skinnedHandle?.play ? (name, opts2) => {
|
|
4732
5840
|
skinnedHandle.play(name, opts2);
|
|
4733
5841
|
} : void 0,
|
|
@@ -4764,6 +5872,17 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4764
5872
|
rz: p.offset?.rotation?.[2] ?? 0
|
|
4765
5873
|
}));
|
|
4766
5874
|
const childHandles = [];
|
|
5875
|
+
const posOut = [basePos[0], basePos[1], basePos[2]];
|
|
5876
|
+
const rotOut = [baseRot[0], baseRot[1], baseRot[2]];
|
|
5877
|
+
const sclOut = [1, 1, 1];
|
|
5878
|
+
const initialScale = opts.scale;
|
|
5879
|
+
if (typeof initialScale === "number") {
|
|
5880
|
+
sclOut[0] = sclOut[1] = sclOut[2] = initialScale;
|
|
5881
|
+
} else if (initialScale) {
|
|
5882
|
+
sclOut[0] = initialScale[0];
|
|
5883
|
+
sclOut[1] = initialScale[1];
|
|
5884
|
+
sclOut[2] = initialScale[2];
|
|
5885
|
+
}
|
|
4767
5886
|
for (let i = 0; i < composite.parts.length; i++) {
|
|
4768
5887
|
const part = composite.parts[i];
|
|
4769
5888
|
const off = offsets[i];
|
|
@@ -4777,30 +5896,59 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4777
5896
|
childHandles.push(this.addInstance(partOpts));
|
|
4778
5897
|
}
|
|
4779
5898
|
return {
|
|
5899
|
+
id: childHandles[0].id,
|
|
4780
5900
|
skinned: childHandles.some((h) => h.skinned),
|
|
5901
|
+
prefabId: composite.id,
|
|
4781
5902
|
setPosition(x, y, z) {
|
|
5903
|
+
posOut[0] = x;
|
|
5904
|
+
posOut[1] = y;
|
|
5905
|
+
posOut[2] = z;
|
|
4782
5906
|
for (let i = 0; i < childHandles.length; i++) {
|
|
4783
5907
|
const o = offsets[i];
|
|
4784
5908
|
childHandles[i].setPosition(x + o.px, y + o.py, z + o.pz);
|
|
4785
5909
|
}
|
|
4786
5910
|
},
|
|
4787
5911
|
setRotation(x, y, z) {
|
|
5912
|
+
rotOut[0] = x;
|
|
5913
|
+
rotOut[1] = y;
|
|
5914
|
+
rotOut[2] = z;
|
|
4788
5915
|
for (let i = 0; i < childHandles.length; i++) {
|
|
4789
5916
|
const o = offsets[i];
|
|
4790
5917
|
childHandles[i].setRotation(x + o.rx, y + o.ry, z + o.rz);
|
|
4791
5918
|
}
|
|
4792
5919
|
},
|
|
4793
5920
|
setScale(x, y, z) {
|
|
5921
|
+
sclOut[0] = x;
|
|
5922
|
+
sclOut[1] = y;
|
|
5923
|
+
sclOut[2] = z;
|
|
4794
5924
|
for (const h of childHandles)
|
|
4795
5925
|
h.setScale(x, y, z);
|
|
4796
5926
|
},
|
|
5927
|
+
teleport(x, y, z) {
|
|
5928
|
+
posOut[0] = x;
|
|
5929
|
+
posOut[1] = y;
|
|
5930
|
+
posOut[2] = z;
|
|
5931
|
+
for (let i = 0; i < childHandles.length; i++) {
|
|
5932
|
+
const o = offsets[i];
|
|
5933
|
+
childHandles[i].teleport(x + o.px, y + o.py, z + o.pz);
|
|
5934
|
+
}
|
|
5935
|
+
},
|
|
5936
|
+
get position() {
|
|
5937
|
+
return posOut;
|
|
5938
|
+
},
|
|
5939
|
+
get rotation() {
|
|
5940
|
+
return rotOut;
|
|
5941
|
+
},
|
|
5942
|
+
get scale() {
|
|
5943
|
+
return sclOut;
|
|
5944
|
+
},
|
|
4797
5945
|
destroy() {
|
|
4798
5946
|
for (const h of childHandles)
|
|
4799
5947
|
h.destroy();
|
|
4800
5948
|
}
|
|
4801
5949
|
};
|
|
4802
5950
|
}
|
|
4803
|
-
addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot) {
|
|
5951
|
+
addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot, prefabId = null) {
|
|
4804
5952
|
const slot = this.skinnedFreeList.allocate();
|
|
4805
5953
|
if (slot === -1)
|
|
4806
5954
|
throw new Error(`Max skinned instances (${this.maxSkinnedInstances}) reached`);
|
|
@@ -4869,10 +6017,16 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4869
6017
|
const capturedBoneOffset = boneOffset;
|
|
4870
6018
|
const capturedSkinIndex = skinIndex;
|
|
4871
6019
|
let destroyed = false;
|
|
4872
|
-
|
|
6020
|
+
const posOut = [0, 0, 0];
|
|
6021
|
+
const rotOut = [0, 0, 0];
|
|
6022
|
+
const sclOut = [0, 0, 0];
|
|
6023
|
+
const id = ++this.nextInstanceId;
|
|
6024
|
+
const handle = {
|
|
6025
|
+
id,
|
|
4873
6026
|
slot,
|
|
4874
6027
|
modelId: modelHandle.id,
|
|
4875
6028
|
skinned: true,
|
|
6029
|
+
prefabId,
|
|
4876
6030
|
setPosition(nx, ny, nz) {
|
|
4877
6031
|
dynamicData[dynBase + DYN_CURR_PX] = nx;
|
|
4878
6032
|
dynamicData[dynBase + DYN_CURR_PY] = ny;
|
|
@@ -4888,6 +6042,32 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4888
6042
|
staticData[statBase + SSTAT_SY] = ny;
|
|
4889
6043
|
staticData[statBase + SSTAT_SZ] = nz;
|
|
4890
6044
|
},
|
|
6045
|
+
teleport(nx, ny, nz) {
|
|
6046
|
+
dynamicData[dynBase + DYN_PREV_PX] = nx;
|
|
6047
|
+
dynamicData[dynBase + DYN_PREV_PY] = ny;
|
|
6048
|
+
dynamicData[dynBase + DYN_PREV_PZ] = nz;
|
|
6049
|
+
dynamicData[dynBase + DYN_CURR_PX] = nx;
|
|
6050
|
+
dynamicData[dynBase + DYN_CURR_PY] = ny;
|
|
6051
|
+
dynamicData[dynBase + DYN_CURR_PZ] = nz;
|
|
6052
|
+
},
|
|
6053
|
+
get position() {
|
|
6054
|
+
posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
|
|
6055
|
+
posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
|
|
6056
|
+
posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
|
|
6057
|
+
return posOut;
|
|
6058
|
+
},
|
|
6059
|
+
get rotation() {
|
|
6060
|
+
rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
|
|
6061
|
+
rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
|
|
6062
|
+
rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
|
|
6063
|
+
return rotOut;
|
|
6064
|
+
},
|
|
6065
|
+
get scale() {
|
|
6066
|
+
sclOut[0] = staticData[statBase + SSTAT_SX];
|
|
6067
|
+
sclOut[1] = staticData[statBase + SSTAT_SY];
|
|
6068
|
+
sclOut[2] = staticData[statBase + SSTAT_SZ];
|
|
6069
|
+
return sclOut;
|
|
6070
|
+
},
|
|
4891
6071
|
play(name, opts2) {
|
|
4892
6072
|
const state = animStates[slot];
|
|
4893
6073
|
if (state)
|
|
@@ -4907,6 +6087,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4907
6087
|
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4908
6088
|
staticData.fill(0, statBase, statBase + SKINNED_STATIC_MESH_FLOATS);
|
|
4909
6089
|
animStates[slot] = null;
|
|
6090
|
+
self.skinnedInstanceHandles[slot] = null;
|
|
4910
6091
|
self.skinnedStaticDirty = true;
|
|
4911
6092
|
if (--self.boneOffsetRefcount[capturedBoneOffset] === 0) {
|
|
4912
6093
|
let pool = self.freedBoneOffsets.get(capturedSkinIndex);
|
|
@@ -4918,6 +6099,8 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4918
6099
|
}
|
|
4919
6100
|
}
|
|
4920
6101
|
};
|
|
6102
|
+
this.skinnedInstanceHandles[slot] = handle;
|
|
6103
|
+
return handle;
|
|
4921
6104
|
}
|
|
4922
6105
|
/**
|
|
4923
6106
|
* Drain pending resyncs from the coordinator. Per affected skin: rebuild
|
|
@@ -5053,7 +6236,8 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
5053
6236
|
{ binding: 1, resource: { buffer: this.rawSkinnedDynamicBuffer } },
|
|
5054
6237
|
{ binding: 2, resource: { buffer: this.rawSkinnedStaticBuffer } },
|
|
5055
6238
|
{ binding: 3, resource: { buffer: this.rawSkinnedSlotIndexBuffer } },
|
|
5056
|
-
{ binding: 4, resource: { buffer: rawBoneBuffer } }
|
|
6239
|
+
{ binding: 4, resource: { buffer: rawBoneBuffer } },
|
|
6240
|
+
{ binding: 5, resource: { buffer: this.rawLightBuffer } }
|
|
5057
6241
|
]
|
|
5058
6242
|
});
|
|
5059
6243
|
this.device.queue.writeBuffer(
|
|
@@ -5189,6 +6373,113 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
5189
6373
|
sDyn[base + DYN_PREV_RZ] = sDyn[base + DYN_CURR_RZ];
|
|
5190
6374
|
}
|
|
5191
6375
|
});
|
|
6376
|
+
this.lights.storePrevious();
|
|
6377
|
+
}
|
|
6378
|
+
/** Resolve an instance's declared hitbox name to its Hitbox via the bucket's library. */
|
|
6379
|
+
resolveHitbox(handle) {
|
|
6380
|
+
if (!this._prefabs || !handle.prefabId)
|
|
6381
|
+
return null;
|
|
6382
|
+
const lib = this._prefabs.hitboxLibrary;
|
|
6383
|
+
if (!lib)
|
|
6384
|
+
return null;
|
|
6385
|
+
const prefab = this._prefabs.get(handle.prefabId);
|
|
6386
|
+
const name = prefab?.hitbox;
|
|
6387
|
+
return name ? lib.get(name) : null;
|
|
6388
|
+
}
|
|
6389
|
+
/**
|
|
6390
|
+
* Pick test for a single instance. Returns the ray-`t` and the struck
|
|
6391
|
+
* part name, or `null`. Uses the prefab's declared hitbox when
|
|
6392
|
+
* available; falls back to the model's axis-aligned bounding box.
|
|
6393
|
+
*/
|
|
6394
|
+
testInstanceRay(ray, handle, cx, cy, cz, sx, sy, sz, halfX, halfY, halfZ) {
|
|
6395
|
+
const hitbox = this.resolveHitbox(handle);
|
|
6396
|
+
if (hitbox) {
|
|
6397
|
+
const hit = testHitbox3D(ray, hitbox, cx, cy, cz, sx, sy, sz);
|
|
6398
|
+
return hit ? { distance: hit.distance, part: hit.part } : null;
|
|
6399
|
+
}
|
|
6400
|
+
const t = ray.entryBox(cx, cy, cz, halfX * sx, halfY * sy, halfZ * sz);
|
|
6401
|
+
return t === null ? null : { distance: t, part: null };
|
|
6402
|
+
}
|
|
6403
|
+
/** Push every instance the screen ray hits within [near, far] into the hit buffer. Unsorted. */
|
|
6404
|
+
_collectRaycastHitsInto(screenX, screenY, rc) {
|
|
6405
|
+
const ray = this.camera.screenToRay(screenX, screenY);
|
|
6406
|
+
const ox = ray.origin[0], oy = ray.origin[1], oz = ray.origin[2];
|
|
6407
|
+
const dx = ray.direction[0], dy = ray.direction[1], dz = ray.direction[2];
|
|
6408
|
+
const minDistance = this.camera.near;
|
|
6409
|
+
const maxDistance = this.camera.far;
|
|
6410
|
+
const dyn = this.dynamicData;
|
|
6411
|
+
const stat = this.staticData;
|
|
6412
|
+
const models = this.models;
|
|
6413
|
+
this.batcher.each((_, instances, count) => {
|
|
6414
|
+
for (let i = 0; i < count; i++) {
|
|
6415
|
+
const slot = instances[i];
|
|
6416
|
+
const handle = this.instanceHandles[slot];
|
|
6417
|
+
if (handle === null)
|
|
6418
|
+
continue;
|
|
6419
|
+
const model = models[handle.modelId];
|
|
6420
|
+
if (!model)
|
|
6421
|
+
continue;
|
|
6422
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6423
|
+
const statBase = slot * STATIC_MESH_FLOATS;
|
|
6424
|
+
const hit = this.testInstanceRay(
|
|
6425
|
+
ray,
|
|
6426
|
+
handle,
|
|
6427
|
+
dyn[dynBase + DYN_CURR_PX],
|
|
6428
|
+
dyn[dynBase + DYN_CURR_PY],
|
|
6429
|
+
dyn[dynBase + DYN_CURR_PZ],
|
|
6430
|
+
stat[statBase + STAT_SX],
|
|
6431
|
+
stat[statBase + STAT_SY],
|
|
6432
|
+
stat[statBase + STAT_SZ],
|
|
6433
|
+
model.halfX,
|
|
6434
|
+
model.halfY,
|
|
6435
|
+
model.halfZ
|
|
6436
|
+
);
|
|
6437
|
+
if (hit === null)
|
|
6438
|
+
continue;
|
|
6439
|
+
const t = hit.distance;
|
|
6440
|
+
if (t < minDistance || t > maxDistance)
|
|
6441
|
+
continue;
|
|
6442
|
+
rc.push(handle, t, ox + dx * t, oy + dy * t, oz + dz * t, t, hit.part);
|
|
6443
|
+
}
|
|
6444
|
+
});
|
|
6445
|
+
const sDyn = this.skinnedDynamicData;
|
|
6446
|
+
const sStat = this.skinnedStaticData;
|
|
6447
|
+
const skinned = this.skinnedModels;
|
|
6448
|
+
this.skinnedBatcher.each((_, instances, count) => {
|
|
6449
|
+
for (let i = 0; i < count; i++) {
|
|
6450
|
+
const slot = instances[i];
|
|
6451
|
+
const handle = this.skinnedInstanceHandles[slot];
|
|
6452
|
+
if (handle === null)
|
|
6453
|
+
continue;
|
|
6454
|
+
const model = models[handle.modelId];
|
|
6455
|
+
if (!model)
|
|
6456
|
+
continue;
|
|
6457
|
+
const skin = skinned[model.skinIndex];
|
|
6458
|
+
if (!skin)
|
|
6459
|
+
continue;
|
|
6460
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6461
|
+
const statBase = slot * SKINNED_STATIC_MESH_FLOATS;
|
|
6462
|
+
const hit = this.testInstanceRay(
|
|
6463
|
+
ray,
|
|
6464
|
+
handle,
|
|
6465
|
+
sDyn[dynBase + DYN_CURR_PX],
|
|
6466
|
+
sDyn[dynBase + DYN_CURR_PY],
|
|
6467
|
+
sDyn[dynBase + DYN_CURR_PZ],
|
|
6468
|
+
sStat[statBase + SSTAT_SX],
|
|
6469
|
+
sStat[statBase + SSTAT_SY],
|
|
6470
|
+
sStat[statBase + SSTAT_SZ],
|
|
6471
|
+
model.halfX,
|
|
6472
|
+
model.halfY,
|
|
6473
|
+
model.halfZ
|
|
6474
|
+
);
|
|
6475
|
+
if (hit === null)
|
|
6476
|
+
continue;
|
|
6477
|
+
const t = hit.distance;
|
|
6478
|
+
if (t < minDistance || t > maxDistance)
|
|
6479
|
+
continue;
|
|
6480
|
+
rc.push(handle, t, ox + dx * t, oy + dy * t, oz + dz * t, t, hit.part);
|
|
6481
|
+
}
|
|
6482
|
+
});
|
|
5192
6483
|
}
|
|
5193
6484
|
render(alpha) {
|
|
5194
6485
|
if (!this._initialized)
|
|
@@ -5217,18 +6508,26 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
5217
6508
|
);
|
|
5218
6509
|
this.staticDirty = false;
|
|
5219
6510
|
}
|
|
6511
|
+
const packed = this.lights.pack();
|
|
6512
|
+
if (packed.count > 0) {
|
|
6513
|
+
this.device.queue.writeBuffer(
|
|
6514
|
+
this.rawLightBuffer,
|
|
6515
|
+
0,
|
|
6516
|
+
packed.data.buffer,
|
|
6517
|
+
packed.data.byteOffset,
|
|
6518
|
+
packed.byteLength
|
|
6519
|
+
);
|
|
6520
|
+
}
|
|
5220
6521
|
const vpMatrix = this.camera.getViewProjectionMatrix();
|
|
5221
6522
|
this.uniformData.set(vpMatrix, 0);
|
|
5222
|
-
this.uniformData[
|
|
5223
|
-
this.uniformData
|
|
5224
|
-
this.uniformData[18] = 0.8;
|
|
5225
|
-
this.uniformData[19] = 0.5;
|
|
6523
|
+
this.uniformData[MESH_UNIFORM_ALPHA_OFFSET] = alpha;
|
|
6524
|
+
this.lights.writeUniforms(this.uniformData, MESH_UNIFORM_LIGHT_OFFSET, packed.count);
|
|
5226
6525
|
this.device.queue.writeBuffer(
|
|
5227
6526
|
this.rawUniformBuffer,
|
|
5228
6527
|
0,
|
|
5229
6528
|
this.uniformData.buffer,
|
|
5230
6529
|
this.uniformData.byteOffset,
|
|
5231
|
-
|
|
6530
|
+
this.uniformData.byteLength
|
|
5232
6531
|
);
|
|
5233
6532
|
this.extractFrustumPlanes(vpMatrix);
|
|
5234
6533
|
let indexOffset = 0;
|
|
@@ -5404,9 +6703,76 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
5404
6703
|
pass.draw(model.vertexCount, batch.count, 0, batch.offset);
|
|
5405
6704
|
}
|
|
5406
6705
|
}
|
|
6706
|
+
if (this.debug.hitboxes) {
|
|
6707
|
+
this.drawDebugHitboxes(pass, vpMatrix);
|
|
6708
|
+
}
|
|
5407
6709
|
pass.end();
|
|
5408
6710
|
this.device.queue.submit([encoder.finish()]);
|
|
5409
6711
|
}
|
|
6712
|
+
drawDebugHitboxes(pass, vp) {
|
|
6713
|
+
if (!this._prefabs)
|
|
6714
|
+
return;
|
|
6715
|
+
const debug = this.hitboxDebug;
|
|
6716
|
+
const state = this.raycast.state;
|
|
6717
|
+
debug.begin(vp);
|
|
6718
|
+
const dyn = this.dynamicData;
|
|
6719
|
+
const stat = this.staticData;
|
|
6720
|
+
this.batcher.each((_, instances, count) => {
|
|
6721
|
+
for (let i = 0; i < count; i++) {
|
|
6722
|
+
const slot = instances[i];
|
|
6723
|
+
const handle = this.instanceHandles[slot];
|
|
6724
|
+
if (handle === null)
|
|
6725
|
+
continue;
|
|
6726
|
+
const hb = this.resolveHitbox(handle);
|
|
6727
|
+
if (!hb)
|
|
6728
|
+
continue;
|
|
6729
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6730
|
+
const statBase = slot * STATIC_MESH_FLOATS;
|
|
6731
|
+
const hovered = state.containsId(handle.id);
|
|
6732
|
+
for (const part of hb.parts) {
|
|
6733
|
+
debug.emit(
|
|
6734
|
+
part,
|
|
6735
|
+
hovered,
|
|
6736
|
+
dyn[dynBase + DYN_CURR_PX],
|
|
6737
|
+
dyn[dynBase + DYN_CURR_PY],
|
|
6738
|
+
dyn[dynBase + DYN_CURR_PZ],
|
|
6739
|
+
stat[statBase + STAT_SX],
|
|
6740
|
+
stat[statBase + STAT_SY],
|
|
6741
|
+
stat[statBase + STAT_SZ]
|
|
6742
|
+
);
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
});
|
|
6746
|
+
const sDyn = this.skinnedDynamicData;
|
|
6747
|
+
const sStat = this.skinnedStaticData;
|
|
6748
|
+
this.skinnedBatcher.each((_, instances, count) => {
|
|
6749
|
+
for (let i = 0; i < count; i++) {
|
|
6750
|
+
const slot = instances[i];
|
|
6751
|
+
const handle = this.skinnedInstanceHandles[slot];
|
|
6752
|
+
if (handle === null)
|
|
6753
|
+
continue;
|
|
6754
|
+
const hb = this.resolveHitbox(handle);
|
|
6755
|
+
if (!hb)
|
|
6756
|
+
continue;
|
|
6757
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6758
|
+
const statBase = slot * SKINNED_STATIC_MESH_FLOATS;
|
|
6759
|
+
const hovered = state.containsId(handle.id);
|
|
6760
|
+
for (const part of hb.parts) {
|
|
6761
|
+
debug.emit(
|
|
6762
|
+
part,
|
|
6763
|
+
hovered,
|
|
6764
|
+
sDyn[dynBase + DYN_CURR_PX],
|
|
6765
|
+
sDyn[dynBase + DYN_CURR_PY],
|
|
6766
|
+
sDyn[dynBase + DYN_CURR_PZ],
|
|
6767
|
+
sStat[statBase + SSTAT_SX],
|
|
6768
|
+
sStat[statBase + SSTAT_SY],
|
|
6769
|
+
sStat[statBase + SSTAT_SZ]
|
|
6770
|
+
);
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
});
|
|
6774
|
+
debug.flush(pass);
|
|
6775
|
+
}
|
|
5410
6776
|
/**
|
|
5411
6777
|
* Extract 6 frustum planes from a column-major VP matrix.
|
|
5412
6778
|
* Each plane is [a, b, c, d] where ax + by + cz + d >= 0 means inside.
|
|
@@ -5587,12 +6953,12 @@ var ParticleEmitter = class {
|
|
|
5587
6953
|
this.renderer = renderer;
|
|
5588
6954
|
this.config = config;
|
|
5589
6955
|
this.rng = new SimpleRNG(config.seed);
|
|
5590
|
-
const
|
|
5591
|
-
this.sprites = new Array(
|
|
5592
|
-
this.lifetimes = new Float32Array(
|
|
5593
|
-
this.maxLifetimes = new Float32Array(
|
|
5594
|
-
this.velocitiesX = new Float32Array(
|
|
5595
|
-
this.velocitiesY = new Float32Array(
|
|
6956
|
+
const max3 = config.max;
|
|
6957
|
+
this.sprites = new Array(max3).fill(null);
|
|
6958
|
+
this.lifetimes = new Float32Array(max3);
|
|
6959
|
+
this.maxLifetimes = new Float32Array(max3);
|
|
6960
|
+
this.velocitiesX = new Float32Array(max3);
|
|
6961
|
+
this.velocitiesY = new Float32Array(max3);
|
|
5596
6962
|
}
|
|
5597
6963
|
emit(x, y, count = 1) {
|
|
5598
6964
|
for (let i = 0; i < count; i++) {
|
|
@@ -5668,61 +7034,6 @@ var ParticleEmitter = class {
|
|
|
5668
7034
|
this.head = 0;
|
|
5669
7035
|
}
|
|
5670
7036
|
};
|
|
5671
|
-
|
|
5672
|
-
// src/shaders/utils.ts
|
|
5673
|
-
import tgpu7 from "typegpu";
|
|
5674
|
-
import * as d6 from "typegpu/data";
|
|
5675
|
-
import * as std4 from "typegpu/std";
|
|
5676
|
-
var rotate2d = tgpu7.fn([d6.vec2f, d6.f32], d6.vec2f)(
|
|
5677
|
-
function rotate2d2(point, angle) {
|
|
5678
|
-
"use gpu";
|
|
5679
|
-
const c = std4.cos(angle);
|
|
5680
|
-
const s = std4.sin(angle);
|
|
5681
|
-
return d6.vec2f(
|
|
5682
|
-
point.x * c - point.y * s,
|
|
5683
|
-
point.x * s + point.y * c
|
|
5684
|
-
);
|
|
5685
|
-
}
|
|
5686
|
-
);
|
|
5687
|
-
var worldToClip2d = tgpu7.fn([d6.vec2f, d6.mat3x3f], d6.vec4f)(
|
|
5688
|
-
function worldToClip2d2(worldPos, cameraMatrix) {
|
|
5689
|
-
"use gpu";
|
|
5690
|
-
const clip = cameraMatrix * d6.vec3f(worldPos.x, worldPos.y, 1);
|
|
5691
|
-
return d6.vec4f(clip.x, clip.y, 0, 1);
|
|
5692
|
-
}
|
|
5693
|
-
);
|
|
5694
|
-
var worldToClip3d = tgpu7.fn([d6.vec3f, d6.mat4x4f], d6.vec4f)(
|
|
5695
|
-
function worldToClip3d2(worldPos, vpMatrix) {
|
|
5696
|
-
"use gpu";
|
|
5697
|
-
return vpMatrix * d6.vec4f(worldPos.x, worldPos.y, worldPos.z, 1);
|
|
5698
|
-
}
|
|
5699
|
-
);
|
|
5700
|
-
var remap = tgpu7.fn([d6.f32, d6.f32, d6.f32, d6.f32, d6.f32], d6.f32)(
|
|
5701
|
-
function remap2(value, inMin, inMax, outMin, outMax) {
|
|
5702
|
-
"use gpu";
|
|
5703
|
-
const t = (value - inMin) / (inMax - inMin);
|
|
5704
|
-
return outMin + t * (outMax - outMin);
|
|
5705
|
-
}
|
|
5706
|
-
);
|
|
5707
|
-
var scaleRotate2d = tgpu7.fn([d6.vec2f, d6.f32], d6.mat2x2f)(
|
|
5708
|
-
function scaleRotate2d2(scale, angle) {
|
|
5709
|
-
"use gpu";
|
|
5710
|
-
const c = std4.cos(angle);
|
|
5711
|
-
const s = std4.sin(angle);
|
|
5712
|
-
return d6.mat2x2f(
|
|
5713
|
-
scale.x * c,
|
|
5714
|
-
scale.x * s,
|
|
5715
|
-
-(scale.y * s),
|
|
5716
|
-
scale.y * c
|
|
5717
|
-
);
|
|
5718
|
-
}
|
|
5719
|
-
);
|
|
5720
|
-
var inverseLerp = tgpu7.fn([d6.f32, d6.f32, d6.f32], d6.f32)(
|
|
5721
|
-
function inverseLerp2(min, max2, value) {
|
|
5722
|
-
"use gpu";
|
|
5723
|
-
return std4.saturate((value - min) / (max2 - min));
|
|
5724
|
-
}
|
|
5725
|
-
);
|
|
5726
7037
|
export {
|
|
5727
7038
|
AnimationController,
|
|
5728
7039
|
Camera2D,
|