murow 0.1.1 → 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 +1 -1
- package/dist/cjs/core/clock/clock.js +1 -0
- package/dist/cjs/core/clock/index.js +1 -0
- 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/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/game/loop/loop.js +1 -1
- package/dist/cjs/game/loop/ticker-schedule.js +1 -0
- 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/clock/clock.js +1 -0
- package/dist/esm/core/clock/index.js +1 -0
- 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/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/game/loop/loop.js +1 -1
- package/dist/esm/game/loop/ticker-schedule.js +1 -0
- 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 +144 -140
- package/dist/netcode/esm/index.js +144 -140
- package/dist/netcode/types/client/game-client.d.ts +17 -3
- package/dist/netcode/types/client/strategies/snapshot-interpolation.d.ts +33 -0
- package/dist/netcode/types/codec/delta-codec.d.ts +1 -1
- package/dist/netcode/types/components/sync-spec.d.ts +6 -0
- package/dist/types/core/clock/clock.d.ts +37 -0
- package/dist/types/core/clock/index.d.ts +1 -0
- 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/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/game/loop/loop.d.ts +30 -0
- package/dist/types/game/loop/ticker-schedule.d.ts +52 -0
- 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 +1777 -587
- package/dist/webgpu/esm/index.js +1769 -573
- 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 +50 -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 +1 -1
- package/dist/netcode/types/client/interpolation-buffer.d.ts +0 -37
- /package/dist/netcode/types/client/{interpolation-buffer.test.d.ts → strategies/snapshot-interpolation.test.d.ts} +0 -0
package/dist/webgpu/cjs/index.js
CHANGED
|
@@ -128,8 +128,8 @@ var std = { ..._std };
|
|
|
128
128
|
// src/2d/renderer.ts
|
|
129
129
|
var import_typegpu6 = __toESM(require("typegpu"), 1);
|
|
130
130
|
var d4 = __toESM(require("typegpu/data"), 1);
|
|
131
|
-
var
|
|
132
|
-
var
|
|
131
|
+
var import_free_list = require("murow/core/free-list");
|
|
132
|
+
var import_renderer3 = require("murow/renderer");
|
|
133
133
|
|
|
134
134
|
// src/core/constants.ts
|
|
135
135
|
var DYNAMIC_OFFSET_PREV_X = 0;
|
|
@@ -283,8 +283,46 @@ var MeshUniforms = d2.struct({
|
|
|
283
283
|
alpha: d2.f32,
|
|
284
284
|
lightDirX: d2.f32,
|
|
285
285
|
lightDirY: d2.f32,
|
|
286
|
-
lightDirZ: d2.f32
|
|
286
|
+
lightDirZ: d2.f32,
|
|
287
|
+
lightDirR: d2.f32,
|
|
288
|
+
lightDirG: d2.f32,
|
|
289
|
+
lightDirB: d2.f32,
|
|
290
|
+
lightDirIntensity: d2.f32,
|
|
291
|
+
ambientR: d2.f32,
|
|
292
|
+
ambientG: d2.f32,
|
|
293
|
+
ambientB: d2.f32,
|
|
294
|
+
lightCount: d2.u32
|
|
295
|
+
});
|
|
296
|
+
var MESH_UNIFORM_ALPHA_OFFSET = 16;
|
|
297
|
+
var MESH_UNIFORM_LIGHT_OFFSET = 17;
|
|
298
|
+
var MESH_UNIFORM_FLOATS = 28;
|
|
299
|
+
var Light = d2.struct({
|
|
300
|
+
kind: d2.f32,
|
|
301
|
+
currPosX: d2.f32,
|
|
302
|
+
currPosY: d2.f32,
|
|
303
|
+
currPosZ: d2.f32,
|
|
304
|
+
prevPosX: d2.f32,
|
|
305
|
+
prevPosY: d2.f32,
|
|
306
|
+
prevPosZ: d2.f32,
|
|
307
|
+
currDirX: d2.f32,
|
|
308
|
+
currDirY: d2.f32,
|
|
309
|
+
currDirZ: d2.f32,
|
|
310
|
+
prevDirX: d2.f32,
|
|
311
|
+
prevDirY: d2.f32,
|
|
312
|
+
prevDirZ: d2.f32,
|
|
313
|
+
colorR: d2.f32,
|
|
314
|
+
colorG: d2.f32,
|
|
315
|
+
colorB: d2.f32,
|
|
316
|
+
intensity: d2.f32,
|
|
317
|
+
range: d2.f32,
|
|
318
|
+
innerCos: d2.f32,
|
|
319
|
+
outerCos: d2.f32,
|
|
320
|
+
castsShadow: d2.f32,
|
|
321
|
+
shadowMapIndex: d2.f32
|
|
287
322
|
});
|
|
323
|
+
var LIGHT_FLOATS = 22;
|
|
324
|
+
var LIGHT_KIND_POINT = 1;
|
|
325
|
+
var LIGHT_KIND_SPOT = 2;
|
|
288
326
|
var InstanceAnimStateGPU = d2.struct({
|
|
289
327
|
clipId: d2.i32,
|
|
290
328
|
time: d2.f32,
|
|
@@ -307,15 +345,19 @@ var import_sparse_batcher = require("murow/core/sparse-batcher");
|
|
|
307
345
|
|
|
308
346
|
// src/2d/sprite-accessor.ts
|
|
309
347
|
var SpriteAccessor = class {
|
|
310
|
-
constructor(dynamicData, staticData, slot, sheetId, onStaticDirty) {
|
|
348
|
+
constructor(dynamicData, staticData, id, slot, sheetId, onStaticDirty) {
|
|
311
349
|
this.dynamicData = dynamicData;
|
|
312
350
|
this.staticData = staticData;
|
|
351
|
+
this._id = id;
|
|
313
352
|
this._slot = slot;
|
|
314
353
|
this._sheetId = sheetId;
|
|
315
354
|
this.dynamicBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
|
|
316
355
|
this.staticBase = slot * STATIC_FLOATS_PER_SPRITE;
|
|
317
356
|
this._onStaticDirty = onStaticDirty;
|
|
318
357
|
}
|
|
358
|
+
get id() {
|
|
359
|
+
return this._id;
|
|
360
|
+
}
|
|
319
361
|
get slot() {
|
|
320
362
|
return this._slot;
|
|
321
363
|
}
|
|
@@ -439,6 +481,88 @@ var SpriteAccessor = class {
|
|
|
439
481
|
}
|
|
440
482
|
};
|
|
441
483
|
|
|
484
|
+
// src/2d/raycast.ts
|
|
485
|
+
var import_raycast = require("murow/core/raycast");
|
|
486
|
+
var import_renderer = require("murow/renderer");
|
|
487
|
+
var WebGPURaycast2D = class extends import_renderer.Raycast {
|
|
488
|
+
constructor(renderer) {
|
|
489
|
+
super();
|
|
490
|
+
this.renderer = renderer;
|
|
491
|
+
this.state = new import_raycast.HitBuffer(2);
|
|
492
|
+
this.resultBuffer = [];
|
|
493
|
+
this.memos = /* @__PURE__ */ new Set();
|
|
494
|
+
}
|
|
495
|
+
update(input) {
|
|
496
|
+
this.state.reset();
|
|
497
|
+
this.renderer._collectRaycastHitsInto(input.mouse.position.x, input.mouse.position.y, this.state);
|
|
498
|
+
for (const m of this.memos)
|
|
499
|
+
m._invalidate();
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Topmost sprite under the cursor, or null. The returned object is
|
|
503
|
+
* pool-backed and valid only until the next `update()` -- copy what
|
|
504
|
+
* you need, or use `memo` for results that persist across frames.
|
|
505
|
+
*/
|
|
506
|
+
hit(opts) {
|
|
507
|
+
return this.state.nearest(opts?.filter, opts?.maxDistance ?? Infinity);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Every sprite under the cursor, topmost first. The array and its
|
|
511
|
+
* entries are reused and overwritten by the next `update()`.
|
|
512
|
+
*/
|
|
513
|
+
hitAll(opts) {
|
|
514
|
+
this.state.collectInto(this.resultBuffer, opts?.filter, opts?.maxDistance ?? Infinity);
|
|
515
|
+
return this.resultBuffer;
|
|
516
|
+
}
|
|
517
|
+
memo(opts) {
|
|
518
|
+
const m = new WebGPURaycastMemo2D(this.state, opts, () => this.memos.delete(m));
|
|
519
|
+
this.memos.add(m);
|
|
520
|
+
return m;
|
|
521
|
+
}
|
|
522
|
+
clearMemos() {
|
|
523
|
+
for (const m of this.memos)
|
|
524
|
+
m._detach();
|
|
525
|
+
this.memos.clear();
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
var WebGPURaycastMemo2D = class extends import_renderer.RaycastMemo {
|
|
529
|
+
constructor(state, opts, onDispose) {
|
|
530
|
+
super();
|
|
531
|
+
this.state = state;
|
|
532
|
+
this.opts = opts;
|
|
533
|
+
this.onDispose = onDispose;
|
|
534
|
+
this.dirty = true;
|
|
535
|
+
this.detached = false;
|
|
536
|
+
this.cached = [];
|
|
537
|
+
}
|
|
538
|
+
get hits() {
|
|
539
|
+
if (this.detached)
|
|
540
|
+
return this.cached;
|
|
541
|
+
if (this.dirty) {
|
|
542
|
+
this.state.collectInto(this.cached, this.opts.filter, this.opts.maxDistance ?? Infinity);
|
|
543
|
+
this.dirty = false;
|
|
544
|
+
}
|
|
545
|
+
return this.cached;
|
|
546
|
+
}
|
|
547
|
+
get first() {
|
|
548
|
+
const arr = this.hits;
|
|
549
|
+
return arr.length > 0 ? arr[0] : null;
|
|
550
|
+
}
|
|
551
|
+
dispose() {
|
|
552
|
+
if (this.detached)
|
|
553
|
+
return;
|
|
554
|
+
this.onDispose();
|
|
555
|
+
this._detach();
|
|
556
|
+
}
|
|
557
|
+
_invalidate() {
|
|
558
|
+
this.dirty = true;
|
|
559
|
+
}
|
|
560
|
+
_detach() {
|
|
561
|
+
this.detached = true;
|
|
562
|
+
this.cached.length = 0;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
442
566
|
// src/camera/camera-2d.ts
|
|
443
567
|
var import_lerp = require("murow/core/lerp");
|
|
444
568
|
var Camera2D = class {
|
|
@@ -648,7 +772,7 @@ function createSpriteFragment(_spriteLayout, textureLayout) {
|
|
|
648
772
|
}
|
|
649
773
|
|
|
650
774
|
// src/spritesheet/spritesheet.ts
|
|
651
|
-
var
|
|
775
|
+
var import_renderer2 = require("murow/renderer");
|
|
652
776
|
var Spritesheet = class {
|
|
653
777
|
constructor(id, texture, textureView, sampler, uvs, width, height) {
|
|
654
778
|
this.id = id;
|
|
@@ -688,7 +812,7 @@ function createTextureFromBitmap(device, bitmap) {
|
|
|
688
812
|
}
|
|
689
813
|
|
|
690
814
|
// src/2d/renderer.ts
|
|
691
|
-
var
|
|
815
|
+
var import_renderer4 = require("murow/renderer");
|
|
692
816
|
|
|
693
817
|
// src/geometry/geometry-builder.ts
|
|
694
818
|
var import_typegpu2 = __toESM(require("typegpu"), 1);
|
|
@@ -1201,7 +1325,7 @@ function resolveBuiltInGeometry(name) {
|
|
|
1201
1325
|
}
|
|
1202
1326
|
|
|
1203
1327
|
// src/geometry/geometry-builder.ts
|
|
1204
|
-
var
|
|
1328
|
+
var import_slot_map = require("murow/core/slot-map");
|
|
1205
1329
|
|
|
1206
1330
|
// src/shaders/runtime-transpile.ts
|
|
1207
1331
|
var acorn = __toESM(require("acorn"), 1);
|
|
@@ -1379,7 +1503,6 @@ var CustomGeometry = class {
|
|
|
1379
1503
|
this._uniformDirty = true;
|
|
1380
1504
|
this._isComputeSourced = false;
|
|
1381
1505
|
this._drawCount = 0;
|
|
1382
|
-
this.activeCount = 0;
|
|
1383
1506
|
this.name = name;
|
|
1384
1507
|
this.root = root;
|
|
1385
1508
|
this.maxInstances = maxInstances;
|
|
@@ -1393,7 +1516,7 @@ var CustomGeometry = class {
|
|
|
1393
1516
|
this.staticFloatsPerInstance = 0;
|
|
1394
1517
|
for (const n of this.staticFieldNames)
|
|
1395
1518
|
this.staticFloatsPerInstance += getFieldFloats(layoutConfig.static[n]);
|
|
1396
|
-
this.
|
|
1519
|
+
this.slots = new import_slot_map.SlotMap(maxInstances);
|
|
1397
1520
|
this.dynamicData = new Float32Array(maxInstances * this.dynamicFloatsPerInstance);
|
|
1398
1521
|
this.staticData = new Float32Array(maxInstances * this.staticFloatsPerInstance);
|
|
1399
1522
|
this.uniformValues = { ...uniformValues };
|
|
@@ -1404,8 +1527,6 @@ var CustomGeometry = class {
|
|
|
1404
1527
|
this.dataBindGroup = dataBindGroup;
|
|
1405
1528
|
this.canvas = canvas;
|
|
1406
1529
|
this.clearColor = clearColor;
|
|
1407
|
-
this.activeSlots = new Uint32Array(maxInstances);
|
|
1408
|
-
this.slotToActive = new Int32Array(maxInstances).fill(-1);
|
|
1409
1530
|
this._ctx = new InstanceContext();
|
|
1410
1531
|
this._isComputeSourced = isComputeSourced;
|
|
1411
1532
|
if (isComputeSourced)
|
|
@@ -1419,34 +1540,20 @@ var CustomGeometry = class {
|
|
|
1419
1540
|
this._drawCount = count;
|
|
1420
1541
|
}
|
|
1421
1542
|
addInstance(data) {
|
|
1422
|
-
const slot = this.
|
|
1543
|
+
const slot = this.slots.add();
|
|
1423
1544
|
if (slot === -1)
|
|
1424
1545
|
throw new Error(`Max instances (${this.maxInstances}) reached for "${this.name}"`);
|
|
1425
1546
|
this.setInstanceData(slot, data);
|
|
1426
|
-
this.activeSlots[this.activeCount] = slot;
|
|
1427
|
-
this.slotToActive[slot] = this.activeCount;
|
|
1428
|
-
this.activeCount++;
|
|
1429
1547
|
return slot;
|
|
1430
1548
|
}
|
|
1431
1549
|
removeInstance(slot) {
|
|
1432
|
-
this.freeList.free(slot);
|
|
1433
1550
|
const dynBase = slot * this.dynamicFloatsPerInstance;
|
|
1434
1551
|
const statBase = slot * this.staticFloatsPerInstance;
|
|
1435
1552
|
this.dynamicData.fill(0, dynBase, dynBase + this.dynamicFloatsPerInstance);
|
|
1436
1553
|
this.staticData.fill(0, statBase, statBase + this.staticFloatsPerInstance);
|
|
1437
1554
|
this._dynamicDirty = true;
|
|
1438
1555
|
this._staticDirty = true;
|
|
1439
|
-
|
|
1440
|
-
if (activeIdx !== -1) {
|
|
1441
|
-
const lastIdx = this.activeCount - 1;
|
|
1442
|
-
if (activeIdx !== lastIdx) {
|
|
1443
|
-
const lastSlot = this.activeSlots[lastIdx];
|
|
1444
|
-
this.activeSlots[activeIdx] = lastSlot;
|
|
1445
|
-
this.slotToActive[lastSlot] = activeIdx;
|
|
1446
|
-
}
|
|
1447
|
-
this.slotToActive[slot] = -1;
|
|
1448
|
-
this.activeCount--;
|
|
1449
|
-
}
|
|
1556
|
+
this.slots.remove(slot);
|
|
1450
1557
|
}
|
|
1451
1558
|
setInstanceData(slot, data) {
|
|
1452
1559
|
const rawData = data;
|
|
@@ -1519,7 +1626,7 @@ var CustomGeometry = class {
|
|
|
1519
1626
|
this._uniformDirty = true;
|
|
1520
1627
|
}
|
|
1521
1628
|
getActiveCount() {
|
|
1522
|
-
return this.
|
|
1629
|
+
return this.slots.size;
|
|
1523
1630
|
}
|
|
1524
1631
|
updateAll(callback) {
|
|
1525
1632
|
this._ctx._bind(
|
|
@@ -1531,8 +1638,8 @@ var CustomGeometry = class {
|
|
|
1531
1638
|
this.staticFieldNames,
|
|
1532
1639
|
this.layoutConfig
|
|
1533
1640
|
);
|
|
1534
|
-
const count = this.
|
|
1535
|
-
const slots = this.activeSlots;
|
|
1641
|
+
const count = this.slots.size;
|
|
1642
|
+
const slots = this.slots.activeSlots;
|
|
1536
1643
|
for (let i = 0; i < count; i++) {
|
|
1537
1644
|
const slot = slots[i];
|
|
1538
1645
|
this._ctx._setSlot(slot);
|
|
@@ -1543,7 +1650,7 @@ var CustomGeometry = class {
|
|
|
1543
1650
|
}
|
|
1544
1651
|
render() {
|
|
1545
1652
|
const device = this.root.device;
|
|
1546
|
-
const count = this._isComputeSourced ? this._drawCount : this.
|
|
1653
|
+
const count = this._isComputeSourced ? this._drawCount : this.slots.size;
|
|
1547
1654
|
if (count === 0)
|
|
1548
1655
|
return;
|
|
1549
1656
|
const context = this.canvas.getContext("webgpu");
|
|
@@ -2168,6 +2275,7 @@ var ComputeBuilder = class {
|
|
|
2168
2275
|
};
|
|
2169
2276
|
|
|
2170
2277
|
// src/2d/renderer.ts
|
|
2278
|
+
var import_hitbox = require("murow/core/hitbox");
|
|
2171
2279
|
var prefab2DHandles = /* @__PURE__ */ new WeakMap();
|
|
2172
2280
|
function isPrefab2D(value) {
|
|
2173
2281
|
return value.type === "spritesheet";
|
|
@@ -2181,7 +2289,7 @@ function resolveSpritePrefabHandle(prefab) {
|
|
|
2181
2289
|
}
|
|
2182
2290
|
return h;
|
|
2183
2291
|
}
|
|
2184
|
-
var WebGPU2DRenderer = class extends
|
|
2292
|
+
var WebGPU2DRenderer = class extends import_renderer3.Base2DRenderer {
|
|
2185
2293
|
constructor(canvas, options) {
|
|
2186
2294
|
const resolvedMaxSprites = options.maxSprites ?? options.maxInstances ?? 1024;
|
|
2187
2295
|
super(canvas, { ...options, maxSprites: resolvedMaxSprites });
|
|
@@ -2191,15 +2299,19 @@ var WebGPU2DRenderer = class extends import_renderer2.Base2DRenderer {
|
|
|
2191
2299
|
this.sheets = /* @__PURE__ */ new Map();
|
|
2192
2300
|
this.nextSheetId = 0;
|
|
2193
2301
|
this.uniformData = new Float32Array(20);
|
|
2302
|
+
this.nextSpriteId = 0;
|
|
2194
2303
|
this.resizeObserver = null;
|
|
2195
2304
|
this.resizeCallbacks = [];
|
|
2196
2305
|
this._prefabs = options.prefabs ?? null;
|
|
2197
2306
|
this.camera = new Camera2D(canvas.width || 800, canvas.height || 600);
|
|
2198
|
-
this.
|
|
2307
|
+
this.raycast = new WebGPURaycast2D(this);
|
|
2308
|
+
this.freeList = new import_free_list.FreeList(resolvedMaxSprites);
|
|
2199
2309
|
this.batcher = new import_sparse_batcher.SparseBatcher(resolvedMaxSprites);
|
|
2200
2310
|
this.dynamicData = new Float32Array(resolvedMaxSprites * DYNAMIC_FLOATS_PER_SPRITE);
|
|
2201
2311
|
this.staticData = new Float32Array(resolvedMaxSprites * STATIC_FLOATS_PER_SPRITE);
|
|
2202
2312
|
this.slotIndexData = new Uint32Array(resolvedMaxSprites);
|
|
2313
|
+
this.spriteHandles = new Array(resolvedMaxSprites).fill(null);
|
|
2314
|
+
this.spriteHitboxes = new Array(resolvedMaxSprites).fill(null);
|
|
2203
2315
|
}
|
|
2204
2316
|
get device() {
|
|
2205
2317
|
return this._device;
|
|
@@ -2325,7 +2437,7 @@ var WebGPU2DRenderer = class extends import_renderer2.Base2DRenderer {
|
|
|
2325
2437
|
this.resizeCallbacks.push(callback);
|
|
2326
2438
|
}
|
|
2327
2439
|
async loadSpritesheet(source) {
|
|
2328
|
-
const parsed = await (0,
|
|
2440
|
+
const parsed = await (0, import_renderer4.parseSpritesheet)(source);
|
|
2329
2441
|
return this.uploadParsedSpritesheet(parsed);
|
|
2330
2442
|
}
|
|
2331
2443
|
/**
|
|
@@ -2359,6 +2471,9 @@ var WebGPU2DRenderer = class extends import_renderer2.Base2DRenderer {
|
|
|
2359
2471
|
const dynBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
|
|
2360
2472
|
const statBase = slot * STATIC_FLOATS_PER_SPRITE;
|
|
2361
2473
|
const sheet = isPrefab2D(opts.sheet) ? resolveSpritePrefabHandle(opts.sheet) : opts.sheet;
|
|
2474
|
+
const hitboxName = isPrefab2D(opts.sheet) ? opts.sheet.hitbox : void 0;
|
|
2475
|
+
const lib = this._prefabs?.hitboxLibrary ?? null;
|
|
2476
|
+
const hitbox = hitboxName && lib ? lib.get(hitboxName) : null;
|
|
2362
2477
|
const [px, py] = opts.position ?? [0, 0];
|
|
2363
2478
|
this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_X] = px;
|
|
2364
2479
|
this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_Y] = py;
|
|
@@ -2387,15 +2502,19 @@ var WebGPU2DRenderer = class extends import_renderer2.Base2DRenderer {
|
|
|
2387
2502
|
this.staticData[statBase + STATIC_OFFSET_TINT_A] = tint[3];
|
|
2388
2503
|
this.staticDirty = true;
|
|
2389
2504
|
this.batcher.add(opts.layer ?? 0, sheet.id, slot);
|
|
2390
|
-
|
|
2505
|
+
const accessor = new SpriteAccessor(
|
|
2391
2506
|
this.dynamicData,
|
|
2392
2507
|
this.staticData,
|
|
2508
|
+
this.nextSpriteId++,
|
|
2393
2509
|
slot,
|
|
2394
2510
|
sheet.id,
|
|
2395
2511
|
() => {
|
|
2396
2512
|
this.staticDirty = true;
|
|
2397
2513
|
}
|
|
2398
2514
|
);
|
|
2515
|
+
this.spriteHandles[slot] = accessor;
|
|
2516
|
+
this.spriteHitboxes[slot] = hitbox;
|
|
2517
|
+
return accessor;
|
|
2399
2518
|
}
|
|
2400
2519
|
removeSprite(sprite) {
|
|
2401
2520
|
const accessor = sprite;
|
|
@@ -2405,8 +2524,47 @@ var WebGPU2DRenderer = class extends import_renderer2.Base2DRenderer {
|
|
|
2405
2524
|
const statBase = accessor.slot * STATIC_FLOATS_PER_SPRITE;
|
|
2406
2525
|
this.dynamicData.fill(0, dynBase, dynBase + DYNAMIC_FLOATS_PER_SPRITE);
|
|
2407
2526
|
this.staticData.fill(0, statBase, statBase + STATIC_FLOATS_PER_SPRITE);
|
|
2527
|
+
this.spriteHandles[accessor.slot] = null;
|
|
2528
|
+
this.spriteHitboxes[accessor.slot] = void 0;
|
|
2408
2529
|
this.staticDirty = true;
|
|
2409
2530
|
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Point-test every sprite against the unprojected cursor and push the
|
|
2533
|
+
* hits into the buffer. Sort key is `-layer` so the topmost sprite is
|
|
2534
|
+
* "nearest". A declared hitbox overrides the default rendered quad.
|
|
2535
|
+
*/
|
|
2536
|
+
_collectRaycastHitsInto(screenX, screenY, rc) {
|
|
2537
|
+
const [wx, wy] = this.camera.screenToWorld(screenX, screenY);
|
|
2538
|
+
const dyn = this.dynamicData;
|
|
2539
|
+
const stat = this.staticData;
|
|
2540
|
+
this.batcher.each((_sheetId, instances, count) => {
|
|
2541
|
+
for (let i = 0; i < count; i++) {
|
|
2542
|
+
const slot = instances[i];
|
|
2543
|
+
const handle = this.spriteHandles[slot];
|
|
2544
|
+
if (handle === null)
|
|
2545
|
+
continue;
|
|
2546
|
+
const dynBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
|
|
2547
|
+
const statBase = slot * STATIC_FLOATS_PER_SPRITE;
|
|
2548
|
+
const cx = dyn[dynBase + DYNAMIC_OFFSET_CURR_X];
|
|
2549
|
+
const cy = dyn[dynBase + DYNAMIC_OFFSET_CURR_Y];
|
|
2550
|
+
const rot = dyn[dynBase + DYNAMIC_OFFSET_CURR_ROTATION];
|
|
2551
|
+
const sx = stat[statBase + STATIC_OFFSET_SCALE_X];
|
|
2552
|
+
const sy = stat[statBase + STATIC_OFFSET_SCALE_Y];
|
|
2553
|
+
const layer = stat[statBase + STATIC_OFFSET_LAYER];
|
|
2554
|
+
const hb = this.spriteHitboxes[slot];
|
|
2555
|
+
let part = null;
|
|
2556
|
+
if (hb) {
|
|
2557
|
+
const hit = (0, import_hitbox.testHitbox2D)(hb, cx, cy, sx, sy, rot, wx, wy);
|
|
2558
|
+
if (!hit)
|
|
2559
|
+
continue;
|
|
2560
|
+
part = hit.part;
|
|
2561
|
+
} else if (!(0, import_hitbox.pointInQuad2D)(cx, cy, sx, sy, rot, wx, wy)) {
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
rc.push(handle, -layer, wx, wy, 0, layer, part);
|
|
2565
|
+
}
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2410
2568
|
storePreviousState() {
|
|
2411
2569
|
this.camera.storePrevious();
|
|
2412
2570
|
const dyn = this.dynamicData;
|
|
@@ -2617,311 +2775,127 @@ var AnimationController = class {
|
|
|
2617
2775
|
};
|
|
2618
2776
|
|
|
2619
2777
|
// src/3d/renderer.ts
|
|
2620
|
-
var
|
|
2621
|
-
var
|
|
2622
|
-
var
|
|
2778
|
+
var import_typegpu10 = __toESM(require("typegpu"), 1);
|
|
2779
|
+
var import_renderer6 = require("murow/renderer");
|
|
2780
|
+
var import_free_list2 = require("murow/core/free-list");
|
|
2623
2781
|
var import_sparse_batcher2 = require("murow/core/sparse-batcher");
|
|
2624
2782
|
|
|
2625
|
-
// src/
|
|
2626
|
-
var
|
|
2627
|
-
var
|
|
2628
|
-
var
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
this._renderTarget = [0, 0, 0];
|
|
2644
|
-
this._viewMatrix = new Float32Array(16);
|
|
2645
|
-
this._projMatrix = new Float32Array(16);
|
|
2646
|
-
this._vpMatrix = new Float32Array(16);
|
|
2647
|
-
this._ray = new import_ray.Ray3D();
|
|
2648
|
-
this._width = 1;
|
|
2649
|
-
this._height = 1;
|
|
2650
|
-
}
|
|
2651
|
-
/**
|
|
2652
|
-
* Store current position/target as previous. Call before each tick.
|
|
2653
|
-
*/
|
|
2654
|
-
storePrevious() {
|
|
2655
|
-
this._prevPosition[0] = this.position[0];
|
|
2656
|
-
this._prevPosition[1] = this.position[1];
|
|
2657
|
-
this._prevPosition[2] = this.position[2];
|
|
2658
|
-
this._prevTarget[0] = this.target[0];
|
|
2659
|
-
this._prevTarget[1] = this.target[1];
|
|
2660
|
-
this._prevTarget[2] = this.target[2];
|
|
2661
|
-
}
|
|
2662
|
-
/**
|
|
2663
|
-
* Smoothly move the camera toward a target point.
|
|
2664
|
-
* Call each tick. The camera and its look-at target lerp toward the given position.
|
|
2665
|
-
* @param targetX World X to follow
|
|
2666
|
-
* @param targetY World Y to follow
|
|
2667
|
-
* @param targetZ World Z to follow
|
|
2668
|
-
* @param smoothing 0-1. 1 = snap instantly, 0.1 = lazy follow. Default 1.
|
|
2669
|
-
*/
|
|
2670
|
-
follow(targetX, targetY, targetZ, smoothing = 1) {
|
|
2671
|
-
const dx = targetX - this.target[0];
|
|
2672
|
-
const dy = targetY - this.target[1];
|
|
2673
|
-
const dz = targetZ - this.target[2];
|
|
2674
|
-
const mx = dx * smoothing;
|
|
2675
|
-
const my = dy * smoothing;
|
|
2676
|
-
const mz = dz * smoothing;
|
|
2677
|
-
this.target[0] += mx;
|
|
2678
|
-
this.target[1] += my;
|
|
2679
|
-
this.target[2] += mz;
|
|
2680
|
-
this.position[0] += mx;
|
|
2681
|
-
this.position[1] += my;
|
|
2682
|
-
this.position[2] += mz;
|
|
2683
|
-
}
|
|
2684
|
-
/**
|
|
2685
|
-
* Interpolate between previous and current state. Call before rendering.
|
|
2686
|
-
*/
|
|
2687
|
-
interpolate(alpha) {
|
|
2688
|
-
this._renderPosition[0] = (0, import_lerp2.lerp)(this._prevPosition[0], this.position[0], alpha);
|
|
2689
|
-
this._renderPosition[1] = (0, import_lerp2.lerp)(this._prevPosition[1], this.position[1], alpha);
|
|
2690
|
-
this._renderPosition[2] = (0, import_lerp2.lerp)(this._prevPosition[2], this.position[2], alpha);
|
|
2691
|
-
this._renderTarget[0] = (0, import_lerp2.lerp)(this._prevTarget[0], this.target[0], alpha);
|
|
2692
|
-
this._renderTarget[1] = (0, import_lerp2.lerp)(this._prevTarget[1], this.target[1], alpha);
|
|
2693
|
-
this._renderTarget[2] = (0, import_lerp2.lerp)(this._prevTarget[2], this.target[2], alpha);
|
|
2694
|
-
}
|
|
2695
|
-
/**
|
|
2696
|
-
* Build the view matrix (lookAt) using interpolated state.
|
|
2697
|
-
*/
|
|
2698
|
-
getViewMatrix() {
|
|
2699
|
-
lookAt(this._viewMatrix, this._renderPosition, this._renderTarget, this.up);
|
|
2700
|
-
return this._viewMatrix;
|
|
2701
|
-
}
|
|
2702
|
-
/**
|
|
2703
|
-
* Build the perspective projection matrix.
|
|
2704
|
-
*/
|
|
2705
|
-
getProjectionMatrix() {
|
|
2706
|
-
perspective(this._projMatrix, this.fov * (Math.PI / 180), this.aspect, this.near, this.far);
|
|
2707
|
-
return this._projMatrix;
|
|
2783
|
+
// src/3d/shader.ts
|
|
2784
|
+
var import_typegpu8 = __toESM(require("typegpu"), 1);
|
|
2785
|
+
var d6 = __toESM(require("typegpu/data"), 1);
|
|
2786
|
+
var std4 = __toESM(require("typegpu/std"), 1);
|
|
2787
|
+
|
|
2788
|
+
// src/shaders/utils.ts
|
|
2789
|
+
var import_typegpu7 = __toESM(require("typegpu"), 1);
|
|
2790
|
+
var d5 = __toESM(require("typegpu/data"), 1);
|
|
2791
|
+
var std3 = __toESM(require("typegpu/std"), 1);
|
|
2792
|
+
var rotate2d = import_typegpu7.default.fn([d5.vec2f, d5.f32], d5.vec2f)(
|
|
2793
|
+
function rotate2d2(point, angle) {
|
|
2794
|
+
"use gpu";
|
|
2795
|
+
const c = std3.cos(angle);
|
|
2796
|
+
const s = std3.sin(angle);
|
|
2797
|
+
return d5.vec2f(
|
|
2798
|
+
point.x * c - point.y * s,
|
|
2799
|
+
point.x * s + point.y * c
|
|
2800
|
+
);
|
|
2708
2801
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
mat4Multiply(this._vpMatrix, this._projMatrix, this._viewMatrix);
|
|
2716
|
-
return this._vpMatrix;
|
|
2802
|
+
);
|
|
2803
|
+
var worldToClip2d = import_typegpu7.default.fn([d5.vec2f, d5.mat3x3f], d5.vec4f)(
|
|
2804
|
+
function worldToClip2d2(worldPos, cameraMatrix) {
|
|
2805
|
+
"use gpu";
|
|
2806
|
+
const clip = cameraMatrix * d5.vec3f(worldPos.x, worldPos.y, 1);
|
|
2807
|
+
return d5.vec4f(clip.x, clip.y, 0, 1);
|
|
2717
2808
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2809
|
+
);
|
|
2810
|
+
var worldToClip3d = import_typegpu7.default.fn([d5.vec3f, d5.mat4x4f], d5.vec4f)(
|
|
2811
|
+
function worldToClip3d2(worldPos, vpMatrix) {
|
|
2812
|
+
"use gpu";
|
|
2813
|
+
return vpMatrix * d5.vec4f(worldPos.x, worldPos.y, worldPos.z, 1);
|
|
2722
2814
|
}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2815
|
+
);
|
|
2816
|
+
var remap = import_typegpu7.default.fn([d5.f32, d5.f32, d5.f32, d5.f32, d5.f32], d5.f32)(
|
|
2817
|
+
function remap2(value, inMin, inMax, outMin, outMax) {
|
|
2818
|
+
"use gpu";
|
|
2819
|
+
const t = (value - inMin) / (inMax - inMin);
|
|
2820
|
+
return outMin + t * (outMax - outMin);
|
|
2727
2821
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
const upX = m[1], upY = m[5], upZ = m[9];
|
|
2741
|
-
const fwdX = -m[2], fwdY = -m[6], fwdZ = -m[10];
|
|
2742
|
-
const dx = fwdX + rightX * ndcX * t * this.aspect + upX * ndcY * t;
|
|
2743
|
-
const dy = fwdY + rightY * ndcX * t * this.aspect + upY * ndcY * t;
|
|
2744
|
-
const dz = fwdZ + rightZ * ndcX * t * this.aspect + upZ * ndcY * t;
|
|
2745
|
-
this._ray.set(this.position[0], this.position[1], this.position[2], dx, dy, dz);
|
|
2746
|
-
return this._ray;
|
|
2822
|
+
);
|
|
2823
|
+
var scaleRotate2d = import_typegpu7.default.fn([d5.vec2f, d5.f32], d5.mat2x2f)(
|
|
2824
|
+
function scaleRotate2d2(scale, angle) {
|
|
2825
|
+
"use gpu";
|
|
2826
|
+
const c = std3.cos(angle);
|
|
2827
|
+
const s = std3.sin(angle);
|
|
2828
|
+
return d5.mat2x2f(
|
|
2829
|
+
scale.x * c,
|
|
2830
|
+
scale.x * s,
|
|
2831
|
+
-(scale.y * s),
|
|
2832
|
+
scale.y * c
|
|
2833
|
+
);
|
|
2747
2834
|
}
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2835
|
+
);
|
|
2836
|
+
var inverseLerp = import_typegpu7.default.fn([d5.f32, d5.f32, d5.f32], d5.f32)(
|
|
2837
|
+
function inverseLerp2(min, max3, value) {
|
|
2838
|
+
"use gpu";
|
|
2839
|
+
return std3.saturate((value - min) / (max3 - min));
|
|
2752
2840
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2841
|
+
);
|
|
2842
|
+
var lightContribution = import_typegpu7.default.fn(
|
|
2843
|
+
[d5.vec3f, d5.vec3f, d5.vec3f, d5.vec4f, d5.vec3f, d5.vec3f],
|
|
2844
|
+
d5.vec3f
|
|
2845
|
+
)(
|
|
2846
|
+
function lightContribution2(pos, axis, color, params, normal, worldPos) {
|
|
2847
|
+
"use gpu";
|
|
2848
|
+
const intensity = params.x;
|
|
2849
|
+
const range = params.y;
|
|
2850
|
+
const innerCos = params.z;
|
|
2851
|
+
const outerCos = params.w;
|
|
2852
|
+
const toLight = d5.vec3f(pos.x - worldPos.x, pos.y - worldPos.y, pos.z - worldPos.z);
|
|
2853
|
+
const dist = std3.length(toLight);
|
|
2854
|
+
const dir = std3.normalize(toLight);
|
|
2855
|
+
const lambert = std3.max(std3.dot(normal, dir), 0);
|
|
2856
|
+
const atten = std3.saturate(1 - dist / std3.max(range, 1e-4));
|
|
2857
|
+
const falloff = atten * atten;
|
|
2858
|
+
const axisLen = std3.length(axis);
|
|
2859
|
+
const isSpot = axisLen > 1e-4;
|
|
2860
|
+
const safeAxis = std3.select(d5.vec3f(0, 0, 1), axis, isSpot);
|
|
2861
|
+
const cosAngle = std3.dot(std3.normalize(safeAxis), d5.vec3f(-dir.x, -dir.y, -dir.z));
|
|
2862
|
+
const spotCone = std3.smoothstep(outerCos, innerCos, cosAngle);
|
|
2863
|
+
const cone = std3.select(1, spotCone, isSpot);
|
|
2864
|
+
const shadowFactor = 1;
|
|
2865
|
+
const scale = lambert * falloff * cone * intensity * shadowFactor;
|
|
2866
|
+
return d5.vec3f(color.x * scale, color.y * scale, color.z * scale);
|
|
2766
2867
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
const dz = m[8] * right + m[9] * up - m[10] * forward;
|
|
2773
|
-
this.position[0] += dx;
|
|
2774
|
-
this.position[1] += dy;
|
|
2775
|
-
this.position[2] += dz;
|
|
2776
|
-
this.target[0] += dx;
|
|
2777
|
-
this.target[1] += dy;
|
|
2778
|
-
this.target[2] += dz;
|
|
2868
|
+
);
|
|
2869
|
+
var tonemap = import_typegpu7.default.fn([d5.vec3f], d5.vec3f)(
|
|
2870
|
+
function tonemap2(c) {
|
|
2871
|
+
"use gpu";
|
|
2872
|
+
return d5.vec3f(c.x / (1 + c.x), c.y / (1 + c.y), c.z / (1 + c.z));
|
|
2779
2873
|
}
|
|
2780
|
-
|
|
2781
|
-
const yaw = Math.atan2(this.target[0] - this.position[0], this.target[2] - this.position[2]);
|
|
2782
|
-
const dx = Math.sin(yaw) * forward + Math.cos(yaw) * right;
|
|
2783
|
-
const dz = Math.cos(yaw) * forward - Math.sin(yaw) * right;
|
|
2784
|
-
this.position[0] += dx;
|
|
2785
|
-
this.position[1] += up;
|
|
2786
|
-
this.position[2] += dz;
|
|
2787
|
-
this.target[0] += dx;
|
|
2788
|
-
this.target[1] += up;
|
|
2789
|
-
this.target[2] += dz;
|
|
2790
|
-
}
|
|
2791
|
-
_moveGlobal(right, up, forward) {
|
|
2792
|
-
this.position[0] += right;
|
|
2793
|
-
this.position[1] += up;
|
|
2794
|
-
this.position[2] += forward;
|
|
2795
|
-
this.target[0] += right;
|
|
2796
|
-
this.target[1] += up;
|
|
2797
|
-
this.target[2] += forward;
|
|
2798
|
-
}
|
|
2799
|
-
/**
|
|
2800
|
-
* Orbit around the target point. Zero allocations.
|
|
2801
|
-
* @param yawDelta Horizontal rotation in radians (positive = rotate right)
|
|
2802
|
-
* @param pitchDelta Vertical rotation in radians (positive = rotate up)
|
|
2803
|
-
*/
|
|
2804
|
-
orbit(yawDelta, pitchDelta) {
|
|
2805
|
-
let ox = this.position[0] - this.target[0];
|
|
2806
|
-
let oy = this.position[1] - this.target[1];
|
|
2807
|
-
let oz = this.position[2] - this.target[2];
|
|
2808
|
-
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
2809
|
-
let yaw = Math.atan2(ox, oz);
|
|
2810
|
-
let pitch = Math.asin(oy / dist);
|
|
2811
|
-
yaw += yawDelta;
|
|
2812
|
-
pitch += pitchDelta;
|
|
2813
|
-
pitch = Math.max(-Math.PI * 0.49, Math.min(Math.PI * 0.49, pitch));
|
|
2814
|
-
this.position[0] = this.target[0] + Math.sin(yaw) * Math.cos(pitch) * dist;
|
|
2815
|
-
this.position[1] = this.target[1] + Math.sin(pitch) * dist;
|
|
2816
|
-
this.position[2] = this.target[2] + Math.cos(yaw) * Math.cos(pitch) * dist;
|
|
2817
|
-
}
|
|
2818
|
-
/**
|
|
2819
|
-
* Zoom by adjusting distance to target. Zero allocations.
|
|
2820
|
-
* @param delta Positive = zoom in, negative = zoom out
|
|
2821
|
-
*/
|
|
2822
|
-
zoom(delta) {
|
|
2823
|
-
let ox = this.position[0] - this.target[0];
|
|
2824
|
-
let oy = this.position[1] - this.target[1];
|
|
2825
|
-
let oz = this.position[2] - this.target[2];
|
|
2826
|
-
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
2827
|
-
const newDist = Math.max(0.1, dist - delta);
|
|
2828
|
-
const scale = newDist / dist;
|
|
2829
|
-
this.position[0] = this.target[0] + ox * scale;
|
|
2830
|
-
this.position[1] = this.target[1] + oy * scale;
|
|
2831
|
-
this.position[2] = this.target[2] + oz * scale;
|
|
2832
|
-
}
|
|
2833
|
-
};
|
|
2834
|
-
function lookAt(out, eye, center, up) {
|
|
2835
|
-
let fx = center[0] - eye[0];
|
|
2836
|
-
let fy = center[1] - eye[1];
|
|
2837
|
-
let fz = center[2] - eye[2];
|
|
2838
|
-
let len = 1 / Math.sqrt(fx * fx + fy * fy + fz * fz);
|
|
2839
|
-
fx *= len;
|
|
2840
|
-
fy *= len;
|
|
2841
|
-
fz *= len;
|
|
2842
|
-
let sx = fy * up[2] - fz * up[1];
|
|
2843
|
-
let sy = fz * up[0] - fx * up[2];
|
|
2844
|
-
let sz = fx * up[1] - fy * up[0];
|
|
2845
|
-
len = Math.sqrt(sx * sx + sy * sy + sz * sz);
|
|
2846
|
-
if (len > 0) {
|
|
2847
|
-
len = 1 / len;
|
|
2848
|
-
sx *= len;
|
|
2849
|
-
sy *= len;
|
|
2850
|
-
sz *= len;
|
|
2851
|
-
}
|
|
2852
|
-
const ux = sy * fz - sz * fy;
|
|
2853
|
-
const uy = sz * fx - sx * fz;
|
|
2854
|
-
const uz = sx * fy - sy * fx;
|
|
2855
|
-
out[0] = sx;
|
|
2856
|
-
out[1] = ux;
|
|
2857
|
-
out[2] = -fx;
|
|
2858
|
-
out[3] = 0;
|
|
2859
|
-
out[4] = sy;
|
|
2860
|
-
out[5] = uy;
|
|
2861
|
-
out[6] = -fy;
|
|
2862
|
-
out[7] = 0;
|
|
2863
|
-
out[8] = sz;
|
|
2864
|
-
out[9] = uz;
|
|
2865
|
-
out[10] = -fz;
|
|
2866
|
-
out[11] = 0;
|
|
2867
|
-
out[12] = -(sx * eye[0] + sy * eye[1] + sz * eye[2]);
|
|
2868
|
-
out[13] = -(ux * eye[0] + uy * eye[1] + uz * eye[2]);
|
|
2869
|
-
out[14] = fx * eye[0] + fy * eye[1] + fz * eye[2];
|
|
2870
|
-
out[15] = 1;
|
|
2871
|
-
}
|
|
2872
|
-
function perspective(out, fovRad, aspect, near, far) {
|
|
2873
|
-
const f = 1 / Math.tan(fovRad * 0.5);
|
|
2874
|
-
const rangeInv = 1 / (near - far);
|
|
2875
|
-
out[0] = f / aspect;
|
|
2876
|
-
out[1] = 0;
|
|
2877
|
-
out[2] = 0;
|
|
2878
|
-
out[3] = 0;
|
|
2879
|
-
out[4] = 0;
|
|
2880
|
-
out[5] = f;
|
|
2881
|
-
out[6] = 0;
|
|
2882
|
-
out[7] = 0;
|
|
2883
|
-
out[8] = 0;
|
|
2884
|
-
out[9] = 0;
|
|
2885
|
-
out[10] = (near + far) * rangeInv;
|
|
2886
|
-
out[11] = -1;
|
|
2887
|
-
out[12] = 0;
|
|
2888
|
-
out[13] = 0;
|
|
2889
|
-
out[14] = 2 * near * far * rangeInv;
|
|
2890
|
-
out[15] = 0;
|
|
2891
|
-
}
|
|
2892
|
-
function mat4Multiply(out, a, b) {
|
|
2893
|
-
for (let i = 0; i < 4; i++) {
|
|
2894
|
-
const ai0 = a[i], ai1 = a[i + 4], ai2 = a[i + 8], ai3 = a[i + 12];
|
|
2895
|
-
out[i] = ai0 * b[0] + ai1 * b[1] + ai2 * b[2] + ai3 * b[3];
|
|
2896
|
-
out[i + 4] = ai0 * b[4] + ai1 * b[5] + ai2 * b[6] + ai3 * b[7];
|
|
2897
|
-
out[i + 8] = ai0 * b[8] + ai1 * b[9] + ai2 * b[10] + ai3 * b[11];
|
|
2898
|
-
out[i + 12] = ai0 * b[12] + ai1 * b[13] + ai2 * b[14] + ai3 * b[15];
|
|
2899
|
-
}
|
|
2900
|
-
}
|
|
2874
|
+
);
|
|
2901
2875
|
|
|
2902
2876
|
// src/3d/shader.ts
|
|
2903
|
-
var
|
|
2904
|
-
var d5 = __toESM(require("typegpu/data"), 1);
|
|
2905
|
-
var std3 = __toESM(require("typegpu/std"), 1);
|
|
2877
|
+
var MAX_LIGHTS = 64;
|
|
2906
2878
|
function createMeshLayout(maxInstances) {
|
|
2907
|
-
return
|
|
2879
|
+
return import_typegpu8.default.bindGroupLayout({
|
|
2908
2880
|
uniforms: { uniform: MeshUniforms },
|
|
2909
|
-
dynamicInstances: { storage:
|
|
2910
|
-
staticInstances: { storage:
|
|
2911
|
-
slotIndices: { storage:
|
|
2881
|
+
dynamicInstances: { storage: d6.arrayOf(DynamicMesh, maxInstances) },
|
|
2882
|
+
staticInstances: { storage: d6.arrayOf(StaticMesh, maxInstances) },
|
|
2883
|
+
slotIndices: { storage: d6.arrayOf(d6.u32, maxInstances) },
|
|
2884
|
+
lights: { storage: d6.arrayOf(Light, MAX_LIGHTS) }
|
|
2912
2885
|
});
|
|
2913
2886
|
}
|
|
2914
2887
|
function createMeshVertex(meshLayout) {
|
|
2915
|
-
return
|
|
2888
|
+
return import_typegpu8.default.vertexFn({
|
|
2916
2889
|
in: {
|
|
2917
|
-
position:
|
|
2918
|
-
normal:
|
|
2919
|
-
instanceIndex:
|
|
2890
|
+
position: d6.location(0, d6.vec3f),
|
|
2891
|
+
normal: d6.location(1, d6.vec3f),
|
|
2892
|
+
instanceIndex: d6.builtin.instanceIndex
|
|
2920
2893
|
},
|
|
2921
2894
|
out: {
|
|
2922
|
-
pos:
|
|
2923
|
-
vNormal:
|
|
2924
|
-
vColor:
|
|
2895
|
+
pos: d6.builtin.position,
|
|
2896
|
+
vNormal: d6.vec3f,
|
|
2897
|
+
vColor: d6.vec3f,
|
|
2898
|
+
vWorldPos: d6.vec3f
|
|
2925
2899
|
}
|
|
2926
2900
|
})(function(input) {
|
|
2927
2901
|
const instanceIndex = input.instanceIndex;
|
|
@@ -2929,115 +2903,144 @@ function createMeshVertex(meshLayout) {
|
|
|
2929
2903
|
const dyn = meshLayout.$.dynamicInstances[slot];
|
|
2930
2904
|
const stat = meshLayout.$.staticInstances[slot];
|
|
2931
2905
|
const alpha = meshLayout.$.uniforms.alpha;
|
|
2932
|
-
const px =
|
|
2933
|
-
const py =
|
|
2934
|
-
const pz =
|
|
2935
|
-
const rx =
|
|
2936
|
-
const ry =
|
|
2937
|
-
const rz =
|
|
2906
|
+
const px = std4.mix(dyn.prevPosX, dyn.currPosX, alpha);
|
|
2907
|
+
const py = std4.mix(dyn.prevPosY, dyn.currPosY, alpha);
|
|
2908
|
+
const pz = std4.mix(dyn.prevPosZ, dyn.currPosZ, alpha);
|
|
2909
|
+
const rx = std4.mix(dyn.prevRotX, dyn.currRotX, alpha);
|
|
2910
|
+
const ry = std4.mix(dyn.prevRotY, dyn.currRotY, alpha);
|
|
2911
|
+
const rz = std4.mix(dyn.prevRotZ, dyn.currRotZ, alpha);
|
|
2938
2912
|
const sx = stat.scaleX;
|
|
2939
2913
|
const sy = stat.scaleY;
|
|
2940
2914
|
const sz = stat.scaleZ;
|
|
2941
|
-
const scaled =
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2915
|
+
const scaled = d6.vec3f(
|
|
2916
|
+
std4.mul(input.position.x, sx),
|
|
2917
|
+
std4.mul(input.position.y, sy),
|
|
2918
|
+
std4.mul(input.position.z, sz)
|
|
2945
2919
|
);
|
|
2946
|
-
const czr =
|
|
2947
|
-
const szr =
|
|
2948
|
-
const rz1 =
|
|
2949
|
-
|
|
2950
|
-
|
|
2920
|
+
const czr = std4.cos(rz);
|
|
2921
|
+
const szr = std4.sin(rz);
|
|
2922
|
+
const rz1 = d6.vec3f(
|
|
2923
|
+
std4.sub(std4.mul(scaled.x, czr), std4.mul(scaled.y, szr)),
|
|
2924
|
+
std4.add(std4.mul(scaled.x, szr), std4.mul(scaled.y, czr)),
|
|
2951
2925
|
scaled.z
|
|
2952
2926
|
);
|
|
2953
|
-
const cyr =
|
|
2954
|
-
const syr =
|
|
2955
|
-
const ry1 =
|
|
2956
|
-
|
|
2927
|
+
const cyr = std4.cos(ry);
|
|
2928
|
+
const syr = std4.sin(ry);
|
|
2929
|
+
const ry1 = d6.vec3f(
|
|
2930
|
+
std4.add(std4.mul(rz1.x, cyr), std4.mul(rz1.z, syr)),
|
|
2957
2931
|
rz1.y,
|
|
2958
|
-
|
|
2932
|
+
std4.sub(std4.mul(rz1.z, cyr), std4.mul(rz1.x, syr))
|
|
2959
2933
|
);
|
|
2960
|
-
const cxr =
|
|
2961
|
-
const sxr =
|
|
2962
|
-
const rx1 =
|
|
2934
|
+
const cxr = std4.cos(rx);
|
|
2935
|
+
const sxr = std4.sin(rx);
|
|
2936
|
+
const rx1 = d6.vec3f(
|
|
2963
2937
|
ry1.x,
|
|
2964
|
-
|
|
2965
|
-
|
|
2938
|
+
std4.sub(std4.mul(ry1.y, cxr), std4.mul(ry1.z, sxr)),
|
|
2939
|
+
std4.add(std4.mul(ry1.y, sxr), std4.mul(ry1.z, cxr))
|
|
2966
2940
|
);
|
|
2967
|
-
const worldPos =
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2941
|
+
const worldPos = d6.vec4f(
|
|
2942
|
+
std4.add(rx1.x, px),
|
|
2943
|
+
std4.add(rx1.y, py),
|
|
2944
|
+
std4.add(rx1.z, pz),
|
|
2971
2945
|
1
|
|
2972
2946
|
);
|
|
2973
2947
|
const nScaled = input.normal;
|
|
2974
|
-
const nRz =
|
|
2975
|
-
|
|
2976
|
-
|
|
2948
|
+
const nRz = d6.vec3f(
|
|
2949
|
+
std4.sub(std4.mul(nScaled.x, czr), std4.mul(nScaled.y, szr)),
|
|
2950
|
+
std4.add(std4.mul(nScaled.x, szr), std4.mul(nScaled.y, czr)),
|
|
2977
2951
|
nScaled.z
|
|
2978
2952
|
);
|
|
2979
|
-
const nRy =
|
|
2980
|
-
|
|
2953
|
+
const nRy = d6.vec3f(
|
|
2954
|
+
std4.add(std4.mul(nRz.x, cyr), std4.mul(nRz.z, syr)),
|
|
2981
2955
|
nRz.y,
|
|
2982
|
-
|
|
2956
|
+
std4.sub(std4.mul(nRz.z, cyr), std4.mul(nRz.x, syr))
|
|
2983
2957
|
);
|
|
2984
|
-
const nRx =
|
|
2958
|
+
const nRx = d6.vec3f(
|
|
2985
2959
|
nRy.x,
|
|
2986
|
-
|
|
2987
|
-
|
|
2960
|
+
std4.sub(std4.mul(nRy.y, cxr), std4.mul(nRy.z, sxr)),
|
|
2961
|
+
std4.add(std4.mul(nRy.y, sxr), std4.mul(nRy.z, cxr))
|
|
2988
2962
|
);
|
|
2989
|
-
const clipPos =
|
|
2963
|
+
const clipPos = std4.mul(meshLayout.$.uniforms.viewProjection, worldPos);
|
|
2990
2964
|
return {
|
|
2991
2965
|
pos: clipPos,
|
|
2992
2966
|
vNormal: nRx,
|
|
2993
|
-
vColor:
|
|
2967
|
+
vColor: d6.vec3f(stat.colorR, stat.colorG, stat.colorB),
|
|
2968
|
+
vWorldPos: d6.vec3f(worldPos.x, worldPos.y, worldPos.z)
|
|
2994
2969
|
};
|
|
2995
2970
|
});
|
|
2996
2971
|
}
|
|
2997
2972
|
function createMeshFragment(meshLayout) {
|
|
2998
|
-
return
|
|
2973
|
+
return import_typegpu8.default.fragmentFn({
|
|
2999
2974
|
in: {
|
|
3000
|
-
vNormal:
|
|
3001
|
-
vColor:
|
|
2975
|
+
vNormal: d6.vec3f,
|
|
2976
|
+
vColor: d6.vec3f,
|
|
2977
|
+
vWorldPos: d6.vec3f
|
|
3002
2978
|
},
|
|
3003
|
-
out:
|
|
2979
|
+
out: d6.vec4f
|
|
3004
2980
|
})(function(input) {
|
|
3005
|
-
const
|
|
3006
|
-
const
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
));
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
std3.add(ambient.x, diffuse.x),
|
|
3016
|
-
std3.add(ambient.y, diffuse.y),
|
|
3017
|
-
std3.add(ambient.z, diffuse.z),
|
|
3018
|
-
1
|
|
2981
|
+
const u = meshLayout.$.uniforms;
|
|
2982
|
+
const baseColor = input.vColor;
|
|
2983
|
+
const worldPos = input.vWorldPos;
|
|
2984
|
+
const normal = std4.normalize(input.vNormal);
|
|
2985
|
+
const lightDir = std4.normalize(d6.vec3f(u.lightDirX, u.lightDirY, u.lightDirZ));
|
|
2986
|
+
const diff = std4.max(std4.dot(normal, lightDir), 0) * u.lightDirIntensity;
|
|
2987
|
+
let acc = d6.vec3f(
|
|
2988
|
+
baseColor.x * (u.ambientR + u.lightDirR * diff),
|
|
2989
|
+
baseColor.y * (u.ambientG + u.lightDirG * diff),
|
|
2990
|
+
baseColor.z * (u.ambientB + u.lightDirB * diff)
|
|
3019
2991
|
);
|
|
2992
|
+
const count = u.lightCount;
|
|
2993
|
+
const a = u.alpha;
|
|
2994
|
+
for (let i = d6.u32(0); i < count; i++) {
|
|
2995
|
+
const L = meshLayout.$.lights[i];
|
|
2996
|
+
const pos = d6.vec3f(
|
|
2997
|
+
std4.mix(L.prevPosX, L.currPosX, a),
|
|
2998
|
+
std4.mix(L.prevPosY, L.currPosY, a),
|
|
2999
|
+
std4.mix(L.prevPosZ, L.currPosZ, a)
|
|
3000
|
+
);
|
|
3001
|
+
const axis = d6.vec3f(
|
|
3002
|
+
std4.mix(L.prevDirX, L.currDirX, a),
|
|
3003
|
+
std4.mix(L.prevDirY, L.currDirY, a),
|
|
3004
|
+
std4.mix(L.prevDirZ, L.currDirZ, a)
|
|
3005
|
+
);
|
|
3006
|
+
const c = lightContribution(
|
|
3007
|
+
pos,
|
|
3008
|
+
axis,
|
|
3009
|
+
d6.vec3f(L.colorR, L.colorG, L.colorB),
|
|
3010
|
+
d6.vec4f(L.intensity, L.range, L.innerCos, L.outerCos),
|
|
3011
|
+
normal,
|
|
3012
|
+
worldPos
|
|
3013
|
+
);
|
|
3014
|
+
acc = d6.vec3f(
|
|
3015
|
+
acc.x + baseColor.x * c.x,
|
|
3016
|
+
acc.y + baseColor.y * c.y,
|
|
3017
|
+
acc.z + baseColor.z * c.z
|
|
3018
|
+
);
|
|
3019
|
+
}
|
|
3020
|
+
const mapped = tonemap(acc);
|
|
3021
|
+
return d6.vec4f(mapped.x, mapped.y, mapped.z, 1);
|
|
3020
3022
|
});
|
|
3021
3023
|
}
|
|
3022
3024
|
function createTextureBindGroupLayout() {
|
|
3023
|
-
return
|
|
3025
|
+
return import_typegpu8.default.bindGroupLayout({
|
|
3024
3026
|
modelTexture: { texture: "float" },
|
|
3025
3027
|
modelSampler: { sampler: "filtering" }
|
|
3026
3028
|
});
|
|
3027
3029
|
}
|
|
3028
3030
|
function createTexturedMeshVertex(meshLayout) {
|
|
3029
|
-
return
|
|
3031
|
+
return import_typegpu8.default.vertexFn({
|
|
3030
3032
|
in: {
|
|
3031
|
-
position:
|
|
3032
|
-
normal:
|
|
3033
|
-
uv:
|
|
3034
|
-
instanceIndex:
|
|
3033
|
+
position: d6.location(0, d6.vec3f),
|
|
3034
|
+
normal: d6.location(1, d6.vec3f),
|
|
3035
|
+
uv: d6.location(2, d6.vec2f),
|
|
3036
|
+
instanceIndex: d6.builtin.instanceIndex
|
|
3035
3037
|
},
|
|
3036
3038
|
out: {
|
|
3037
|
-
pos:
|
|
3038
|
-
vNormal:
|
|
3039
|
-
vColor:
|
|
3040
|
-
vUV:
|
|
3039
|
+
pos: d6.builtin.position,
|
|
3040
|
+
vNormal: d6.vec3f,
|
|
3041
|
+
vColor: d6.vec3f,
|
|
3042
|
+
vUV: d6.vec2f,
|
|
3043
|
+
vWorldPos: d6.vec3f
|
|
3041
3044
|
}
|
|
3042
3045
|
})(function(input) {
|
|
3043
3046
|
const instanceIndex = input.instanceIndex;
|
|
@@ -3045,128 +3048,157 @@ function createTexturedMeshVertex(meshLayout) {
|
|
|
3045
3048
|
const dyn = meshLayout.$.dynamicInstances[slot];
|
|
3046
3049
|
const stat = meshLayout.$.staticInstances[slot];
|
|
3047
3050
|
const alpha = meshLayout.$.uniforms.alpha;
|
|
3048
|
-
const px =
|
|
3049
|
-
const py =
|
|
3050
|
-
const pz =
|
|
3051
|
-
const rx =
|
|
3052
|
-
const ry =
|
|
3053
|
-
const rz =
|
|
3051
|
+
const px = std4.mix(dyn.prevPosX, dyn.currPosX, alpha);
|
|
3052
|
+
const py = std4.mix(dyn.prevPosY, dyn.currPosY, alpha);
|
|
3053
|
+
const pz = std4.mix(dyn.prevPosZ, dyn.currPosZ, alpha);
|
|
3054
|
+
const rx = std4.mix(dyn.prevRotX, dyn.currRotX, alpha);
|
|
3055
|
+
const ry = std4.mix(dyn.prevRotY, dyn.currRotY, alpha);
|
|
3056
|
+
const rz = std4.mix(dyn.prevRotZ, dyn.currRotZ, alpha);
|
|
3054
3057
|
const sx = stat.scaleX;
|
|
3055
3058
|
const sy = stat.scaleY;
|
|
3056
3059
|
const sz = stat.scaleZ;
|
|
3057
|
-
const scaled =
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3060
|
+
const scaled = d6.vec3f(
|
|
3061
|
+
std4.mul(input.position.x, sx),
|
|
3062
|
+
std4.mul(input.position.y, sy),
|
|
3063
|
+
std4.mul(input.position.z, sz)
|
|
3061
3064
|
);
|
|
3062
|
-
const czr =
|
|
3063
|
-
const szr =
|
|
3064
|
-
const rz1 =
|
|
3065
|
-
|
|
3066
|
-
|
|
3065
|
+
const czr = std4.cos(rz);
|
|
3066
|
+
const szr = std4.sin(rz);
|
|
3067
|
+
const rz1 = d6.vec3f(
|
|
3068
|
+
std4.sub(std4.mul(scaled.x, czr), std4.mul(scaled.y, szr)),
|
|
3069
|
+
std4.add(std4.mul(scaled.x, szr), std4.mul(scaled.y, czr)),
|
|
3067
3070
|
scaled.z
|
|
3068
3071
|
);
|
|
3069
|
-
const cyr =
|
|
3070
|
-
const syr =
|
|
3071
|
-
const ry1 =
|
|
3072
|
-
|
|
3072
|
+
const cyr = std4.cos(ry);
|
|
3073
|
+
const syr = std4.sin(ry);
|
|
3074
|
+
const ry1 = d6.vec3f(
|
|
3075
|
+
std4.add(std4.mul(rz1.x, cyr), std4.mul(rz1.z, syr)),
|
|
3073
3076
|
rz1.y,
|
|
3074
|
-
|
|
3077
|
+
std4.sub(std4.mul(rz1.z, cyr), std4.mul(rz1.x, syr))
|
|
3075
3078
|
);
|
|
3076
|
-
const cxr =
|
|
3077
|
-
const sxr =
|
|
3078
|
-
const rx1 =
|
|
3079
|
+
const cxr = std4.cos(rx);
|
|
3080
|
+
const sxr = std4.sin(rx);
|
|
3081
|
+
const rx1 = d6.vec3f(
|
|
3079
3082
|
ry1.x,
|
|
3080
|
-
|
|
3081
|
-
|
|
3083
|
+
std4.sub(std4.mul(ry1.y, cxr), std4.mul(ry1.z, sxr)),
|
|
3084
|
+
std4.add(std4.mul(ry1.y, sxr), std4.mul(ry1.z, cxr))
|
|
3082
3085
|
);
|
|
3083
|
-
const worldPos =
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3086
|
+
const worldPos = d6.vec4f(
|
|
3087
|
+
std4.add(rx1.x, px),
|
|
3088
|
+
std4.add(rx1.y, py),
|
|
3089
|
+
std4.add(rx1.z, pz),
|
|
3087
3090
|
1
|
|
3088
3091
|
);
|
|
3089
3092
|
const nScaled = input.normal;
|
|
3090
|
-
const nRz =
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
+
const nRz = d6.vec3f(
|
|
3094
|
+
std4.sub(std4.mul(nScaled.x, czr), std4.mul(nScaled.y, szr)),
|
|
3095
|
+
std4.add(std4.mul(nScaled.x, szr), std4.mul(nScaled.y, czr)),
|
|
3093
3096
|
nScaled.z
|
|
3094
3097
|
);
|
|
3095
|
-
const nRy =
|
|
3096
|
-
|
|
3098
|
+
const nRy = d6.vec3f(
|
|
3099
|
+
std4.add(std4.mul(nRz.x, cyr), std4.mul(nRz.z, syr)),
|
|
3097
3100
|
nRz.y,
|
|
3098
|
-
|
|
3101
|
+
std4.sub(std4.mul(nRz.z, cyr), std4.mul(nRz.x, syr))
|
|
3099
3102
|
);
|
|
3100
|
-
const nRx =
|
|
3103
|
+
const nRx = d6.vec3f(
|
|
3101
3104
|
nRy.x,
|
|
3102
|
-
|
|
3103
|
-
|
|
3105
|
+
std4.sub(std4.mul(nRy.y, cxr), std4.mul(nRy.z, sxr)),
|
|
3106
|
+
std4.add(std4.mul(nRy.y, sxr), std4.mul(nRy.z, cxr))
|
|
3104
3107
|
);
|
|
3105
|
-
const clipPos =
|
|
3108
|
+
const clipPos = std4.mul(meshLayout.$.uniforms.viewProjection, worldPos);
|
|
3106
3109
|
return {
|
|
3107
3110
|
pos: clipPos,
|
|
3108
3111
|
vNormal: nRx,
|
|
3109
|
-
vColor:
|
|
3110
|
-
vUV: input.uv
|
|
3112
|
+
vColor: d6.vec3f(stat.colorR, stat.colorG, stat.colorB),
|
|
3113
|
+
vUV: input.uv,
|
|
3114
|
+
vWorldPos: d6.vec3f(worldPos.x, worldPos.y, worldPos.z)
|
|
3111
3115
|
};
|
|
3112
3116
|
});
|
|
3113
3117
|
}
|
|
3114
3118
|
function createTexturedMeshFragment(meshLayout, texLayout) {
|
|
3115
|
-
return
|
|
3119
|
+
return import_typegpu8.default.fragmentFn({
|
|
3116
3120
|
in: {
|
|
3117
|
-
vNormal:
|
|
3118
|
-
vColor:
|
|
3119
|
-
vUV:
|
|
3121
|
+
vNormal: d6.vec3f,
|
|
3122
|
+
vColor: d6.vec3f,
|
|
3123
|
+
vUV: d6.vec2f,
|
|
3124
|
+
vWorldPos: d6.vec3f
|
|
3120
3125
|
},
|
|
3121
|
-
out:
|
|
3126
|
+
out: d6.vec4f
|
|
3122
3127
|
})(function(input) {
|
|
3123
|
-
const
|
|
3124
|
-
const
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
std3.mul(texColor.x, input.vColor.x),
|
|
3132
|
-
std3.mul(texColor.y, input.vColor.y),
|
|
3133
|
-
std3.mul(texColor.z, input.vColor.z)
|
|
3128
|
+
const u = meshLayout.$.uniforms;
|
|
3129
|
+
const worldPos = input.vWorldPos;
|
|
3130
|
+
const normal = std4.normalize(input.vNormal);
|
|
3131
|
+
const texColor = std4.textureSample(texLayout.$.modelTexture, texLayout.$.modelSampler, input.vUV);
|
|
3132
|
+
const baseColor = d6.vec3f(
|
|
3133
|
+
std4.mul(texColor.x, input.vColor.x),
|
|
3134
|
+
std4.mul(texColor.y, input.vColor.y),
|
|
3135
|
+
std4.mul(texColor.z, input.vColor.z)
|
|
3134
3136
|
);
|
|
3135
|
-
const
|
|
3136
|
-
const
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
std3.add(ambient.z, diffuse.z),
|
|
3142
|
-
std3.mul(texColor.w, 1)
|
|
3137
|
+
const lightDir = std4.normalize(d6.vec3f(u.lightDirX, u.lightDirY, u.lightDirZ));
|
|
3138
|
+
const diff = std4.max(std4.dot(normal, lightDir), 0) * u.lightDirIntensity;
|
|
3139
|
+
let acc = d6.vec3f(
|
|
3140
|
+
baseColor.x * (u.ambientR + u.lightDirR * diff),
|
|
3141
|
+
baseColor.y * (u.ambientG + u.lightDirG * diff),
|
|
3142
|
+
baseColor.z * (u.ambientB + u.lightDirB * diff)
|
|
3143
3143
|
);
|
|
3144
|
+
const count = u.lightCount;
|
|
3145
|
+
const a = u.alpha;
|
|
3146
|
+
for (let i = d6.u32(0); i < count; i++) {
|
|
3147
|
+
const L = meshLayout.$.lights[i];
|
|
3148
|
+
const pos = d6.vec3f(
|
|
3149
|
+
std4.mix(L.prevPosX, L.currPosX, a),
|
|
3150
|
+
std4.mix(L.prevPosY, L.currPosY, a),
|
|
3151
|
+
std4.mix(L.prevPosZ, L.currPosZ, a)
|
|
3152
|
+
);
|
|
3153
|
+
const axis = d6.vec3f(
|
|
3154
|
+
std4.mix(L.prevDirX, L.currDirX, a),
|
|
3155
|
+
std4.mix(L.prevDirY, L.currDirY, a),
|
|
3156
|
+
std4.mix(L.prevDirZ, L.currDirZ, a)
|
|
3157
|
+
);
|
|
3158
|
+
const c = lightContribution(
|
|
3159
|
+
pos,
|
|
3160
|
+
axis,
|
|
3161
|
+
d6.vec3f(L.colorR, L.colorG, L.colorB),
|
|
3162
|
+
d6.vec4f(L.intensity, L.range, L.innerCos, L.outerCos),
|
|
3163
|
+
normal,
|
|
3164
|
+
worldPos
|
|
3165
|
+
);
|
|
3166
|
+
acc = d6.vec3f(
|
|
3167
|
+
acc.x + baseColor.x * c.x,
|
|
3168
|
+
acc.y + baseColor.y * c.y,
|
|
3169
|
+
acc.z + baseColor.z * c.z
|
|
3170
|
+
);
|
|
3171
|
+
}
|
|
3172
|
+
const mapped = tonemap(acc);
|
|
3173
|
+
return d6.vec4f(mapped.x, mapped.y, mapped.z, texColor.w);
|
|
3144
3174
|
});
|
|
3145
3175
|
}
|
|
3146
3176
|
function createSkinnedMeshLayout(maxInstances, maxBones) {
|
|
3147
|
-
return
|
|
3177
|
+
return import_typegpu8.default.bindGroupLayout({
|
|
3148
3178
|
uniforms: { uniform: MeshUniforms },
|
|
3149
|
-
dynamicInstances: { storage:
|
|
3150
|
-
staticInstances: { storage:
|
|
3151
|
-
slotIndices: { storage:
|
|
3152
|
-
boneMatrices: { storage:
|
|
3179
|
+
dynamicInstances: { storage: d6.arrayOf(DynamicMesh, maxInstances) },
|
|
3180
|
+
staticInstances: { storage: d6.arrayOf(SkinnedStaticMesh, maxInstances) },
|
|
3181
|
+
slotIndices: { storage: d6.arrayOf(d6.u32, maxInstances) },
|
|
3182
|
+
boneMatrices: { storage: d6.arrayOf(d6.mat4x4f, maxBones) },
|
|
3183
|
+
lights: { storage: d6.arrayOf(Light, MAX_LIGHTS) }
|
|
3153
3184
|
});
|
|
3154
3185
|
}
|
|
3155
3186
|
function createSkinnedMeshVertex(layout) {
|
|
3156
|
-
return
|
|
3187
|
+
return import_typegpu8.default.vertexFn({
|
|
3157
3188
|
in: {
|
|
3158
|
-
position:
|
|
3159
|
-
normal:
|
|
3160
|
-
uv:
|
|
3161
|
-
joints:
|
|
3162
|
-
weights:
|
|
3163
|
-
instanceIndex:
|
|
3189
|
+
position: d6.location(0, d6.vec3f),
|
|
3190
|
+
normal: d6.location(1, d6.vec3f),
|
|
3191
|
+
uv: d6.location(2, d6.vec2f),
|
|
3192
|
+
joints: d6.location(3, d6.vec4u),
|
|
3193
|
+
weights: d6.location(4, d6.vec4f),
|
|
3194
|
+
instanceIndex: d6.builtin.instanceIndex
|
|
3164
3195
|
},
|
|
3165
3196
|
out: {
|
|
3166
|
-
pos:
|
|
3167
|
-
vNormal:
|
|
3168
|
-
vColor:
|
|
3169
|
-
vUV:
|
|
3197
|
+
pos: d6.builtin.position,
|
|
3198
|
+
vNormal: d6.vec3f,
|
|
3199
|
+
vColor: d6.vec3f,
|
|
3200
|
+
vUV: d6.vec2f,
|
|
3201
|
+
vWorldPos: d6.vec3f
|
|
3170
3202
|
}
|
|
3171
3203
|
})(function(input) {
|
|
3172
3204
|
const slot = layout.$.slotIndices[input.instanceIndex];
|
|
@@ -3186,69 +3218,69 @@ function createSkinnedMeshVertex(layout) {
|
|
|
3186
3218
|
const m1 = layout.$.boneMatrices[boneOffset + j1];
|
|
3187
3219
|
const m2 = layout.$.boneMatrices[boneOffset + j2];
|
|
3188
3220
|
const m3 = layout.$.boneMatrices[boneOffset + j3];
|
|
3189
|
-
const p =
|
|
3221
|
+
const p = d6.vec4f(input.position.x, input.position.y, input.position.z, 1);
|
|
3190
3222
|
const sp0 = m0 * p;
|
|
3191
3223
|
const sp1 = m1 * p;
|
|
3192
3224
|
const sp2 = m2 * p;
|
|
3193
3225
|
const sp3 = m3 * p;
|
|
3194
|
-
const skinnedPos =
|
|
3226
|
+
const skinnedPos = d6.vec3f(
|
|
3195
3227
|
sp0.x * w0 + sp1.x * w1 + sp2.x * w2 + sp3.x * w3,
|
|
3196
3228
|
sp0.y * w0 + sp1.y * w1 + sp2.y * w2 + sp3.y * w3,
|
|
3197
3229
|
sp0.z * w0 + sp1.z * w1 + sp2.z * w2 + sp3.z * w3
|
|
3198
3230
|
);
|
|
3199
|
-
const n =
|
|
3231
|
+
const n = d6.vec4f(input.normal.x, input.normal.y, input.normal.z, 0);
|
|
3200
3232
|
const sn0 = m0 * n;
|
|
3201
3233
|
const sn1 = m1 * n;
|
|
3202
3234
|
const sn2 = m2 * n;
|
|
3203
3235
|
const sn3 = m3 * n;
|
|
3204
|
-
const skinnedNormal =
|
|
3236
|
+
const skinnedNormal = d6.vec3f(
|
|
3205
3237
|
sn0.x * w0 + sn1.x * w1 + sn2.x * w2 + sn3.x * w3,
|
|
3206
3238
|
sn0.y * w0 + sn1.y * w1 + sn2.y * w2 + sn3.y * w3,
|
|
3207
3239
|
sn0.z * w0 + sn1.z * w1 + sn2.z * w2 + sn3.z * w3
|
|
3208
3240
|
);
|
|
3209
|
-
const px =
|
|
3210
|
-
const py =
|
|
3211
|
-
const pz =
|
|
3212
|
-
const rx =
|
|
3213
|
-
const ry =
|
|
3214
|
-
const rz =
|
|
3241
|
+
const px = std4.mix(dyn.prevPosX, dyn.currPosX, alpha);
|
|
3242
|
+
const py = std4.mix(dyn.prevPosY, dyn.currPosY, alpha);
|
|
3243
|
+
const pz = std4.mix(dyn.prevPosZ, dyn.currPosZ, alpha);
|
|
3244
|
+
const rx = std4.mix(dyn.prevRotX, dyn.currRotX, alpha);
|
|
3245
|
+
const ry = std4.mix(dyn.prevRotY, dyn.currRotY, alpha);
|
|
3246
|
+
const rz = std4.mix(dyn.prevRotZ, dyn.currRotZ, alpha);
|
|
3215
3247
|
const sx = stat.scaleX;
|
|
3216
3248
|
const sy = stat.scaleY;
|
|
3217
3249
|
const sz = stat.scaleZ;
|
|
3218
|
-
const scaled =
|
|
3219
|
-
const czr =
|
|
3220
|
-
const szr =
|
|
3221
|
-
const rz1 =
|
|
3250
|
+
const scaled = d6.vec3f(skinnedPos.x * sx, skinnedPos.y * sy, skinnedPos.z * sz);
|
|
3251
|
+
const czr = std4.cos(rz);
|
|
3252
|
+
const szr = std4.sin(rz);
|
|
3253
|
+
const rz1 = d6.vec3f(
|
|
3222
3254
|
scaled.x * czr - scaled.y * szr,
|
|
3223
3255
|
scaled.x * szr + scaled.y * czr,
|
|
3224
3256
|
scaled.z
|
|
3225
3257
|
);
|
|
3226
|
-
const cyr =
|
|
3227
|
-
const syr =
|
|
3228
|
-
const ry1 =
|
|
3258
|
+
const cyr = std4.cos(ry);
|
|
3259
|
+
const syr = std4.sin(ry);
|
|
3260
|
+
const ry1 = d6.vec3f(
|
|
3229
3261
|
rz1.x * cyr + rz1.z * syr,
|
|
3230
3262
|
rz1.y,
|
|
3231
3263
|
rz1.z * cyr - rz1.x * syr
|
|
3232
3264
|
);
|
|
3233
|
-
const cxr =
|
|
3234
|
-
const sxr =
|
|
3235
|
-
const rx1 =
|
|
3265
|
+
const cxr = std4.cos(rx);
|
|
3266
|
+
const sxr = std4.sin(rx);
|
|
3267
|
+
const rx1 = d6.vec3f(
|
|
3236
3268
|
ry1.x,
|
|
3237
3269
|
ry1.y * cxr - ry1.z * sxr,
|
|
3238
3270
|
ry1.y * sxr + ry1.z * cxr
|
|
3239
3271
|
);
|
|
3240
|
-
const worldPos =
|
|
3241
|
-
const nRz =
|
|
3272
|
+
const worldPos = d6.vec4f(rx1.x + px, rx1.y + py, rx1.z + pz, 1);
|
|
3273
|
+
const nRz = d6.vec3f(
|
|
3242
3274
|
skinnedNormal.x * czr - skinnedNormal.y * szr,
|
|
3243
3275
|
skinnedNormal.x * szr + skinnedNormal.y * czr,
|
|
3244
3276
|
skinnedNormal.z
|
|
3245
3277
|
);
|
|
3246
|
-
const nRy =
|
|
3278
|
+
const nRy = d6.vec3f(
|
|
3247
3279
|
nRz.x * cyr + nRz.z * syr,
|
|
3248
3280
|
nRz.y,
|
|
3249
3281
|
nRz.z * cyr - nRz.x * syr
|
|
3250
3282
|
);
|
|
3251
|
-
const nRx =
|
|
3283
|
+
const nRx = d6.vec3f(
|
|
3252
3284
|
nRy.x,
|
|
3253
3285
|
nRy.y * cxr - nRy.z * sxr,
|
|
3254
3286
|
nRy.y * sxr + nRy.z * cxr
|
|
@@ -3257,14 +3289,629 @@ function createSkinnedMeshVertex(layout) {
|
|
|
3257
3289
|
return {
|
|
3258
3290
|
pos: clipPos,
|
|
3259
3291
|
vNormal: nRx,
|
|
3260
|
-
vColor:
|
|
3261
|
-
vUV: input.uv
|
|
3292
|
+
vColor: d6.vec3f(stat.colorR, stat.colorG, stat.colorB),
|
|
3293
|
+
vUV: input.uv,
|
|
3294
|
+
vWorldPos: d6.vec3f(worldPos.x, worldPos.y, worldPos.z)
|
|
3262
3295
|
};
|
|
3263
3296
|
});
|
|
3264
3297
|
}
|
|
3298
|
+
function createSkinnedMeshFragment(meshLayout) {
|
|
3299
|
+
return import_typegpu8.default.fragmentFn({
|
|
3300
|
+
in: {
|
|
3301
|
+
vNormal: d6.vec3f,
|
|
3302
|
+
vColor: d6.vec3f,
|
|
3303
|
+
vUV: d6.vec2f,
|
|
3304
|
+
vWorldPos: d6.vec3f
|
|
3305
|
+
},
|
|
3306
|
+
out: d6.vec4f
|
|
3307
|
+
})(function(input) {
|
|
3308
|
+
const u = meshLayout.$.uniforms;
|
|
3309
|
+
const baseColor = input.vColor;
|
|
3310
|
+
const worldPos = input.vWorldPos;
|
|
3311
|
+
const normal = std4.normalize(input.vNormal);
|
|
3312
|
+
const lightDir = std4.normalize(d6.vec3f(u.lightDirX, u.lightDirY, u.lightDirZ));
|
|
3313
|
+
const diff = std4.max(std4.dot(normal, lightDir), 0) * u.lightDirIntensity;
|
|
3314
|
+
let acc = d6.vec3f(
|
|
3315
|
+
baseColor.x * (u.ambientR + u.lightDirR * diff),
|
|
3316
|
+
baseColor.y * (u.ambientG + u.lightDirG * diff),
|
|
3317
|
+
baseColor.z * (u.ambientB + u.lightDirB * diff)
|
|
3318
|
+
);
|
|
3319
|
+
const count = u.lightCount;
|
|
3320
|
+
const a = u.alpha;
|
|
3321
|
+
for (let i = d6.u32(0); i < count; i++) {
|
|
3322
|
+
const L = meshLayout.$.lights[i];
|
|
3323
|
+
const pos = d6.vec3f(
|
|
3324
|
+
std4.mix(L.prevPosX, L.currPosX, a),
|
|
3325
|
+
std4.mix(L.prevPosY, L.currPosY, a),
|
|
3326
|
+
std4.mix(L.prevPosZ, L.currPosZ, a)
|
|
3327
|
+
);
|
|
3328
|
+
const axis = d6.vec3f(
|
|
3329
|
+
std4.mix(L.prevDirX, L.currDirX, a),
|
|
3330
|
+
std4.mix(L.prevDirY, L.currDirY, a),
|
|
3331
|
+
std4.mix(L.prevDirZ, L.currDirZ, a)
|
|
3332
|
+
);
|
|
3333
|
+
const c = lightContribution(
|
|
3334
|
+
pos,
|
|
3335
|
+
axis,
|
|
3336
|
+
d6.vec3f(L.colorR, L.colorG, L.colorB),
|
|
3337
|
+
d6.vec4f(L.intensity, L.range, L.innerCos, L.outerCos),
|
|
3338
|
+
normal,
|
|
3339
|
+
worldPos
|
|
3340
|
+
);
|
|
3341
|
+
acc = d6.vec3f(
|
|
3342
|
+
acc.x + baseColor.x * c.x,
|
|
3343
|
+
acc.y + baseColor.y * c.y,
|
|
3344
|
+
acc.z + baseColor.z * c.z
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
const mapped = tonemap(acc);
|
|
3348
|
+
return d6.vec4f(mapped.x, mapped.y, mapped.z, 1);
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
// src/3d/lights.ts
|
|
3353
|
+
var import_slot_map2 = require("murow/core/slot-map");
|
|
3354
|
+
var KIND = 0;
|
|
3355
|
+
var CURR_POS_X = 1;
|
|
3356
|
+
var CURR_POS_Y = 2;
|
|
3357
|
+
var CURR_POS_Z = 3;
|
|
3358
|
+
var PREV_POS_X = 4;
|
|
3359
|
+
var PREV_POS_Y = 5;
|
|
3360
|
+
var PREV_POS_Z = 6;
|
|
3361
|
+
var CURR_DIR_X = 7;
|
|
3362
|
+
var CURR_DIR_Y = 8;
|
|
3363
|
+
var CURR_DIR_Z = 9;
|
|
3364
|
+
var PREV_DIR_X = 10;
|
|
3365
|
+
var PREV_DIR_Y = 11;
|
|
3366
|
+
var PREV_DIR_Z = 12;
|
|
3367
|
+
var COL_R = 13;
|
|
3368
|
+
var COL_G = 14;
|
|
3369
|
+
var COL_B = 15;
|
|
3370
|
+
var INTENSITY = 16;
|
|
3371
|
+
var RANGE = 17;
|
|
3372
|
+
var INNER_COS = 18;
|
|
3373
|
+
var OUTER_COS = 19;
|
|
3374
|
+
var CASTS_SHADOW = 20;
|
|
3375
|
+
var SHADOW_INDEX = 21;
|
|
3376
|
+
var LightSystem = class {
|
|
3377
|
+
constructor(maxLights) {
|
|
3378
|
+
this.maxLights = maxLights;
|
|
3379
|
+
// Global directional + ambient terms (the classic fixed look; now configurable).
|
|
3380
|
+
this.dirDir = [0.3, 0.8, 0.5];
|
|
3381
|
+
this.dirColor = [1, 1, 1];
|
|
3382
|
+
this.dirIntensity = 1;
|
|
3383
|
+
this.ambient = [0.3, 0.3, 0.3];
|
|
3384
|
+
this.data = new Float32Array(maxLights * LIGHT_FLOATS);
|
|
3385
|
+
this.slots = new import_slot_map2.SlotMap(maxLights);
|
|
3386
|
+
this.enabled = new Uint8Array(maxLights).fill(1);
|
|
3387
|
+
this.handles = new Array(maxLights).fill(null);
|
|
3388
|
+
this.uploadData = new Float32Array(maxLights * LIGHT_FLOATS);
|
|
3389
|
+
this.angle = new Float32Array(maxLights);
|
|
3390
|
+
this.smoothness = new Float32Array(maxLights);
|
|
3391
|
+
}
|
|
3392
|
+
/** Add a dynamic point or spot light. Throws past `maxLights`. */
|
|
3393
|
+
add(spec) {
|
|
3394
|
+
const slot = this.slots.add();
|
|
3395
|
+
if (slot === -1)
|
|
3396
|
+
throw new Error(`Max lights (${this.maxLights}) reached`);
|
|
3397
|
+
this.enabled[slot] = 1;
|
|
3398
|
+
this.writeSlot(slot, spec);
|
|
3399
|
+
const data = this.data;
|
|
3400
|
+
const enabledArr = this.enabled;
|
|
3401
|
+
const slots = this.slots;
|
|
3402
|
+
const handles = this.handles;
|
|
3403
|
+
const angleArr = this.angle;
|
|
3404
|
+
const smoothArr = this.smoothness;
|
|
3405
|
+
const base = slot * LIGHT_FLOATS;
|
|
3406
|
+
let destroyed = false;
|
|
3407
|
+
const posOut = [0, 0, 0];
|
|
3408
|
+
const dirOut = [0, 0, 0];
|
|
3409
|
+
const colOut = [0, 0, 0];
|
|
3410
|
+
const handle = {
|
|
3411
|
+
slot,
|
|
3412
|
+
setPosition(x, y, z) {
|
|
3413
|
+
data[base + CURR_POS_X] = x;
|
|
3414
|
+
data[base + CURR_POS_Y] = y;
|
|
3415
|
+
data[base + CURR_POS_Z] = z;
|
|
3416
|
+
},
|
|
3417
|
+
setDirection(x, y, z) {
|
|
3418
|
+
data[base + CURR_DIR_X] = x;
|
|
3419
|
+
data[base + CURR_DIR_Y] = y;
|
|
3420
|
+
data[base + CURR_DIR_Z] = z;
|
|
3421
|
+
},
|
|
3422
|
+
teleport(x, y, z) {
|
|
3423
|
+
data[base + CURR_POS_X] = x;
|
|
3424
|
+
data[base + CURR_POS_Y] = y;
|
|
3425
|
+
data[base + CURR_POS_Z] = z;
|
|
3426
|
+
data[base + PREV_POS_X] = x;
|
|
3427
|
+
data[base + PREV_POS_Y] = y;
|
|
3428
|
+
data[base + PREV_POS_Z] = z;
|
|
3429
|
+
},
|
|
3430
|
+
setColor(r, g, b) {
|
|
3431
|
+
data[base + COL_R] = r;
|
|
3432
|
+
data[base + COL_G] = g;
|
|
3433
|
+
data[base + COL_B] = b;
|
|
3434
|
+
},
|
|
3435
|
+
get position() {
|
|
3436
|
+
posOut[0] = data[base + CURR_POS_X];
|
|
3437
|
+
posOut[1] = data[base + CURR_POS_Y];
|
|
3438
|
+
posOut[2] = data[base + CURR_POS_Z];
|
|
3439
|
+
return posOut;
|
|
3440
|
+
},
|
|
3441
|
+
get direction() {
|
|
3442
|
+
dirOut[0] = data[base + CURR_DIR_X];
|
|
3443
|
+
dirOut[1] = data[base + CURR_DIR_Y];
|
|
3444
|
+
dirOut[2] = data[base + CURR_DIR_Z];
|
|
3445
|
+
return dirOut;
|
|
3446
|
+
},
|
|
3447
|
+
get color() {
|
|
3448
|
+
colOut[0] = data[base + COL_R];
|
|
3449
|
+
colOut[1] = data[base + COL_G];
|
|
3450
|
+
colOut[2] = data[base + COL_B];
|
|
3451
|
+
return colOut;
|
|
3452
|
+
},
|
|
3453
|
+
get intensity() {
|
|
3454
|
+
return data[base + INTENSITY];
|
|
3455
|
+
},
|
|
3456
|
+
set intensity(v) {
|
|
3457
|
+
data[base + INTENSITY] = v;
|
|
3458
|
+
},
|
|
3459
|
+
get range() {
|
|
3460
|
+
return data[base + RANGE];
|
|
3461
|
+
},
|
|
3462
|
+
set range(v) {
|
|
3463
|
+
data[base + RANGE] = v;
|
|
3464
|
+
},
|
|
3465
|
+
get angle() {
|
|
3466
|
+
return angleArr[slot];
|
|
3467
|
+
},
|
|
3468
|
+
set angle(v) {
|
|
3469
|
+
angleArr[slot] = v;
|
|
3470
|
+
const { innerCos, outerCos } = coneCosines(v, smoothArr[slot]);
|
|
3471
|
+
data[base + INNER_COS] = innerCos;
|
|
3472
|
+
data[base + OUTER_COS] = outerCos;
|
|
3473
|
+
},
|
|
3474
|
+
get smoothness() {
|
|
3475
|
+
return smoothArr[slot];
|
|
3476
|
+
},
|
|
3477
|
+
set smoothness(v) {
|
|
3478
|
+
const s = v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3479
|
+
smoothArr[slot] = s;
|
|
3480
|
+
const { innerCos, outerCos } = coneCosines(angleArr[slot], s);
|
|
3481
|
+
data[base + INNER_COS] = innerCos;
|
|
3482
|
+
data[base + OUTER_COS] = outerCos;
|
|
3483
|
+
},
|
|
3484
|
+
get enabled() {
|
|
3485
|
+
return enabledArr[slot] === 1;
|
|
3486
|
+
},
|
|
3487
|
+
set enabled(v) {
|
|
3488
|
+
enabledArr[slot] = v ? 1 : 0;
|
|
3489
|
+
},
|
|
3490
|
+
destroy() {
|
|
3491
|
+
if (destroyed)
|
|
3492
|
+
return;
|
|
3493
|
+
destroyed = true;
|
|
3494
|
+
data.fill(0, base, base + LIGHT_FLOATS);
|
|
3495
|
+
handles[slot] = null;
|
|
3496
|
+
slots.remove(slot);
|
|
3497
|
+
}
|
|
3498
|
+
};
|
|
3499
|
+
this.handles[slot] = handle;
|
|
3500
|
+
return handle;
|
|
3501
|
+
}
|
|
3502
|
+
/**
|
|
3503
|
+
* Set the global directional light (the "sun"). `direction` points from the
|
|
3504
|
+
* surface toward the light. Defaults to `(0.3, 0.8, 0.5)`, white, intensity 1.
|
|
3505
|
+
*/
|
|
3506
|
+
setDirectional(direction, color = [1, 1, 1], intensity = 1) {
|
|
3507
|
+
this.dirDir = [direction[0], direction[1], direction[2]];
|
|
3508
|
+
this.dirColor = [color[0], color[1], color[2]];
|
|
3509
|
+
this.dirIntensity = intensity;
|
|
3510
|
+
}
|
|
3511
|
+
/** Set the global ambient term. Defaults to `(0.3, 0.3, 0.3)`. */
|
|
3512
|
+
setAmbient(color) {
|
|
3513
|
+
this.ambient = [color[0], color[1], color[2]];
|
|
3514
|
+
}
|
|
3515
|
+
/** Number of live dynamic lights. */
|
|
3516
|
+
get count() {
|
|
3517
|
+
return this.slots.size;
|
|
3518
|
+
}
|
|
3519
|
+
/**
|
|
3520
|
+
* Pack enabled lights into a dense run for upload. Disabled lights are
|
|
3521
|
+
* skipped so the shader loop only walks contributing lights. Returns the
|
|
3522
|
+
* shared scratch buffer, the light count, and the byte length to upload
|
|
3523
|
+
* (so the caller never needs the record layout).
|
|
3524
|
+
*/
|
|
3525
|
+
pack() {
|
|
3526
|
+
const active = this.slots.activeSlots;
|
|
3527
|
+
const size = this.slots.size;
|
|
3528
|
+
const src = this.data;
|
|
3529
|
+
const dst = this.uploadData;
|
|
3530
|
+
let count = 0;
|
|
3531
|
+
for (let i = 0; i < size; i++) {
|
|
3532
|
+
const slot = active[i];
|
|
3533
|
+
if (this.enabled[slot] === 0)
|
|
3534
|
+
continue;
|
|
3535
|
+
const sBase = slot * LIGHT_FLOATS;
|
|
3536
|
+
dst.set(src.subarray(sBase, sBase + LIGHT_FLOATS), count * LIGHT_FLOATS);
|
|
3537
|
+
count++;
|
|
3538
|
+
}
|
|
3539
|
+
return { data: dst, count, byteLength: count * LIGHT_FLOATS * 4 };
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Stamp the directional + ambient terms and the light count into the
|
|
3543
|
+
* renderer's uniform array, starting at `offset` (the float index after the
|
|
3544
|
+
* VP matrix + alpha). Layout: lightDir(3), dirColor(3), dirIntensity(1),
|
|
3545
|
+
* ambient(3), then lightCount as a u32 reinterpret at `offset + 10`.
|
|
3546
|
+
*/
|
|
3547
|
+
writeUniforms(uniformData, offset, count) {
|
|
3548
|
+
uniformData[offset + 0] = this.dirDir[0];
|
|
3549
|
+
uniformData[offset + 1] = this.dirDir[1];
|
|
3550
|
+
uniformData[offset + 2] = this.dirDir[2];
|
|
3551
|
+
uniformData[offset + 3] = this.dirColor[0];
|
|
3552
|
+
uniformData[offset + 4] = this.dirColor[1];
|
|
3553
|
+
uniformData[offset + 5] = this.dirColor[2];
|
|
3554
|
+
uniformData[offset + 6] = this.dirIntensity;
|
|
3555
|
+
uniformData[offset + 7] = this.ambient[0];
|
|
3556
|
+
uniformData[offset + 8] = this.ambient[1];
|
|
3557
|
+
uniformData[offset + 9] = this.ambient[2];
|
|
3558
|
+
new Uint32Array(uniformData.buffer)[offset + 10] = count;
|
|
3559
|
+
}
|
|
3560
|
+
/**
|
|
3561
|
+
* Snapshot every live light's current position/direction into its prev
|
|
3562
|
+
* slot. Called from the renderer's `storePreviousState()` in `pre-tick`, so
|
|
3563
|
+
* the shader can `mix(prev, curr, alpha)` and moving lights interpolate at
|
|
3564
|
+
* render rate instead of snapping at the tick boundary.
|
|
3565
|
+
*/
|
|
3566
|
+
storePrevious() {
|
|
3567
|
+
const active = this.slots.activeSlots;
|
|
3568
|
+
const size = this.slots.size;
|
|
3569
|
+
const data = this.data;
|
|
3570
|
+
for (let i = 0; i < size; i++) {
|
|
3571
|
+
const base = active[i] * LIGHT_FLOATS;
|
|
3572
|
+
data[base + PREV_POS_X] = data[base + CURR_POS_X];
|
|
3573
|
+
data[base + PREV_POS_Y] = data[base + CURR_POS_Y];
|
|
3574
|
+
data[base + PREV_POS_Z] = data[base + CURR_POS_Z];
|
|
3575
|
+
data[base + PREV_DIR_X] = data[base + CURR_DIR_X];
|
|
3576
|
+
data[base + PREV_DIR_Y] = data[base + CURR_DIR_Y];
|
|
3577
|
+
data[base + PREV_DIR_Z] = data[base + CURR_DIR_Z];
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
/** Write a light spec into its CPU slot. Prev is seeded to curr (no spawn lerp). */
|
|
3581
|
+
writeSlot(slot, spec) {
|
|
3582
|
+
const base = slot * LIGHT_FLOATS;
|
|
3583
|
+
const data = this.data;
|
|
3584
|
+
const color = spec.color ?? [1, 1, 1];
|
|
3585
|
+
const [px, py, pz] = spec.position;
|
|
3586
|
+
data[base + KIND] = spec.type === "spot" ? LIGHT_KIND_SPOT : LIGHT_KIND_POINT;
|
|
3587
|
+
data[base + CURR_POS_X] = px;
|
|
3588
|
+
data[base + PREV_POS_X] = px;
|
|
3589
|
+
data[base + CURR_POS_Y] = py;
|
|
3590
|
+
data[base + PREV_POS_Y] = py;
|
|
3591
|
+
data[base + CURR_POS_Z] = pz;
|
|
3592
|
+
data[base + PREV_POS_Z] = pz;
|
|
3593
|
+
data[base + COL_R] = color[0];
|
|
3594
|
+
data[base + COL_G] = color[1];
|
|
3595
|
+
data[base + COL_B] = color[2];
|
|
3596
|
+
data[base + INTENSITY] = spec.intensity ?? 1;
|
|
3597
|
+
data[base + RANGE] = spec.range ?? 10;
|
|
3598
|
+
data[base + CASTS_SHADOW] = 0;
|
|
3599
|
+
data[base + SHADOW_INDEX] = -1;
|
|
3600
|
+
if (spec.type === "spot") {
|
|
3601
|
+
const [dx, dy, dz] = spec.direction;
|
|
3602
|
+
data[base + CURR_DIR_X] = dx;
|
|
3603
|
+
data[base + PREV_DIR_X] = dx;
|
|
3604
|
+
data[base + CURR_DIR_Y] = dy;
|
|
3605
|
+
data[base + PREV_DIR_Y] = dy;
|
|
3606
|
+
data[base + CURR_DIR_Z] = dz;
|
|
3607
|
+
data[base + PREV_DIR_Z] = dz;
|
|
3608
|
+
const angle = spec.angle ?? 0.5;
|
|
3609
|
+
const smoothness = Math.min(1, Math.max(0, spec.smoothness ?? 0.5));
|
|
3610
|
+
this.angle[slot] = angle;
|
|
3611
|
+
this.smoothness[slot] = smoothness;
|
|
3612
|
+
const { innerCos, outerCos } = coneCosines(angle, smoothness);
|
|
3613
|
+
data[base + INNER_COS] = innerCos;
|
|
3614
|
+
data[base + OUTER_COS] = outerCos;
|
|
3615
|
+
} else {
|
|
3616
|
+
data[base + CURR_DIR_X] = 0;
|
|
3617
|
+
data[base + PREV_DIR_X] = 0;
|
|
3618
|
+
data[base + CURR_DIR_Y] = 0;
|
|
3619
|
+
data[base + PREV_DIR_Y] = 0;
|
|
3620
|
+
data[base + CURR_DIR_Z] = 0;
|
|
3621
|
+
data[base + PREV_DIR_Z] = 0;
|
|
3622
|
+
this.angle[slot] = 0;
|
|
3623
|
+
this.smoothness[slot] = 0;
|
|
3624
|
+
data[base + INNER_COS] = 1;
|
|
3625
|
+
data[base + OUTER_COS] = -1;
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
};
|
|
3629
|
+
function coneCosines(angle, smoothness) {
|
|
3630
|
+
const outerCos = Math.cos(angle);
|
|
3631
|
+
const innerCos = Math.cos(angle * (1 - smoothness));
|
|
3632
|
+
return { innerCos, outerCos };
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
// src/camera/camera-3d.ts
|
|
3636
|
+
var import_lerp2 = require("murow/core/lerp");
|
|
3637
|
+
var import_ray = require("murow/core/ray");
|
|
3638
|
+
var Camera3D = class {
|
|
3639
|
+
constructor() {
|
|
3640
|
+
this.position = [0, 5, -10];
|
|
3641
|
+
this.target = [0, 0, 0];
|
|
3642
|
+
this.up = [0, 1, 0];
|
|
3643
|
+
this.fov = 60;
|
|
3644
|
+
this.near = 0.1;
|
|
3645
|
+
this.far = 1e3;
|
|
3646
|
+
this.aspect = 1;
|
|
3647
|
+
this.movement = "local";
|
|
3648
|
+
// Previous state for interpolation (stored before each tick)
|
|
3649
|
+
this._prevPosition = [0, 5, -10];
|
|
3650
|
+
this._prevTarget = [0, 0, 0];
|
|
3651
|
+
// Interpolated state used for rendering
|
|
3652
|
+
this._renderPosition = [0, 5, -10];
|
|
3653
|
+
this._renderTarget = [0, 0, 0];
|
|
3654
|
+
this._viewMatrix = new Float32Array(16);
|
|
3655
|
+
this._projMatrix = new Float32Array(16);
|
|
3656
|
+
this._vpMatrix = new Float32Array(16);
|
|
3657
|
+
this._ray = new import_ray.Ray3D();
|
|
3658
|
+
this._width = 1;
|
|
3659
|
+
this._height = 1;
|
|
3660
|
+
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Store current position/target as previous. Call before each tick.
|
|
3663
|
+
*/
|
|
3664
|
+
storePrevious() {
|
|
3665
|
+
this._prevPosition[0] = this.position[0];
|
|
3666
|
+
this._prevPosition[1] = this.position[1];
|
|
3667
|
+
this._prevPosition[2] = this.position[2];
|
|
3668
|
+
this._prevTarget[0] = this.target[0];
|
|
3669
|
+
this._prevTarget[1] = this.target[1];
|
|
3670
|
+
this._prevTarget[2] = this.target[2];
|
|
3671
|
+
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Smoothly move the camera toward a target point.
|
|
3674
|
+
* Call each tick. The camera and its look-at target lerp toward the given position.
|
|
3675
|
+
* @param targetX World X to follow
|
|
3676
|
+
* @param targetY World Y to follow
|
|
3677
|
+
* @param targetZ World Z to follow
|
|
3678
|
+
* @param smoothing 0-1. 1 = snap instantly, 0.1 = lazy follow. Default 1.
|
|
3679
|
+
*/
|
|
3680
|
+
follow(targetX, targetY, targetZ, smoothing = 1) {
|
|
3681
|
+
const dx = targetX - this.target[0];
|
|
3682
|
+
const dy = targetY - this.target[1];
|
|
3683
|
+
const dz = targetZ - this.target[2];
|
|
3684
|
+
const mx = dx * smoothing;
|
|
3685
|
+
const my = dy * smoothing;
|
|
3686
|
+
const mz = dz * smoothing;
|
|
3687
|
+
this.target[0] += mx;
|
|
3688
|
+
this.target[1] += my;
|
|
3689
|
+
this.target[2] += mz;
|
|
3690
|
+
this.position[0] += mx;
|
|
3691
|
+
this.position[1] += my;
|
|
3692
|
+
this.position[2] += mz;
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3695
|
+
* Interpolate between previous and current state. Call before rendering.
|
|
3696
|
+
*/
|
|
3697
|
+
interpolate(alpha) {
|
|
3698
|
+
this._renderPosition[0] = (0, import_lerp2.lerp)(this._prevPosition[0], this.position[0], alpha);
|
|
3699
|
+
this._renderPosition[1] = (0, import_lerp2.lerp)(this._prevPosition[1], this.position[1], alpha);
|
|
3700
|
+
this._renderPosition[2] = (0, import_lerp2.lerp)(this._prevPosition[2], this.position[2], alpha);
|
|
3701
|
+
this._renderTarget[0] = (0, import_lerp2.lerp)(this._prevTarget[0], this.target[0], alpha);
|
|
3702
|
+
this._renderTarget[1] = (0, import_lerp2.lerp)(this._prevTarget[1], this.target[1], alpha);
|
|
3703
|
+
this._renderTarget[2] = (0, import_lerp2.lerp)(this._prevTarget[2], this.target[2], alpha);
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Build the view matrix (lookAt) using interpolated state.
|
|
3707
|
+
*/
|
|
3708
|
+
getViewMatrix() {
|
|
3709
|
+
lookAt(this._viewMatrix, this._renderPosition, this._renderTarget, this.up);
|
|
3710
|
+
return this._viewMatrix;
|
|
3711
|
+
}
|
|
3712
|
+
/**
|
|
3713
|
+
* Build the perspective projection matrix.
|
|
3714
|
+
*/
|
|
3715
|
+
getProjectionMatrix() {
|
|
3716
|
+
perspective(this._projMatrix, this.fov * (Math.PI / 180), this.aspect, this.near, this.far);
|
|
3717
|
+
return this._projMatrix;
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Build the combined view-projection matrix.
|
|
3721
|
+
*/
|
|
3722
|
+
getViewProjectionMatrix() {
|
|
3723
|
+
this.getViewMatrix();
|
|
3724
|
+
this.getProjectionMatrix();
|
|
3725
|
+
mat4Multiply(this._vpMatrix, this._projMatrix, this._viewMatrix);
|
|
3726
|
+
return this._vpMatrix;
|
|
3727
|
+
}
|
|
3728
|
+
setAspect(width, height) {
|
|
3729
|
+
this.aspect = width / height;
|
|
3730
|
+
this._width = width;
|
|
3731
|
+
this._height = height;
|
|
3732
|
+
}
|
|
3733
|
+
setPosition(x, y, z) {
|
|
3734
|
+
this.position[0] = x;
|
|
3735
|
+
this.position[1] = y;
|
|
3736
|
+
this.position[2] = z;
|
|
3737
|
+
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Unproject a screen coordinate into a world-space ray.
|
|
3740
|
+
* Requires `setAspect(width, height)` to have been called first.
|
|
3741
|
+
* Returns a pre-allocated Ray3D — copy origin/direction if you need to store it.
|
|
3742
|
+
*/
|
|
3743
|
+
screenToRay(screenX, screenY) {
|
|
3744
|
+
this.getViewMatrix();
|
|
3745
|
+
const m = this._viewMatrix;
|
|
3746
|
+
const ndcX = 2 * screenX / this._width - 1;
|
|
3747
|
+
const ndcY = 1 - 2 * screenY / this._height;
|
|
3748
|
+
const t = Math.tan(this.fov * Math.PI / 180 * 0.5);
|
|
3749
|
+
const rightX = m[0], rightY = m[4], rightZ = m[8];
|
|
3750
|
+
const upX = m[1], upY = m[5], upZ = m[9];
|
|
3751
|
+
const fwdX = -m[2], fwdY = -m[6], fwdZ = -m[10];
|
|
3752
|
+
const dx = fwdX + rightX * ndcX * t * this.aspect + upX * ndcY * t;
|
|
3753
|
+
const dy = fwdY + rightY * ndcX * t * this.aspect + upY * ndcY * t;
|
|
3754
|
+
const dz = fwdZ + rightZ * ndcX * t * this.aspect + upZ * ndcY * t;
|
|
3755
|
+
this._ray.set(this.position[0], this.position[1], this.position[2], dx, dy, dz);
|
|
3756
|
+
return this._ray;
|
|
3757
|
+
}
|
|
3758
|
+
setTarget(x, y, z) {
|
|
3759
|
+
this.target[0] = x;
|
|
3760
|
+
this.target[1] = y;
|
|
3761
|
+
this.target[2] = z;
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Move the camera. Behaviour is determined by the `movement` property:
|
|
3765
|
+
* - `'local'` — along camera axes, pitch included (free-fly / spectator)
|
|
3766
|
+
* - `'grounded'` — yaw-projected XZ + world Y (FPS)
|
|
3767
|
+
* - `'global'` — world axes directly (isometric / platformer)
|
|
3768
|
+
*/
|
|
3769
|
+
move(right, up, forward) {
|
|
3770
|
+
if (this.movement === "grounded")
|
|
3771
|
+
this._moveGrounded(right, up, forward);
|
|
3772
|
+
else if (this.movement === "global")
|
|
3773
|
+
this._moveGlobal(right, up, forward);
|
|
3774
|
+
else
|
|
3775
|
+
this._moveLocal(right, up, forward);
|
|
3776
|
+
}
|
|
3777
|
+
_moveLocal(right, up, forward) {
|
|
3778
|
+
this.getViewMatrix();
|
|
3779
|
+
const m = this._viewMatrix;
|
|
3780
|
+
const dx = m[0] * right + m[1] * up - m[2] * forward;
|
|
3781
|
+
const dy = m[4] * right + m[5] * up - m[6] * forward;
|
|
3782
|
+
const dz = m[8] * right + m[9] * up - m[10] * forward;
|
|
3783
|
+
this.position[0] += dx;
|
|
3784
|
+
this.position[1] += dy;
|
|
3785
|
+
this.position[2] += dz;
|
|
3786
|
+
this.target[0] += dx;
|
|
3787
|
+
this.target[1] += dy;
|
|
3788
|
+
this.target[2] += dz;
|
|
3789
|
+
}
|
|
3790
|
+
_moveGrounded(right, up, forward) {
|
|
3791
|
+
const yaw = Math.atan2(this.target[0] - this.position[0], this.target[2] - this.position[2]);
|
|
3792
|
+
const dx = Math.sin(yaw) * forward + Math.cos(yaw) * right;
|
|
3793
|
+
const dz = Math.cos(yaw) * forward - Math.sin(yaw) * right;
|
|
3794
|
+
this.position[0] += dx;
|
|
3795
|
+
this.position[1] += up;
|
|
3796
|
+
this.position[2] += dz;
|
|
3797
|
+
this.target[0] += dx;
|
|
3798
|
+
this.target[1] += up;
|
|
3799
|
+
this.target[2] += dz;
|
|
3800
|
+
}
|
|
3801
|
+
_moveGlobal(right, up, forward) {
|
|
3802
|
+
this.position[0] += right;
|
|
3803
|
+
this.position[1] += up;
|
|
3804
|
+
this.position[2] += forward;
|
|
3805
|
+
this.target[0] += right;
|
|
3806
|
+
this.target[1] += up;
|
|
3807
|
+
this.target[2] += forward;
|
|
3808
|
+
}
|
|
3809
|
+
/**
|
|
3810
|
+
* Orbit around the target point. Zero allocations.
|
|
3811
|
+
* @param yawDelta Horizontal rotation in radians (positive = rotate right)
|
|
3812
|
+
* @param pitchDelta Vertical rotation in radians (positive = rotate up)
|
|
3813
|
+
*/
|
|
3814
|
+
orbit(yawDelta, pitchDelta) {
|
|
3815
|
+
let ox = this.position[0] - this.target[0];
|
|
3816
|
+
let oy = this.position[1] - this.target[1];
|
|
3817
|
+
let oz = this.position[2] - this.target[2];
|
|
3818
|
+
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
3819
|
+
let yaw = Math.atan2(ox, oz);
|
|
3820
|
+
let pitch = Math.asin(oy / dist);
|
|
3821
|
+
yaw += yawDelta;
|
|
3822
|
+
pitch += pitchDelta;
|
|
3823
|
+
pitch = Math.max(-Math.PI * 0.49, Math.min(Math.PI * 0.49, pitch));
|
|
3824
|
+
this.position[0] = this.target[0] + Math.sin(yaw) * Math.cos(pitch) * dist;
|
|
3825
|
+
this.position[1] = this.target[1] + Math.sin(pitch) * dist;
|
|
3826
|
+
this.position[2] = this.target[2] + Math.cos(yaw) * Math.cos(pitch) * dist;
|
|
3827
|
+
}
|
|
3828
|
+
/**
|
|
3829
|
+
* Zoom by adjusting distance to target. Zero allocations.
|
|
3830
|
+
* @param delta Positive = zoom in, negative = zoom out
|
|
3831
|
+
*/
|
|
3832
|
+
zoom(delta) {
|
|
3833
|
+
let ox = this.position[0] - this.target[0];
|
|
3834
|
+
let oy = this.position[1] - this.target[1];
|
|
3835
|
+
let oz = this.position[2] - this.target[2];
|
|
3836
|
+
const dist = Math.sqrt(ox * ox + oy * oy + oz * oz);
|
|
3837
|
+
const newDist = Math.max(0.1, dist - delta);
|
|
3838
|
+
const scale = newDist / dist;
|
|
3839
|
+
this.position[0] = this.target[0] + ox * scale;
|
|
3840
|
+
this.position[1] = this.target[1] + oy * scale;
|
|
3841
|
+
this.position[2] = this.target[2] + oz * scale;
|
|
3842
|
+
}
|
|
3843
|
+
};
|
|
3844
|
+
function lookAt(out, eye, center, up) {
|
|
3845
|
+
let fx = center[0] - eye[0];
|
|
3846
|
+
let fy = center[1] - eye[1];
|
|
3847
|
+
let fz = center[2] - eye[2];
|
|
3848
|
+
let len = 1 / Math.sqrt(fx * fx + fy * fy + fz * fz);
|
|
3849
|
+
fx *= len;
|
|
3850
|
+
fy *= len;
|
|
3851
|
+
fz *= len;
|
|
3852
|
+
let sx = fy * up[2] - fz * up[1];
|
|
3853
|
+
let sy = fz * up[0] - fx * up[2];
|
|
3854
|
+
let sz = fx * up[1] - fy * up[0];
|
|
3855
|
+
len = Math.sqrt(sx * sx + sy * sy + sz * sz);
|
|
3856
|
+
if (len > 0) {
|
|
3857
|
+
len = 1 / len;
|
|
3858
|
+
sx *= len;
|
|
3859
|
+
sy *= len;
|
|
3860
|
+
sz *= len;
|
|
3861
|
+
}
|
|
3862
|
+
const ux = sy * fz - sz * fy;
|
|
3863
|
+
const uy = sz * fx - sx * fz;
|
|
3864
|
+
const uz = sx * fy - sy * fx;
|
|
3865
|
+
out[0] = sx;
|
|
3866
|
+
out[1] = ux;
|
|
3867
|
+
out[2] = -fx;
|
|
3868
|
+
out[3] = 0;
|
|
3869
|
+
out[4] = sy;
|
|
3870
|
+
out[5] = uy;
|
|
3871
|
+
out[6] = -fy;
|
|
3872
|
+
out[7] = 0;
|
|
3873
|
+
out[8] = sz;
|
|
3874
|
+
out[9] = uz;
|
|
3875
|
+
out[10] = -fz;
|
|
3876
|
+
out[11] = 0;
|
|
3877
|
+
out[12] = -(sx * eye[0] + sy * eye[1] + sz * eye[2]);
|
|
3878
|
+
out[13] = -(ux * eye[0] + uy * eye[1] + uz * eye[2]);
|
|
3879
|
+
out[14] = fx * eye[0] + fy * eye[1] + fz * eye[2];
|
|
3880
|
+
out[15] = 1;
|
|
3881
|
+
}
|
|
3882
|
+
function perspective(out, fovRad, aspect, near, far) {
|
|
3883
|
+
const f = 1 / Math.tan(fovRad * 0.5);
|
|
3884
|
+
const rangeInv = 1 / (near - far);
|
|
3885
|
+
out[0] = f / aspect;
|
|
3886
|
+
out[1] = 0;
|
|
3887
|
+
out[2] = 0;
|
|
3888
|
+
out[3] = 0;
|
|
3889
|
+
out[4] = 0;
|
|
3890
|
+
out[5] = f;
|
|
3891
|
+
out[6] = 0;
|
|
3892
|
+
out[7] = 0;
|
|
3893
|
+
out[8] = 0;
|
|
3894
|
+
out[9] = 0;
|
|
3895
|
+
out[10] = (near + far) * rangeInv;
|
|
3896
|
+
out[11] = -1;
|
|
3897
|
+
out[12] = 0;
|
|
3898
|
+
out[13] = 0;
|
|
3899
|
+
out[14] = 2 * near * far * rangeInv;
|
|
3900
|
+
out[15] = 0;
|
|
3901
|
+
}
|
|
3902
|
+
function mat4Multiply(out, a, b) {
|
|
3903
|
+
for (let i = 0; i < 4; i++) {
|
|
3904
|
+
const ai0 = a[i], ai1 = a[i + 4], ai2 = a[i + 8], ai3 = a[i + 12];
|
|
3905
|
+
out[i] = ai0 * b[0] + ai1 * b[1] + ai2 * b[2] + ai3 * b[3];
|
|
3906
|
+
out[i + 4] = ai0 * b[4] + ai1 * b[5] + ai2 * b[6] + ai3 * b[7];
|
|
3907
|
+
out[i + 8] = ai0 * b[8] + ai1 * b[9] + ai2 * b[10] + ai3 * b[11];
|
|
3908
|
+
out[i + 12] = ai0 * b[12] + ai1 * b[13] + ai2 * b[14] + ai3 * b[15];
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3265
3911
|
|
|
3266
3912
|
// src/3d/renderer.ts
|
|
3267
|
-
var
|
|
3913
|
+
var import_renderer7 = require("murow/renderer");
|
|
3914
|
+
var import_hitbox3 = require("murow/core/hitbox");
|
|
3268
3915
|
|
|
3269
3916
|
// src/3d/skeletal-animation-compute/packer.ts
|
|
3270
3917
|
function packAnimationData(packed) {
|
|
@@ -3464,8 +4111,8 @@ function buildAnimationKernel(root, packed, maxInstances, maxTotalBones, budgets
|
|
|
3464
4111
|
let by = animF32[offB + 1];
|
|
3465
4112
|
let bz = animF32[offB + 2];
|
|
3466
4113
|
let bw = animF32[offB + 3];
|
|
3467
|
-
const
|
|
3468
|
-
if (
|
|
4114
|
+
const dot3 = ax * bx + ay * by + az * bz + aw * bw;
|
|
4115
|
+
if (dot3 < 0) {
|
|
3469
4116
|
bx = -bx;
|
|
3470
4117
|
by = -by;
|
|
3471
4118
|
bz = -bz;
|
|
@@ -3740,6 +4387,327 @@ var GltfClipResyncCoordinator = class {
|
|
|
3740
4387
|
}
|
|
3741
4388
|
};
|
|
3742
4389
|
|
|
4390
|
+
// src/3d/raycast.ts
|
|
4391
|
+
var import_raycast3 = require("murow/core/raycast");
|
|
4392
|
+
var import_renderer5 = require("murow/renderer");
|
|
4393
|
+
var WebGPURaycast3D = class extends import_renderer5.Raycast {
|
|
4394
|
+
constructor(renderer) {
|
|
4395
|
+
super();
|
|
4396
|
+
this.renderer = renderer;
|
|
4397
|
+
this.state = new import_raycast3.HitBuffer(3);
|
|
4398
|
+
this.resultBuffer = [];
|
|
4399
|
+
this.memos = /* @__PURE__ */ new Set();
|
|
4400
|
+
}
|
|
4401
|
+
update(input) {
|
|
4402
|
+
this.state.reset();
|
|
4403
|
+
this.renderer._collectRaycastHitsInto(input.mouse.position.x, input.mouse.position.y, this.state);
|
|
4404
|
+
for (const m of this.memos)
|
|
4405
|
+
m._invalidate();
|
|
4406
|
+
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Nearest hit, or null. The returned object is pool-backed and valid
|
|
4409
|
+
* only until the next `update()` -- copy what you need, or use `memo`
|
|
4410
|
+
* for results that persist across frames.
|
|
4411
|
+
*/
|
|
4412
|
+
hit(opts) {
|
|
4413
|
+
return this.state.nearest(opts?.filter, opts?.maxDistance ?? Infinity);
|
|
4414
|
+
}
|
|
4415
|
+
/**
|
|
4416
|
+
* All hits, nearest first. The array and its entries are reused across
|
|
4417
|
+
* calls and overwritten by the next `update()`; do not retain them.
|
|
4418
|
+
*/
|
|
4419
|
+
hitAll(opts) {
|
|
4420
|
+
this.state.collectInto(this.resultBuffer, opts?.filter, opts?.maxDistance ?? Infinity);
|
|
4421
|
+
return this.resultBuffer;
|
|
4422
|
+
}
|
|
4423
|
+
memo(opts) {
|
|
4424
|
+
const m = new WebGPURaycastMemo3D(this.state, opts, () => this.memos.delete(m));
|
|
4425
|
+
this.memos.add(m);
|
|
4426
|
+
return m;
|
|
4427
|
+
}
|
|
4428
|
+
clearMemos() {
|
|
4429
|
+
for (const m of this.memos)
|
|
4430
|
+
m._detach();
|
|
4431
|
+
this.memos.clear();
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
var WebGPURaycastMemo3D = class extends import_renderer5.RaycastMemo {
|
|
4435
|
+
constructor(state, opts, onDispose) {
|
|
4436
|
+
super();
|
|
4437
|
+
this.state = state;
|
|
4438
|
+
this.opts = opts;
|
|
4439
|
+
this.onDispose = onDispose;
|
|
4440
|
+
this.dirty = true;
|
|
4441
|
+
this.detached = false;
|
|
4442
|
+
this.cached = [];
|
|
4443
|
+
}
|
|
4444
|
+
get hits() {
|
|
4445
|
+
if (this.detached)
|
|
4446
|
+
return this.cached;
|
|
4447
|
+
if (this.dirty) {
|
|
4448
|
+
this.state.collectInto(this.cached, this.opts.filter, this.opts.maxDistance ?? Infinity);
|
|
4449
|
+
this.dirty = false;
|
|
4450
|
+
}
|
|
4451
|
+
return this.cached;
|
|
4452
|
+
}
|
|
4453
|
+
get first() {
|
|
4454
|
+
const arr = this.hits;
|
|
4455
|
+
return arr.length > 0 ? arr[0] : null;
|
|
4456
|
+
}
|
|
4457
|
+
dispose() {
|
|
4458
|
+
if (this.detached)
|
|
4459
|
+
return;
|
|
4460
|
+
this.onDispose();
|
|
4461
|
+
this._detach();
|
|
4462
|
+
}
|
|
4463
|
+
_invalidate() {
|
|
4464
|
+
this.dirty = true;
|
|
4465
|
+
}
|
|
4466
|
+
_detach() {
|
|
4467
|
+
this.detached = true;
|
|
4468
|
+
this.cached.length = 0;
|
|
4469
|
+
}
|
|
4470
|
+
};
|
|
4471
|
+
|
|
4472
|
+
// src/3d/hitbox.ts
|
|
4473
|
+
var import_hitbox2 = require("murow/core/hitbox");
|
|
4474
|
+
function buildUnitSphereWireframe(segments = 16) {
|
|
4475
|
+
const out = [];
|
|
4476
|
+
const step2 = Math.PI * 2 / segments;
|
|
4477
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
4478
|
+
for (let i = 0; i < segments; i++) {
|
|
4479
|
+
const a = i * step2;
|
|
4480
|
+
const b = (i + 1) * step2;
|
|
4481
|
+
const ca = Math.cos(a), sa = Math.sin(a);
|
|
4482
|
+
const cb = Math.cos(b), sb = Math.sin(b);
|
|
4483
|
+
if (axis === 0) {
|
|
4484
|
+
out.push(0, ca, sa, 0, cb, sb);
|
|
4485
|
+
} else if (axis === 1) {
|
|
4486
|
+
out.push(ca, 0, sa, cb, 0, sb);
|
|
4487
|
+
} else {
|
|
4488
|
+
out.push(ca, sa, 0, cb, sb, 0);
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
return new Float32Array(out);
|
|
4493
|
+
}
|
|
4494
|
+
function buildUnitBoxWireframe() {
|
|
4495
|
+
const h = 0.5;
|
|
4496
|
+
const corners = [
|
|
4497
|
+
[-h, -h, -h],
|
|
4498
|
+
[h, -h, -h],
|
|
4499
|
+
[h, h, -h],
|
|
4500
|
+
[-h, h, -h],
|
|
4501
|
+
[-h, -h, h],
|
|
4502
|
+
[h, -h, h],
|
|
4503
|
+
[h, h, h],
|
|
4504
|
+
[-h, h, h]
|
|
4505
|
+
];
|
|
4506
|
+
const edges = [
|
|
4507
|
+
[0, 1],
|
|
4508
|
+
[1, 2],
|
|
4509
|
+
[2, 3],
|
|
4510
|
+
[3, 0],
|
|
4511
|
+
[4, 5],
|
|
4512
|
+
[5, 6],
|
|
4513
|
+
[6, 7],
|
|
4514
|
+
[7, 4],
|
|
4515
|
+
[0, 4],
|
|
4516
|
+
[1, 5],
|
|
4517
|
+
[2, 6],
|
|
4518
|
+
[3, 7]
|
|
4519
|
+
];
|
|
4520
|
+
const out = [];
|
|
4521
|
+
for (const [a, b] of edges) {
|
|
4522
|
+
out.push(...corners[a], ...corners[b]);
|
|
4523
|
+
}
|
|
4524
|
+
return new Float32Array(out);
|
|
4525
|
+
}
|
|
4526
|
+
function buildUnitCylinderWireframe(segments = 24) {
|
|
4527
|
+
const h = 0.5;
|
|
4528
|
+
const step2 = Math.PI * 2 / segments;
|
|
4529
|
+
const out = [];
|
|
4530
|
+
for (let i = 0; i < segments; i++) {
|
|
4531
|
+
const a = i * step2, b = (i + 1) * step2;
|
|
4532
|
+
const ca = Math.cos(a), sa = Math.sin(a);
|
|
4533
|
+
const cb = Math.cos(b), sb = Math.sin(b);
|
|
4534
|
+
out.push(ca, -h, sa, cb, -h, sb);
|
|
4535
|
+
out.push(ca, h, sa, cb, h, sb);
|
|
4536
|
+
}
|
|
4537
|
+
for (let i = 0; i < 4; i++) {
|
|
4538
|
+
const a = i * (Math.PI * 0.5);
|
|
4539
|
+
const ca = Math.cos(a), sa = Math.sin(a);
|
|
4540
|
+
out.push(ca, -h, sa, ca, h, sa);
|
|
4541
|
+
}
|
|
4542
|
+
return new Float32Array(out);
|
|
4543
|
+
}
|
|
4544
|
+
var IDLE = [1, 0, 1, 1];
|
|
4545
|
+
var HOVERED = [0.2, 1, 0.4, 1];
|
|
4546
|
+
var UNIFORM_STRIDE = 256;
|
|
4547
|
+
var MIN_BINDING_SIZE = 80;
|
|
4548
|
+
var CAPACITY = 4096;
|
|
4549
|
+
var HitboxDebugRenderer = class {
|
|
4550
|
+
constructor() {
|
|
4551
|
+
this.device = null;
|
|
4552
|
+
this.pipeline = null;
|
|
4553
|
+
this.bindGroup = null;
|
|
4554
|
+
this.uniformBuffer = null;
|
|
4555
|
+
this.sphereBuffer = null;
|
|
4556
|
+
this.sphereVertexCount = 0;
|
|
4557
|
+
this.boxBuffer = null;
|
|
4558
|
+
this.boxVertexCount = 0;
|
|
4559
|
+
this.cylinderBuffer = null;
|
|
4560
|
+
this.cylinderVertexCount = 0;
|
|
4561
|
+
this.stage = new Float32Array(0);
|
|
4562
|
+
this.entries = [];
|
|
4563
|
+
this.vp = new Float32Array(16);
|
|
4564
|
+
}
|
|
4565
|
+
init(device, format) {
|
|
4566
|
+
this.device = device;
|
|
4567
|
+
const upload = (data) => {
|
|
4568
|
+
const buf = device.createBuffer({
|
|
4569
|
+
size: data.byteLength,
|
|
4570
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
4571
|
+
});
|
|
4572
|
+
device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
|
|
4573
|
+
return buf;
|
|
4574
|
+
};
|
|
4575
|
+
const sphereData = buildUnitSphereWireframe();
|
|
4576
|
+
const boxData = buildUnitBoxWireframe();
|
|
4577
|
+
const cylinderData = buildUnitCylinderWireframe();
|
|
4578
|
+
this.sphereBuffer = upload(sphereData);
|
|
4579
|
+
this.sphereVertexCount = sphereData.length / 3;
|
|
4580
|
+
this.boxBuffer = upload(boxData);
|
|
4581
|
+
this.boxVertexCount = boxData.length / 3;
|
|
4582
|
+
this.cylinderBuffer = upload(cylinderData);
|
|
4583
|
+
this.cylinderVertexCount = cylinderData.length / 3;
|
|
4584
|
+
this.uniformBuffer = device.createBuffer({
|
|
4585
|
+
size: UNIFORM_STRIDE * CAPACITY,
|
|
4586
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
4587
|
+
});
|
|
4588
|
+
this.stage = new Float32Array(UNIFORM_STRIDE * CAPACITY / 4);
|
|
4589
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
4590
|
+
entries: [{
|
|
4591
|
+
binding: 0,
|
|
4592
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
4593
|
+
buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: MIN_BINDING_SIZE }
|
|
4594
|
+
}]
|
|
4595
|
+
});
|
|
4596
|
+
this.bindGroup = device.createBindGroup({
|
|
4597
|
+
layout: bindGroupLayout,
|
|
4598
|
+
entries: [{ binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: MIN_BINDING_SIZE } }]
|
|
4599
|
+
});
|
|
4600
|
+
const shaderModule = device.createShaderModule({
|
|
4601
|
+
code: `
|
|
4602
|
+
struct Uniforms {
|
|
4603
|
+
mvp: mat4x4<f32>,
|
|
4604
|
+
color: vec4<f32>,
|
|
4605
|
+
};
|
|
4606
|
+
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
4607
|
+
@vertex
|
|
4608
|
+
fn vs(@location(0) p: vec3<f32>) -> @builtin(position) vec4<f32> {
|
|
4609
|
+
return u.mvp * vec4<f32>(p, 1.0);
|
|
4610
|
+
}
|
|
4611
|
+
@fragment
|
|
4612
|
+
fn fs() -> @location(0) vec4<f32> {
|
|
4613
|
+
return u.color;
|
|
4614
|
+
}
|
|
4615
|
+
`
|
|
4616
|
+
});
|
|
4617
|
+
this.pipeline = device.createRenderPipeline({
|
|
4618
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
|
|
4619
|
+
vertex: {
|
|
4620
|
+
module: shaderModule,
|
|
4621
|
+
entryPoint: "vs",
|
|
4622
|
+
buffers: [{
|
|
4623
|
+
arrayStride: 12,
|
|
4624
|
+
attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }]
|
|
4625
|
+
}]
|
|
4626
|
+
},
|
|
4627
|
+
fragment: { module: shaderModule, entryPoint: "fs", targets: [{ format }] },
|
|
4628
|
+
primitive: { topology: "line-list" },
|
|
4629
|
+
depthStencil: {
|
|
4630
|
+
format: "depth24plus",
|
|
4631
|
+
depthWriteEnabled: false,
|
|
4632
|
+
depthCompare: "less-equal"
|
|
4633
|
+
}
|
|
4634
|
+
});
|
|
4635
|
+
}
|
|
4636
|
+
begin(vp) {
|
|
4637
|
+
this.entries.length = 0;
|
|
4638
|
+
this.vp = vp;
|
|
4639
|
+
}
|
|
4640
|
+
emit(hb, hovered, px, py, pz, sx, sy, sz) {
|
|
4641
|
+
const p = (0, import_hitbox2.placePart3D)(hb, px, py, pz, sx, sy, sz);
|
|
4642
|
+
const color = hovered ? HOVERED : IDLE;
|
|
4643
|
+
if (hb.shape === "sphere") {
|
|
4644
|
+
this.collect(this.sphereBuffer, this.sphereVertexCount, p.cx, p.cy, p.cz, p.hx, p.hx, p.hx, color);
|
|
4645
|
+
} else if (hb.shape === "box") {
|
|
4646
|
+
this.collect(this.boxBuffer, this.boxVertexCount, p.cx, p.cy, p.cz, p.hx * 2, p.hy * 2, p.hz * 2, color);
|
|
4647
|
+
} else {
|
|
4648
|
+
this.collect(this.cylinderBuffer, this.cylinderVertexCount, p.cx, p.cy, p.cz, p.hx, p.hy * 2, p.hz, color);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
flush(pass) {
|
|
4652
|
+
const { pipeline, bindGroup, uniformBuffer, entries } = this;
|
|
4653
|
+
if (!pipeline || !bindGroup || !uniformBuffer || entries.length === 0)
|
|
4654
|
+
return;
|
|
4655
|
+
this.device.queue.writeBuffer(
|
|
4656
|
+
uniformBuffer,
|
|
4657
|
+
0,
|
|
4658
|
+
this.stage.buffer,
|
|
4659
|
+
0,
|
|
4660
|
+
entries.length * UNIFORM_STRIDE
|
|
4661
|
+
);
|
|
4662
|
+
pass.setPipeline(pipeline);
|
|
4663
|
+
let currentVbo = null;
|
|
4664
|
+
for (const e of entries) {
|
|
4665
|
+
if (e.vbo !== currentVbo) {
|
|
4666
|
+
pass.setVertexBuffer(0, e.vbo);
|
|
4667
|
+
currentVbo = e.vbo;
|
|
4668
|
+
}
|
|
4669
|
+
pass.setBindGroup(0, bindGroup, [e.offset]);
|
|
4670
|
+
pass.draw(e.vertexCount, 1, 0, 0);
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
/**
|
|
4674
|
+
* The model matrix is pure scale-then-translate, so `MVP = VP * M`
|
|
4675
|
+
* collapses to scaling VP's first three columns by the extents and
|
|
4676
|
+
* replacing the fourth with `VP * (center, 1)` -- no matrix multiply.
|
|
4677
|
+
*/
|
|
4678
|
+
collect(vbo, vertexCount, cx, cy, cz, ex, ey, ez, color) {
|
|
4679
|
+
if (!vbo || vertexCount === 0)
|
|
4680
|
+
return;
|
|
4681
|
+
if (this.entries.length >= CAPACITY)
|
|
4682
|
+
return;
|
|
4683
|
+
const idx = this.entries.length;
|
|
4684
|
+
const base = idx * UNIFORM_STRIDE >>> 2;
|
|
4685
|
+
const f324 = this.stage;
|
|
4686
|
+
const vp = this.vp;
|
|
4687
|
+
f324[base + 0] = vp[0] * ex;
|
|
4688
|
+
f324[base + 1] = vp[1] * ex;
|
|
4689
|
+
f324[base + 2] = vp[2] * ex;
|
|
4690
|
+
f324[base + 3] = vp[3] * ex;
|
|
4691
|
+
f324[base + 4] = vp[4] * ey;
|
|
4692
|
+
f324[base + 5] = vp[5] * ey;
|
|
4693
|
+
f324[base + 6] = vp[6] * ey;
|
|
4694
|
+
f324[base + 7] = vp[7] * ey;
|
|
4695
|
+
f324[base + 8] = vp[8] * ez;
|
|
4696
|
+
f324[base + 9] = vp[9] * ez;
|
|
4697
|
+
f324[base + 10] = vp[10] * ez;
|
|
4698
|
+
f324[base + 11] = vp[11] * ez;
|
|
4699
|
+
f324[base + 12] = vp[0] * cx + vp[4] * cy + vp[8] * cz + vp[12];
|
|
4700
|
+
f324[base + 13] = vp[1] * cx + vp[5] * cy + vp[9] * cz + vp[13];
|
|
4701
|
+
f324[base + 14] = vp[2] * cx + vp[6] * cy + vp[10] * cz + vp[14];
|
|
4702
|
+
f324[base + 15] = vp[3] * cx + vp[7] * cy + vp[11] * cz + vp[15];
|
|
4703
|
+
f324[base + 16] = color[0];
|
|
4704
|
+
f324[base + 17] = color[1];
|
|
4705
|
+
f324[base + 18] = color[2];
|
|
4706
|
+
f324[base + 19] = color[3];
|
|
4707
|
+
this.entries.push({ vbo, vertexCount, offset: idx * UNIFORM_STRIDE });
|
|
4708
|
+
}
|
|
4709
|
+
};
|
|
4710
|
+
|
|
3743
4711
|
// src/3d/renderer.ts
|
|
3744
4712
|
var DYN_PREV_PX = 0;
|
|
3745
4713
|
var DYN_PREV_PY = 1;
|
|
@@ -3801,7 +4769,7 @@ function computeBucketStats(bucket) {
|
|
|
3801
4769
|
}
|
|
3802
4770
|
return { maxSkinnedParts, maxJointCount };
|
|
3803
4771
|
}
|
|
3804
|
-
var WebGPU3DRenderer = class extends
|
|
4772
|
+
var WebGPU3DRenderer = class extends import_renderer6.Base3DRenderer {
|
|
3805
4773
|
constructor(canvas, options) {
|
|
3806
4774
|
const resolvedMaxInstances = options.maxInstances ?? (options.prefabs ? options.prefabs.size + 16 : 32);
|
|
3807
4775
|
super(canvas, { ...options, maxInstances: resolvedMaxInstances });
|
|
@@ -3809,6 +4777,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3809
4777
|
this.resizeCallbacks = [];
|
|
3810
4778
|
// layer=0, sheetId=modelId
|
|
3811
4779
|
this.staticDirty = false;
|
|
4780
|
+
this.nextInstanceId = 0;
|
|
3812
4781
|
// Models (vertex + index buffers)
|
|
3813
4782
|
this.models = [];
|
|
3814
4783
|
this.nextModelId = 0;
|
|
@@ -3818,7 +4787,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3818
4787
|
this.clipResync = null;
|
|
3819
4788
|
this.boneMatrixDirty = true;
|
|
3820
4789
|
// GPU animation compute
|
|
3821
|
-
this.packedAnimData = (0,
|
|
4790
|
+
this.packedAnimData = (0, import_renderer7.createPackedAnimationData)();
|
|
3822
4791
|
this.animComputeKernel = null;
|
|
3823
4792
|
this.animComputeNeedsRebuild = false;
|
|
3824
4793
|
// Capacities the active kernel was built with — used to gate the in-place upload path on resync.
|
|
@@ -3834,10 +4803,15 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3834
4803
|
this.freedBoneOffsets = /* @__PURE__ */ new Map();
|
|
3835
4804
|
// Frustum planes (6 planes × 4 floats each), extracted from VP matrix
|
|
3836
4805
|
this.frustumPlanes = new Float32Array(24);
|
|
3837
|
-
|
|
3838
|
-
//
|
|
4806
|
+
// Dynamic lights — CPU state (SoA, slots, globals) lives in LightSystem;
|
|
4807
|
+
// the renderer owns only the GPU buffer it packs into each frame.
|
|
4808
|
+
this.lights = new LightSystem(MAX_LIGHTS);
|
|
4809
|
+
this.uniformData = new Float32Array(MESH_UNIFORM_FLOATS);
|
|
3839
4810
|
this.lastRenderTime = 0;
|
|
4811
|
+
this.debug = { hitboxes: false };
|
|
4812
|
+
this.hitboxDebug = new HitboxDebugRenderer();
|
|
3840
4813
|
this.camera = new Camera3D();
|
|
4814
|
+
this.raycast = new WebGPURaycast3D(this);
|
|
3841
4815
|
this._prefabs = options.prefabs ?? null;
|
|
3842
4816
|
const SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP = 3;
|
|
3843
4817
|
const bucketStats = this._prefabs ? computeBucketStats(this._prefabs) : null;
|
|
@@ -3849,14 +4823,15 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3849
4823
|
this.updatedBoneOffsets = new Uint8Array(this.maxTotalBones);
|
|
3850
4824
|
this.boneOffsetRefcount = new Uint32Array(this.maxTotalBones);
|
|
3851
4825
|
this.boneOffsetSkinIndex = new Uint32Array(this.maxTotalBones);
|
|
3852
|
-
this.freeList = new
|
|
4826
|
+
this.freeList = new import_free_list2.FreeList(resolvedMaxInstances);
|
|
3853
4827
|
this.batcher = new import_sparse_batcher2.SparseBatcher(resolvedMaxInstances);
|
|
3854
4828
|
this.dynamicData = new Float32Array(resolvedMaxInstances * DYNAMIC_MESH_FLOATS);
|
|
3855
4829
|
this.staticData = new Float32Array(resolvedMaxInstances * STATIC_MESH_FLOATS);
|
|
3856
4830
|
this.slotIndexData = new Uint32Array(resolvedMaxInstances);
|
|
3857
4831
|
this.instanceModelIds = new Uint8Array(resolvedMaxInstances);
|
|
4832
|
+
this.instanceHandles = new Array(resolvedMaxInstances).fill(null);
|
|
3858
4833
|
const msi = this.maxSkinnedInstances;
|
|
3859
|
-
this.skinnedFreeList = new
|
|
4834
|
+
this.skinnedFreeList = new import_free_list2.FreeList(msi);
|
|
3860
4835
|
this.skinnedBatcher = new import_sparse_batcher2.SparseBatcher(msi);
|
|
3861
4836
|
this.skinnedDynamicData = new Float32Array(msi * DYNAMIC_MESH_FLOATS);
|
|
3862
4837
|
this.skinnedStaticData = new Float32Array(msi * SKINNED_STATIC_MESH_FLOATS);
|
|
@@ -3865,6 +4840,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3865
4840
|
this.skinnedInstanceModelIds = new Uint8Array(msi);
|
|
3866
4841
|
this.skinnedInstanceBoneOffsets = new Uint32Array(msi);
|
|
3867
4842
|
this.skinnedAnimStates = new Array(msi).fill(null);
|
|
4843
|
+
this.skinnedInstanceHandles = new Array(msi).fill(null);
|
|
3868
4844
|
this.boneMatrixData = new Float32Array(this.maxTotalBones * 16);
|
|
3869
4845
|
const instBufSize = msi * 8;
|
|
3870
4846
|
this.gpuInstData = new Float32Array(instBufSize);
|
|
@@ -3883,7 +4859,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3883
4859
|
maxComputeInvocationsPerWorkgroup: a.maxComputeInvocationsPerWorkgroup
|
|
3884
4860
|
};
|
|
3885
4861
|
const device = await adapter.requestDevice({ requiredLimits });
|
|
3886
|
-
this.root =
|
|
4862
|
+
this.root = import_typegpu10.default.initFromDevice({ device });
|
|
3887
4863
|
this.device = this.root.device;
|
|
3888
4864
|
this.context = this.canvas.getContext("webgpu");
|
|
3889
4865
|
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
@@ -3894,7 +4870,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3894
4870
|
});
|
|
3895
4871
|
this._width = this.canvas.width;
|
|
3896
4872
|
this._height = this.canvas.height;
|
|
3897
|
-
this.camera.
|
|
4873
|
+
this.camera.setAspect(this.canvas.clientWidth || this._width, this.canvas.clientHeight || this._height);
|
|
3898
4874
|
this.depthTexture = this.device.createTexture({
|
|
3899
4875
|
size: [this._width, this._height],
|
|
3900
4876
|
format: "depth24plus",
|
|
@@ -3924,7 +4900,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3924
4900
|
};
|
|
3925
4901
|
const vertex = createMeshVertex(this.meshLayout);
|
|
3926
4902
|
const fragment = createMeshFragment(this.meshLayout);
|
|
3927
|
-
const { code: wgslCode } =
|
|
4903
|
+
const { code: wgslCode } = import_typegpu10.default.resolveWithContext([vertex, fragment]);
|
|
3928
4904
|
const shaderModule = this.device.createShaderModule({ code: wgslCode });
|
|
3929
4905
|
const rawBGL = this.root.unwrap(this.meshLayout);
|
|
3930
4906
|
this.rawPipeline = this.device.createRenderPipeline({
|
|
@@ -3937,7 +4913,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3937
4913
|
const texLayout = createTextureBindGroupLayout();
|
|
3938
4914
|
const texVertex = createTexturedMeshVertex(this.meshLayout);
|
|
3939
4915
|
const texFragment = createTexturedMeshFragment(this.meshLayout, texLayout);
|
|
3940
|
-
const { code: texWgslCode } =
|
|
4916
|
+
const { code: texWgslCode } = import_typegpu10.default.resolveWithContext([texVertex, texFragment]);
|
|
3941
4917
|
const texShaderModule = this.device.createShaderModule({ code: texWgslCode });
|
|
3942
4918
|
const rawTexBGL = this.root.unwrap(texLayout);
|
|
3943
4919
|
this.rawTexturedPipeline = this.device.createRenderPipeline({
|
|
@@ -3951,17 +4927,20 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3951
4927
|
this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.maxInstances)).$usage("storage");
|
|
3952
4928
|
this.uniformBuffer = this.root.createBuffer(MeshUniforms).$usage("uniform");
|
|
3953
4929
|
this.slotIndexBuffer = this.root.createBuffer(d.arrayOf(d.u32, this.maxInstances)).$usage("storage");
|
|
4930
|
+
this.lightBuffer = this.root.createBuffer(d.arrayOf(Light, MAX_LIGHTS)).$usage("storage");
|
|
3954
4931
|
this.rawDynamicBuffer = this.root.unwrap(this.dynamicBuffer);
|
|
3955
4932
|
this.rawStaticBuffer = this.root.unwrap(this.staticBuffer);
|
|
3956
4933
|
this.rawUniformBuffer = this.root.unwrap(this.uniformBuffer);
|
|
3957
4934
|
this.rawSlotIndexBuffer = this.root.unwrap(this.slotIndexBuffer);
|
|
4935
|
+
this.rawLightBuffer = this.root.unwrap(this.lightBuffer);
|
|
3958
4936
|
this.rawBindGroup = this.device.createBindGroup({
|
|
3959
4937
|
layout: rawBGL,
|
|
3960
4938
|
entries: [
|
|
3961
4939
|
{ binding: 0, resource: { buffer: this.rawUniformBuffer } },
|
|
3962
4940
|
{ binding: 1, resource: { buffer: this.rawDynamicBuffer } },
|
|
3963
4941
|
{ binding: 2, resource: { buffer: this.rawStaticBuffer } },
|
|
3964
|
-
{ binding: 3, resource: { buffer: this.rawSlotIndexBuffer } }
|
|
4942
|
+
{ binding: 3, resource: { buffer: this.rawSlotIndexBuffer } },
|
|
4943
|
+
{ binding: 4, resource: { buffer: this.rawLightBuffer } }
|
|
3965
4944
|
]
|
|
3966
4945
|
});
|
|
3967
4946
|
const msi = this.maxSkinnedInstances;
|
|
@@ -3983,8 +4962,8 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3983
4962
|
]
|
|
3984
4963
|
};
|
|
3985
4964
|
const skinnedVertex = createSkinnedMeshVertex(this.skinnedMeshLayout);
|
|
3986
|
-
const skinnedFragment =
|
|
3987
|
-
const { code: skinnedWgsl } =
|
|
4965
|
+
const skinnedFragment = createSkinnedMeshFragment(this.skinnedMeshLayout);
|
|
4966
|
+
const { code: skinnedWgsl } = import_typegpu10.default.resolveWithContext([skinnedVertex, skinnedFragment]);
|
|
3988
4967
|
const skinnedShaderModule = this.device.createShaderModule({ code: skinnedWgsl });
|
|
3989
4968
|
const rawSkinnedBGL = this.root.unwrap(this.skinnedMeshLayout);
|
|
3990
4969
|
this.rawSkinnedPipeline = this.device.createRenderPipeline({
|
|
@@ -3996,7 +4975,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3996
4975
|
});
|
|
3997
4976
|
const skinnedTexVertex = createSkinnedMeshVertex(this.skinnedMeshLayout);
|
|
3998
4977
|
const skinnedTexFragment = createTexturedMeshFragment(this.skinnedMeshLayout, texLayout);
|
|
3999
|
-
const { code: skinnedTexWgsl } =
|
|
4978
|
+
const { code: skinnedTexWgsl } = import_typegpu10.default.resolveWithContext([skinnedTexVertex, skinnedTexFragment]);
|
|
4000
4979
|
const skinnedTexShaderModule = this.device.createShaderModule({ code: skinnedTexWgsl });
|
|
4001
4980
|
this.rawSkinnedTexturedPipeline = this.device.createRenderPipeline({
|
|
4002
4981
|
layout: this.device.createPipelineLayout({ bindGroupLayouts: [rawSkinnedBGL, rawTexBGL] }),
|
|
@@ -4020,12 +4999,14 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4020
4999
|
{ binding: 1, resource: { buffer: this.rawSkinnedDynamicBuffer } },
|
|
4021
5000
|
{ binding: 2, resource: { buffer: this.rawSkinnedStaticBuffer } },
|
|
4022
5001
|
{ binding: 3, resource: { buffer: this.rawSkinnedSlotIndexBuffer } },
|
|
4023
|
-
{ binding: 4, resource: { buffer: this.rawBoneMatrixBuffer } }
|
|
5002
|
+
{ binding: 4, resource: { buffer: this.rawBoneMatrixBuffer } },
|
|
5003
|
+
{ binding: 5, resource: { buffer: this.rawLightBuffer } }
|
|
4024
5004
|
]
|
|
4025
5005
|
});
|
|
4026
5006
|
if (this._prefabs) {
|
|
4027
5007
|
this.uploadPrefabBucket(this._prefabs);
|
|
4028
5008
|
}
|
|
5009
|
+
this.hitboxDebug.init(this.device, this.format);
|
|
4029
5010
|
this.setupResizeObserver();
|
|
4030
5011
|
this._initialized = true;
|
|
4031
5012
|
}
|
|
@@ -4095,7 +5076,10 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4095
5076
|
alphaMode: "premultiplied"
|
|
4096
5077
|
});
|
|
4097
5078
|
}
|
|
4098
|
-
|
|
5079
|
+
const cssBox = entry.contentBoxSize?.[0];
|
|
5080
|
+
const cssW = cssBox ? cssBox.inlineSize : w;
|
|
5081
|
+
const cssH = cssBox ? cssBox.blockSize : h;
|
|
5082
|
+
this.camera.setAspect(cssW, cssH);
|
|
4099
5083
|
this.depthTexture.destroy();
|
|
4100
5084
|
this.depthTexture = this.device.createTexture({
|
|
4101
5085
|
size: [w, h],
|
|
@@ -4149,6 +5133,33 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4149
5133
|
get maxSkinned() {
|
|
4150
5134
|
return this.maxSkinnedInstances;
|
|
4151
5135
|
}
|
|
5136
|
+
/**
|
|
5137
|
+
* Add a dynamic point or spot light. Returns a live handle whose position,
|
|
5138
|
+
* color, intensity, range, and enabled state can all be changed every frame.
|
|
5139
|
+
* Up to `MAX_LIGHTS` (64) lights may be live at once; throws past that.
|
|
5140
|
+
*
|
|
5141
|
+
* The global directional + ambient terms are separate — see
|
|
5142
|
+
* `setDirectionalLight` / `setAmbient`.
|
|
5143
|
+
*/
|
|
5144
|
+
addLight(spec) {
|
|
5145
|
+
return this.lights.add(spec);
|
|
5146
|
+
}
|
|
5147
|
+
/**
|
|
5148
|
+
* Set the global directional light (the "sun"). `direction` points from the
|
|
5149
|
+
* surface toward the light. Defaults to `(0.3, 0.8, 0.5)`, white, intensity 1
|
|
5150
|
+
* — the engine's classic fixed look.
|
|
5151
|
+
*/
|
|
5152
|
+
setDirectionalLight(direction, color = [1, 1, 1], intensity = 1) {
|
|
5153
|
+
this.lights.setDirectional(direction, color, intensity);
|
|
5154
|
+
}
|
|
5155
|
+
/** Set the global ambient term. Defaults to `(0.3, 0.3, 0.3)`. */
|
|
5156
|
+
setAmbient(color) {
|
|
5157
|
+
this.lights.setAmbient(color);
|
|
5158
|
+
}
|
|
5159
|
+
/** Number of live dynamic lights. */
|
|
5160
|
+
get lightCount() {
|
|
5161
|
+
return this.lights.count;
|
|
5162
|
+
}
|
|
4152
5163
|
/**
|
|
4153
5164
|
* Create a flat grid mesh on the XZ plane at Y=0.
|
|
4154
5165
|
*
|
|
@@ -4439,13 +5450,30 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4439
5450
|
const uvs = data.uvs ?? new Float32Array(vertexCount * 2);
|
|
4440
5451
|
const hasTexture = !!texture;
|
|
4441
5452
|
let maxRadiusSq = 0;
|
|
5453
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
5454
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
4442
5455
|
for (let i = 0; i < vertexCount; i++) {
|
|
4443
5456
|
const px = positions[i * 3], py = positions[i * 3 + 1], pz = positions[i * 3 + 2];
|
|
4444
5457
|
const rSq = px * px + py * py + pz * pz;
|
|
4445
5458
|
if (rSq > maxRadiusSq)
|
|
4446
5459
|
maxRadiusSq = rSq;
|
|
5460
|
+
if (px < minX)
|
|
5461
|
+
minX = px;
|
|
5462
|
+
if (px > maxX)
|
|
5463
|
+
maxX = px;
|
|
5464
|
+
if (py < minY)
|
|
5465
|
+
minY = py;
|
|
5466
|
+
if (py > maxY)
|
|
5467
|
+
maxY = py;
|
|
5468
|
+
if (pz < minZ)
|
|
5469
|
+
minZ = pz;
|
|
5470
|
+
if (pz > maxZ)
|
|
5471
|
+
maxZ = pz;
|
|
4447
5472
|
}
|
|
4448
5473
|
const boundingRadius = Math.sqrt(maxRadiusSq);
|
|
5474
|
+
const halfX = vertexCount ? (maxX - minX) * 0.5 : 0;
|
|
5475
|
+
const halfY = vertexCount ? (maxY - minY) * 0.5 : 0;
|
|
5476
|
+
const halfZ = vertexCount ? (maxZ - minZ) * 0.5 : 0;
|
|
4449
5477
|
const interleaved = new Float32Array(vertexCount * 8);
|
|
4450
5478
|
for (let i = 0; i < vertexCount; i++) {
|
|
4451
5479
|
const o = i * 8;
|
|
@@ -4506,6 +5534,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4506
5534
|
indexCount,
|
|
4507
5535
|
indexFormat: indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
4508
5536
|
boundingRadius,
|
|
5537
|
+
halfX,
|
|
5538
|
+
halfY,
|
|
5539
|
+
halfZ,
|
|
4509
5540
|
hasTexture,
|
|
4510
5541
|
textureBindGroup,
|
|
4511
5542
|
skinned: false,
|
|
@@ -4524,13 +5555,30 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4524
5555
|
const uvs = data.uvs ?? new Float32Array(vertexCount * 2);
|
|
4525
5556
|
const hasTexture = !!texture;
|
|
4526
5557
|
let maxRadiusSq = 0;
|
|
5558
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
5559
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
4527
5560
|
for (let i = 0; i < vertexCount; i++) {
|
|
4528
5561
|
const px = positions[i * 3], py = positions[i * 3 + 1], pz = positions[i * 3 + 2];
|
|
4529
5562
|
const rSq = px * px + py * py + pz * pz;
|
|
4530
5563
|
if (rSq > maxRadiusSq)
|
|
4531
5564
|
maxRadiusSq = rSq;
|
|
5565
|
+
if (px < minX)
|
|
5566
|
+
minX = px;
|
|
5567
|
+
if (px > maxX)
|
|
5568
|
+
maxX = px;
|
|
5569
|
+
if (py < minY)
|
|
5570
|
+
minY = py;
|
|
5571
|
+
if (py > maxY)
|
|
5572
|
+
maxY = py;
|
|
5573
|
+
if (pz < minZ)
|
|
5574
|
+
minZ = pz;
|
|
5575
|
+
if (pz > maxZ)
|
|
5576
|
+
maxZ = pz;
|
|
4532
5577
|
}
|
|
4533
5578
|
const boundingRadius = Math.sqrt(maxRadiusSq);
|
|
5579
|
+
const halfX = vertexCount ? (maxX - minX) * 0.5 : 0;
|
|
5580
|
+
const halfY = vertexCount ? (maxY - minY) * 0.5 : 0;
|
|
5581
|
+
const halfZ = vertexCount ? (maxZ - minZ) * 0.5 : 0;
|
|
4534
5582
|
const buf = new ArrayBuffer(vertexCount * 56);
|
|
4535
5583
|
const floatView = new Float32Array(buf);
|
|
4536
5584
|
const u16View = new Uint16Array(buf);
|
|
@@ -4602,6 +5650,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4602
5650
|
indexCount,
|
|
4603
5651
|
indexFormat: indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
4604
5652
|
boundingRadius,
|
|
5653
|
+
halfX,
|
|
5654
|
+
halfY,
|
|
5655
|
+
halfZ,
|
|
4605
5656
|
hasTexture,
|
|
4606
5657
|
textureBindGroup,
|
|
4607
5658
|
skinned: true,
|
|
@@ -4660,7 +5711,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4660
5711
|
* ```
|
|
4661
5712
|
*/
|
|
4662
5713
|
async loadGltf(url, opts) {
|
|
4663
|
-
const parsed = await (0,
|
|
5714
|
+
const parsed = await (0, import_renderer7.parseGltf)(url, opts);
|
|
4664
5715
|
return this.uploadParsedGltf(parsed);
|
|
4665
5716
|
}
|
|
4666
5717
|
/**
|
|
@@ -4673,7 +5724,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4673
5724
|
let skinnedModelSkinIndex = -1;
|
|
4674
5725
|
if (parsed.skin) {
|
|
4675
5726
|
const { data: skinData, animClips } = parsed.skin;
|
|
4676
|
-
const animation = new
|
|
5727
|
+
const animation = new import_renderer7.SkeletalAnimation(skinData, animClips);
|
|
4677
5728
|
const ibm = skinData.inverseBindMatrices;
|
|
4678
5729
|
let maxRadSq = 0;
|
|
4679
5730
|
for (let j = 0; j < skinData.jointCount; j++) {
|
|
@@ -4690,7 +5741,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4690
5741
|
boundingRadius: skinnedRadius,
|
|
4691
5742
|
parsedSkin: parsed.skin
|
|
4692
5743
|
});
|
|
4693
|
-
(0,
|
|
5744
|
+
(0, import_renderer7.packSkinAndAnimations)(this.packedAnimData, skinData, animClips);
|
|
4694
5745
|
this.animComputeNeedsRebuild = true;
|
|
4695
5746
|
}
|
|
4696
5747
|
const handles = [];
|
|
@@ -4781,7 +5832,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4781
5832
|
const posOut = [0, 0, 0];
|
|
4782
5833
|
const rotOut = [0, 0, 0];
|
|
4783
5834
|
const sclOut = [0, 0, 0];
|
|
5835
|
+
const id = ++this.nextInstanceId;
|
|
4784
5836
|
const handle = {
|
|
5837
|
+
id,
|
|
4785
5838
|
slot,
|
|
4786
5839
|
modelId: modelHandle.id,
|
|
4787
5840
|
skinned: false,
|
|
@@ -4835,9 +5888,11 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4835
5888
|
self.freeList.free(slot);
|
|
4836
5889
|
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4837
5890
|
staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
|
|
5891
|
+
self.instanceHandles[slot] = null;
|
|
4838
5892
|
self.staticDirty = true;
|
|
4839
5893
|
}
|
|
4840
5894
|
};
|
|
5895
|
+
this.instanceHandles[slot] = handle;
|
|
4841
5896
|
return handle;
|
|
4842
5897
|
}
|
|
4843
5898
|
addGltfInstance(opts, gltf, prefabId) {
|
|
@@ -4859,6 +5914,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4859
5914
|
const skinnedHandle = childHandles.find((h) => h.skinned);
|
|
4860
5915
|
const lead = childHandles[0];
|
|
4861
5916
|
return {
|
|
5917
|
+
id: lead.id,
|
|
4862
5918
|
skinned: gltf.skinned,
|
|
4863
5919
|
prefabId,
|
|
4864
5920
|
setPosition(x, y, z) {
|
|
@@ -4946,6 +6002,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4946
6002
|
childHandles.push(this.addInstance(partOpts));
|
|
4947
6003
|
}
|
|
4948
6004
|
return {
|
|
6005
|
+
id: childHandles[0].id,
|
|
4949
6006
|
skinned: childHandles.some((h) => h.skinned),
|
|
4950
6007
|
prefabId: composite.id,
|
|
4951
6008
|
setPosition(x, y, z) {
|
|
@@ -5069,7 +6126,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5069
6126
|
const posOut = [0, 0, 0];
|
|
5070
6127
|
const rotOut = [0, 0, 0];
|
|
5071
6128
|
const sclOut = [0, 0, 0];
|
|
5072
|
-
|
|
6129
|
+
const id = ++this.nextInstanceId;
|
|
6130
|
+
const handle = {
|
|
6131
|
+
id,
|
|
5073
6132
|
slot,
|
|
5074
6133
|
modelId: modelHandle.id,
|
|
5075
6134
|
skinned: true,
|
|
@@ -5134,6 +6193,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5134
6193
|
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
5135
6194
|
staticData.fill(0, statBase, statBase + SKINNED_STATIC_MESH_FLOATS);
|
|
5136
6195
|
animStates[slot] = null;
|
|
6196
|
+
self.skinnedInstanceHandles[slot] = null;
|
|
5137
6197
|
self.skinnedStaticDirty = true;
|
|
5138
6198
|
if (--self.boneOffsetRefcount[capturedBoneOffset] === 0) {
|
|
5139
6199
|
let pool = self.freedBoneOffsets.get(capturedSkinIndex);
|
|
@@ -5145,6 +6205,8 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5145
6205
|
}
|
|
5146
6206
|
}
|
|
5147
6207
|
};
|
|
6208
|
+
this.skinnedInstanceHandles[slot] = handle;
|
|
6209
|
+
return handle;
|
|
5148
6210
|
}
|
|
5149
6211
|
/**
|
|
5150
6212
|
* Drain pending resyncs from the coordinator. Per affected skin: rebuild
|
|
@@ -5191,9 +6253,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5191
6253
|
animState.prevClipId = remap3[animState.prevClipId];
|
|
5192
6254
|
}
|
|
5193
6255
|
}
|
|
5194
|
-
this.packedAnimData = (0,
|
|
6256
|
+
this.packedAnimData = (0, import_renderer7.createPackedAnimationData)();
|
|
5195
6257
|
for (const sm of this.skinnedModels) {
|
|
5196
|
-
(0,
|
|
6258
|
+
(0, import_renderer7.packSkinAndAnimations)(this.packedAnimData, sm.parsedSkin.data, sm.parsedSkin.animClips);
|
|
5197
6259
|
}
|
|
5198
6260
|
if (this.tryUploadInPlace())
|
|
5199
6261
|
return;
|
|
@@ -5280,7 +6342,8 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5280
6342
|
{ binding: 1, resource: { buffer: this.rawSkinnedDynamicBuffer } },
|
|
5281
6343
|
{ binding: 2, resource: { buffer: this.rawSkinnedStaticBuffer } },
|
|
5282
6344
|
{ binding: 3, resource: { buffer: this.rawSkinnedSlotIndexBuffer } },
|
|
5283
|
-
{ binding: 4, resource: { buffer: rawBoneBuffer } }
|
|
6345
|
+
{ binding: 4, resource: { buffer: rawBoneBuffer } },
|
|
6346
|
+
{ binding: 5, resource: { buffer: this.rawLightBuffer } }
|
|
5284
6347
|
]
|
|
5285
6348
|
});
|
|
5286
6349
|
this.device.queue.writeBuffer(
|
|
@@ -5416,6 +6479,113 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5416
6479
|
sDyn[base + DYN_PREV_RZ] = sDyn[base + DYN_CURR_RZ];
|
|
5417
6480
|
}
|
|
5418
6481
|
});
|
|
6482
|
+
this.lights.storePrevious();
|
|
6483
|
+
}
|
|
6484
|
+
/** Resolve an instance's declared hitbox name to its Hitbox via the bucket's library. */
|
|
6485
|
+
resolveHitbox(handle) {
|
|
6486
|
+
if (!this._prefabs || !handle.prefabId)
|
|
6487
|
+
return null;
|
|
6488
|
+
const lib = this._prefabs.hitboxLibrary;
|
|
6489
|
+
if (!lib)
|
|
6490
|
+
return null;
|
|
6491
|
+
const prefab = this._prefabs.get(handle.prefabId);
|
|
6492
|
+
const name = prefab?.hitbox;
|
|
6493
|
+
return name ? lib.get(name) : null;
|
|
6494
|
+
}
|
|
6495
|
+
/**
|
|
6496
|
+
* Pick test for a single instance. Returns the ray-`t` and the struck
|
|
6497
|
+
* part name, or `null`. Uses the prefab's declared hitbox when
|
|
6498
|
+
* available; falls back to the model's axis-aligned bounding box.
|
|
6499
|
+
*/
|
|
6500
|
+
testInstanceRay(ray, handle, cx, cy, cz, sx, sy, sz, halfX, halfY, halfZ) {
|
|
6501
|
+
const hitbox = this.resolveHitbox(handle);
|
|
6502
|
+
if (hitbox) {
|
|
6503
|
+
const hit = (0, import_hitbox3.testHitbox3D)(ray, hitbox, cx, cy, cz, sx, sy, sz);
|
|
6504
|
+
return hit ? { distance: hit.distance, part: hit.part } : null;
|
|
6505
|
+
}
|
|
6506
|
+
const t = ray.entryBox(cx, cy, cz, halfX * sx, halfY * sy, halfZ * sz);
|
|
6507
|
+
return t === null ? null : { distance: t, part: null };
|
|
6508
|
+
}
|
|
6509
|
+
/** Push every instance the screen ray hits within [near, far] into the hit buffer. Unsorted. */
|
|
6510
|
+
_collectRaycastHitsInto(screenX, screenY, rc) {
|
|
6511
|
+
const ray = this.camera.screenToRay(screenX, screenY);
|
|
6512
|
+
const ox = ray.origin[0], oy = ray.origin[1], oz = ray.origin[2];
|
|
6513
|
+
const dx = ray.direction[0], dy = ray.direction[1], dz = ray.direction[2];
|
|
6514
|
+
const minDistance = this.camera.near;
|
|
6515
|
+
const maxDistance = this.camera.far;
|
|
6516
|
+
const dyn = this.dynamicData;
|
|
6517
|
+
const stat = this.staticData;
|
|
6518
|
+
const models = this.models;
|
|
6519
|
+
this.batcher.each((_, instances, count) => {
|
|
6520
|
+
for (let i = 0; i < count; i++) {
|
|
6521
|
+
const slot = instances[i];
|
|
6522
|
+
const handle = this.instanceHandles[slot];
|
|
6523
|
+
if (handle === null)
|
|
6524
|
+
continue;
|
|
6525
|
+
const model = models[handle.modelId];
|
|
6526
|
+
if (!model)
|
|
6527
|
+
continue;
|
|
6528
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6529
|
+
const statBase = slot * STATIC_MESH_FLOATS;
|
|
6530
|
+
const hit = this.testInstanceRay(
|
|
6531
|
+
ray,
|
|
6532
|
+
handle,
|
|
6533
|
+
dyn[dynBase + DYN_CURR_PX],
|
|
6534
|
+
dyn[dynBase + DYN_CURR_PY],
|
|
6535
|
+
dyn[dynBase + DYN_CURR_PZ],
|
|
6536
|
+
stat[statBase + STAT_SX],
|
|
6537
|
+
stat[statBase + STAT_SY],
|
|
6538
|
+
stat[statBase + STAT_SZ],
|
|
6539
|
+
model.halfX,
|
|
6540
|
+
model.halfY,
|
|
6541
|
+
model.halfZ
|
|
6542
|
+
);
|
|
6543
|
+
if (hit === null)
|
|
6544
|
+
continue;
|
|
6545
|
+
const t = hit.distance;
|
|
6546
|
+
if (t < minDistance || t > maxDistance)
|
|
6547
|
+
continue;
|
|
6548
|
+
rc.push(handle, t, ox + dx * t, oy + dy * t, oz + dz * t, t, hit.part);
|
|
6549
|
+
}
|
|
6550
|
+
});
|
|
6551
|
+
const sDyn = this.skinnedDynamicData;
|
|
6552
|
+
const sStat = this.skinnedStaticData;
|
|
6553
|
+
const skinned = this.skinnedModels;
|
|
6554
|
+
this.skinnedBatcher.each((_, instances, count) => {
|
|
6555
|
+
for (let i = 0; i < count; i++) {
|
|
6556
|
+
const slot = instances[i];
|
|
6557
|
+
const handle = this.skinnedInstanceHandles[slot];
|
|
6558
|
+
if (handle === null)
|
|
6559
|
+
continue;
|
|
6560
|
+
const model = models[handle.modelId];
|
|
6561
|
+
if (!model)
|
|
6562
|
+
continue;
|
|
6563
|
+
const skin = skinned[model.skinIndex];
|
|
6564
|
+
if (!skin)
|
|
6565
|
+
continue;
|
|
6566
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6567
|
+
const statBase = slot * SKINNED_STATIC_MESH_FLOATS;
|
|
6568
|
+
const hit = this.testInstanceRay(
|
|
6569
|
+
ray,
|
|
6570
|
+
handle,
|
|
6571
|
+
sDyn[dynBase + DYN_CURR_PX],
|
|
6572
|
+
sDyn[dynBase + DYN_CURR_PY],
|
|
6573
|
+
sDyn[dynBase + DYN_CURR_PZ],
|
|
6574
|
+
sStat[statBase + SSTAT_SX],
|
|
6575
|
+
sStat[statBase + SSTAT_SY],
|
|
6576
|
+
sStat[statBase + SSTAT_SZ],
|
|
6577
|
+
model.halfX,
|
|
6578
|
+
model.halfY,
|
|
6579
|
+
model.halfZ
|
|
6580
|
+
);
|
|
6581
|
+
if (hit === null)
|
|
6582
|
+
continue;
|
|
6583
|
+
const t = hit.distance;
|
|
6584
|
+
if (t < minDistance || t > maxDistance)
|
|
6585
|
+
continue;
|
|
6586
|
+
rc.push(handle, t, ox + dx * t, oy + dy * t, oz + dz * t, t, hit.part);
|
|
6587
|
+
}
|
|
6588
|
+
});
|
|
5419
6589
|
}
|
|
5420
6590
|
render(alpha) {
|
|
5421
6591
|
if (!this._initialized)
|
|
@@ -5444,18 +6614,26 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5444
6614
|
);
|
|
5445
6615
|
this.staticDirty = false;
|
|
5446
6616
|
}
|
|
6617
|
+
const packed = this.lights.pack();
|
|
6618
|
+
if (packed.count > 0) {
|
|
6619
|
+
this.device.queue.writeBuffer(
|
|
6620
|
+
this.rawLightBuffer,
|
|
6621
|
+
0,
|
|
6622
|
+
packed.data.buffer,
|
|
6623
|
+
packed.data.byteOffset,
|
|
6624
|
+
packed.byteLength
|
|
6625
|
+
);
|
|
6626
|
+
}
|
|
5447
6627
|
const vpMatrix = this.camera.getViewProjectionMatrix();
|
|
5448
6628
|
this.uniformData.set(vpMatrix, 0);
|
|
5449
|
-
this.uniformData[
|
|
5450
|
-
this.uniformData
|
|
5451
|
-
this.uniformData[18] = 0.8;
|
|
5452
|
-
this.uniformData[19] = 0.5;
|
|
6629
|
+
this.uniformData[MESH_UNIFORM_ALPHA_OFFSET] = alpha;
|
|
6630
|
+
this.lights.writeUniforms(this.uniformData, MESH_UNIFORM_LIGHT_OFFSET, packed.count);
|
|
5453
6631
|
this.device.queue.writeBuffer(
|
|
5454
6632
|
this.rawUniformBuffer,
|
|
5455
6633
|
0,
|
|
5456
6634
|
this.uniformData.buffer,
|
|
5457
6635
|
this.uniformData.byteOffset,
|
|
5458
|
-
|
|
6636
|
+
this.uniformData.byteLength
|
|
5459
6637
|
);
|
|
5460
6638
|
this.extractFrustumPlanes(vpMatrix);
|
|
5461
6639
|
let indexOffset = 0;
|
|
@@ -5631,9 +6809,76 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
5631
6809
|
pass.draw(model.vertexCount, batch.count, 0, batch.offset);
|
|
5632
6810
|
}
|
|
5633
6811
|
}
|
|
6812
|
+
if (this.debug.hitboxes) {
|
|
6813
|
+
this.drawDebugHitboxes(pass, vpMatrix);
|
|
6814
|
+
}
|
|
5634
6815
|
pass.end();
|
|
5635
6816
|
this.device.queue.submit([encoder.finish()]);
|
|
5636
6817
|
}
|
|
6818
|
+
drawDebugHitboxes(pass, vp) {
|
|
6819
|
+
if (!this._prefabs)
|
|
6820
|
+
return;
|
|
6821
|
+
const debug = this.hitboxDebug;
|
|
6822
|
+
const state = this.raycast.state;
|
|
6823
|
+
debug.begin(vp);
|
|
6824
|
+
const dyn = this.dynamicData;
|
|
6825
|
+
const stat = this.staticData;
|
|
6826
|
+
this.batcher.each((_, instances, count) => {
|
|
6827
|
+
for (let i = 0; i < count; i++) {
|
|
6828
|
+
const slot = instances[i];
|
|
6829
|
+
const handle = this.instanceHandles[slot];
|
|
6830
|
+
if (handle === null)
|
|
6831
|
+
continue;
|
|
6832
|
+
const hb = this.resolveHitbox(handle);
|
|
6833
|
+
if (!hb)
|
|
6834
|
+
continue;
|
|
6835
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6836
|
+
const statBase = slot * STATIC_MESH_FLOATS;
|
|
6837
|
+
const hovered = state.containsId(handle.id);
|
|
6838
|
+
for (const part of hb.parts) {
|
|
6839
|
+
debug.emit(
|
|
6840
|
+
part,
|
|
6841
|
+
hovered,
|
|
6842
|
+
dyn[dynBase + DYN_CURR_PX],
|
|
6843
|
+
dyn[dynBase + DYN_CURR_PY],
|
|
6844
|
+
dyn[dynBase + DYN_CURR_PZ],
|
|
6845
|
+
stat[statBase + STAT_SX],
|
|
6846
|
+
stat[statBase + STAT_SY],
|
|
6847
|
+
stat[statBase + STAT_SZ]
|
|
6848
|
+
);
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
});
|
|
6852
|
+
const sDyn = this.skinnedDynamicData;
|
|
6853
|
+
const sStat = this.skinnedStaticData;
|
|
6854
|
+
this.skinnedBatcher.each((_, instances, count) => {
|
|
6855
|
+
for (let i = 0; i < count; i++) {
|
|
6856
|
+
const slot = instances[i];
|
|
6857
|
+
const handle = this.skinnedInstanceHandles[slot];
|
|
6858
|
+
if (handle === null)
|
|
6859
|
+
continue;
|
|
6860
|
+
const hb = this.resolveHitbox(handle);
|
|
6861
|
+
if (!hb)
|
|
6862
|
+
continue;
|
|
6863
|
+
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
6864
|
+
const statBase = slot * SKINNED_STATIC_MESH_FLOATS;
|
|
6865
|
+
const hovered = state.containsId(handle.id);
|
|
6866
|
+
for (const part of hb.parts) {
|
|
6867
|
+
debug.emit(
|
|
6868
|
+
part,
|
|
6869
|
+
hovered,
|
|
6870
|
+
sDyn[dynBase + DYN_CURR_PX],
|
|
6871
|
+
sDyn[dynBase + DYN_CURR_PY],
|
|
6872
|
+
sDyn[dynBase + DYN_CURR_PZ],
|
|
6873
|
+
sStat[statBase + SSTAT_SX],
|
|
6874
|
+
sStat[statBase + SSTAT_SY],
|
|
6875
|
+
sStat[statBase + SSTAT_SZ]
|
|
6876
|
+
);
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
});
|
|
6880
|
+
debug.flush(pass);
|
|
6881
|
+
}
|
|
5637
6882
|
/**
|
|
5638
6883
|
* Extract 6 frustum planes from a column-major VP matrix.
|
|
5639
6884
|
* Each plane is [a, b, c, d] where ax + by + cz + d >= 0 means inside.
|
|
@@ -5814,12 +7059,12 @@ var ParticleEmitter = class {
|
|
|
5814
7059
|
this.renderer = renderer;
|
|
5815
7060
|
this.config = config;
|
|
5816
7061
|
this.rng = new import_simple_rng.SimpleRNG(config.seed);
|
|
5817
|
-
const
|
|
5818
|
-
this.sprites = new Array(
|
|
5819
|
-
this.lifetimes = new Float32Array(
|
|
5820
|
-
this.maxLifetimes = new Float32Array(
|
|
5821
|
-
this.velocitiesX = new Float32Array(
|
|
5822
|
-
this.velocitiesY = new Float32Array(
|
|
7062
|
+
const max3 = config.max;
|
|
7063
|
+
this.sprites = new Array(max3).fill(null);
|
|
7064
|
+
this.lifetimes = new Float32Array(max3);
|
|
7065
|
+
this.maxLifetimes = new Float32Array(max3);
|
|
7066
|
+
this.velocitiesX = new Float32Array(max3);
|
|
7067
|
+
this.velocitiesY = new Float32Array(max3);
|
|
5823
7068
|
}
|
|
5824
7069
|
emit(x, y, count = 1) {
|
|
5825
7070
|
for (let i = 0; i < count; i++) {
|
|
@@ -5895,58 +7140,3 @@ var ParticleEmitter = class {
|
|
|
5895
7140
|
this.head = 0;
|
|
5896
7141
|
}
|
|
5897
7142
|
};
|
|
5898
|
-
|
|
5899
|
-
// src/shaders/utils.ts
|
|
5900
|
-
var import_typegpu11 = __toESM(require("typegpu"), 1);
|
|
5901
|
-
var d6 = __toESM(require("typegpu/data"), 1);
|
|
5902
|
-
var std4 = __toESM(require("typegpu/std"), 1);
|
|
5903
|
-
var rotate2d = import_typegpu11.default.fn([d6.vec2f, d6.f32], d6.vec2f)(
|
|
5904
|
-
function rotate2d2(point, angle) {
|
|
5905
|
-
"use gpu";
|
|
5906
|
-
const c = std4.cos(angle);
|
|
5907
|
-
const s = std4.sin(angle);
|
|
5908
|
-
return d6.vec2f(
|
|
5909
|
-
point.x * c - point.y * s,
|
|
5910
|
-
point.x * s + point.y * c
|
|
5911
|
-
);
|
|
5912
|
-
}
|
|
5913
|
-
);
|
|
5914
|
-
var worldToClip2d = import_typegpu11.default.fn([d6.vec2f, d6.mat3x3f], d6.vec4f)(
|
|
5915
|
-
function worldToClip2d2(worldPos, cameraMatrix) {
|
|
5916
|
-
"use gpu";
|
|
5917
|
-
const clip = cameraMatrix * d6.vec3f(worldPos.x, worldPos.y, 1);
|
|
5918
|
-
return d6.vec4f(clip.x, clip.y, 0, 1);
|
|
5919
|
-
}
|
|
5920
|
-
);
|
|
5921
|
-
var worldToClip3d = import_typegpu11.default.fn([d6.vec3f, d6.mat4x4f], d6.vec4f)(
|
|
5922
|
-
function worldToClip3d2(worldPos, vpMatrix) {
|
|
5923
|
-
"use gpu";
|
|
5924
|
-
return vpMatrix * d6.vec4f(worldPos.x, worldPos.y, worldPos.z, 1);
|
|
5925
|
-
}
|
|
5926
|
-
);
|
|
5927
|
-
var remap = import_typegpu11.default.fn([d6.f32, d6.f32, d6.f32, d6.f32, d6.f32], d6.f32)(
|
|
5928
|
-
function remap2(value, inMin, inMax, outMin, outMax) {
|
|
5929
|
-
"use gpu";
|
|
5930
|
-
const t = (value - inMin) / (inMax - inMin);
|
|
5931
|
-
return outMin + t * (outMax - outMin);
|
|
5932
|
-
}
|
|
5933
|
-
);
|
|
5934
|
-
var scaleRotate2d = import_typegpu11.default.fn([d6.vec2f, d6.f32], d6.mat2x2f)(
|
|
5935
|
-
function scaleRotate2d2(scale, angle) {
|
|
5936
|
-
"use gpu";
|
|
5937
|
-
const c = std4.cos(angle);
|
|
5938
|
-
const s = std4.sin(angle);
|
|
5939
|
-
return d6.mat2x2f(
|
|
5940
|
-
scale.x * c,
|
|
5941
|
-
scale.x * s,
|
|
5942
|
-
-(scale.y * s),
|
|
5943
|
-
scale.y * c
|
|
5944
|
-
);
|
|
5945
|
-
}
|
|
5946
|
-
);
|
|
5947
|
-
var inverseLerp = import_typegpu11.default.fn([d6.f32, d6.f32, d6.f32], d6.f32)(
|
|
5948
|
-
function inverseLerp2(min, max2, value) {
|
|
5949
|
-
"use gpu";
|
|
5950
|
-
return std4.saturate((value - min) / (max2 - min));
|
|
5951
|
-
}
|
|
5952
|
-
);
|