@downdraft/engine 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/src/renderer/bootstrap.ts +0 -6
- package/app/src/renderer/feature-log.ts +1 -9
- package/app/src/renderer/mcp-harness.ts +8 -11
- package/app/src/renderer/standard-automation-tools.ts +8 -145
- package/core/src/module/host.ts +3 -6
- package/core/src/module/index.ts +1 -1
- package/core/src/module/module.ts +4 -47
- package/core/src/physics/interface.ts +5 -0
- package/core/src/platform/render-surface.ts +13 -0
- package/core/src/render/canvas-resize-watcher.ts +1 -10
- package/core/src/render/game-renderer.ts +40 -18
- package/core/src/render/index.ts +0 -2
- package/core/src/render/input-manager.ts +7 -7
- package/core/src/render/renderer-module-host.ts +2 -4
- package/core/src/ui-font-scale.ts +5 -38
- package/core/src/worker/rpc.ts +65 -0
- package/dist/.dd-manifest.json +859 -884
- package/dist/app/src/renderer/bootstrap.js +0 -4
- package/dist/app/src/renderer/feature-log.js +1 -10
- package/dist/app/src/renderer/mcp-harness.js +8 -11
- package/dist/app/src/renderer/standard-automation-tools.js +6 -138
- package/dist/core/src/module/host.js +3 -6
- package/dist/core/src/render/canvas-resize-watcher.js +1 -10
- package/dist/core/src/render/game-renderer.js +44 -18
- package/dist/core/src/render/index.js +0 -1
- package/dist/core/src/render/input-manager.js +7 -7
- package/dist/core/src/render/renderer-module-host.js +2 -4
- package/dist/core/src/ui-font-scale.js +5 -38
- package/dist/core/src/worker/rpc.js +59 -0
- package/dist/libraries/blitz-ui/src/native-module.js +8 -1
- package/dist/libraries/devtools/src/native-providers.js +0 -2
- package/dist/libraries/entities/src/model-renderer.js +87 -27
- package/dist/libraries/entities/src/shaders/model.wgsl +13 -4
- package/dist/libraries/networking/src/index.js +1 -0
- package/dist/libraries/networking/src/ws-server.js +387 -0
- package/dist/libraries/physics-rapier/src/api.js +15 -0
- package/dist/libraries/physics-rapier/src/backend.js +15 -0
- package/dist/libraries/physics-rapier/src/rapier-backend.js +5 -0
- package/dist/libraries/postfx/src/post-process-stack.js +1 -3
- package/dist/libraries/sand/src/materials.js +2 -32
- package/dist/libraries/sand/src/sand-world.js +0 -3
- package/dist/modules/devtools/src/api.js +8 -23
- package/dist/modules/devtools/src/data-bridge.js +0 -11
- package/dist/modules/devtools/src/index.js +0 -6
- package/dist/modules/devtools/src/init.js +8 -42
- package/dist/modules/devtools/src/scene-inspector.js +1 -4
- package/dist/modules/devtools/src/worker-sync.js +3 -11
- package/dist/test/src/vision-test.js +1 -3
- package/libraries/blitz-ui/src/native-module.ts +8 -1
- package/libraries/devtools/src/native-providers.ts +0 -2
- package/libraries/entities/src/model-renderer.ts +88 -27
- package/libraries/entities/src/shaders/model.wgsl +13 -4
- package/libraries/networking/src/index.ts +4 -0
- package/libraries/networking/src/ws-server.ts +397 -0
- package/libraries/physics-rapier/src/api.ts +15 -0
- package/libraries/physics-rapier/src/backend.ts +15 -0
- package/libraries/physics-rapier/src/rapier-backend.ts +6 -0
- package/libraries/postfx/src/post-process-stack.ts +1 -2
- package/libraries/sand/src/materials.ts +2 -32
- package/libraries/sand/src/sand-world.ts +0 -4
- package/modules/devtools/README.md +1 -3
- package/modules/devtools/src/api.ts +10 -34
- package/modules/devtools/src/data-bridge.ts +0 -16
- package/modules/devtools/src/index.ts +1 -11
- package/modules/devtools/src/init.ts +8 -51
- package/modules/devtools/src/scene-inspector.ts +2 -6
- package/modules/devtools/src/types.ts +5 -90
- package/modules/devtools/src/worker-sync.ts +3 -13
- package/package.json +4 -5
- package/test/src/vision-test.ts +2 -4
- package/core/src/render/sab-canvas-overlay.ts +0 -122
- package/dist/core/src/render/sab-canvas-overlay.js +0 -83
- package/dist/modules/devtools/src/debug-overlay.js +0 -181
- package/dist/modules/devtools/src/doctor-panel.js +0 -163
- package/dist/modules/devtools/src/label-overlay.js +0 -129
- package/dist/modules/devtools/src/material-stats-panel.js +0 -171
- package/dist/modules/devtools/src/sim-stats-panel.js +0 -250
- package/modules/devtools/src/debug-overlay.ts +0 -238
- package/modules/devtools/src/doctor-panel.ts +0 -182
- package/modules/devtools/src/label-overlay.ts +0 -154
- package/modules/devtools/src/material-stats-panel.ts +0 -188
- package/modules/devtools/src/sim-stats-panel.ts +0 -272
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
// 1. Creating an IDevToolsDataRenderer adapter
|
|
6
6
|
// 2. Instantiating DevToolsDataBridge (or BaseSceneInspector)
|
|
7
7
|
// 3. Calling init()
|
|
8
|
-
// 4. Wiring sim stats provider,
|
|
8
|
+
// 4. Wiring sim stats provider, debug overlay provider, etc.
|
|
9
9
|
//
|
|
10
|
-
// Also merges globally-registered
|
|
11
|
-
// singleton (registered by plugins via ctx.devtools.
|
|
10
|
+
// Also merges globally-registered feeds/commands from the `devtools`
|
|
11
|
+
// singleton (registered by plugins via ctx.devtools.registerDataFeed() etc.)
|
|
12
12
|
// and from worker manifests (via syncWorkerManifests).
|
|
13
13
|
//
|
|
14
14
|
// Returns the bridge instance for games that need post-init calls
|
|
@@ -22,7 +22,7 @@ import { BaseSceneInspector } from "./scene-inspector.js";
|
|
|
22
22
|
import { syncWorkerManifests } from "./worker-sync.js";
|
|
23
23
|
/**
|
|
24
24
|
* Initialize DevTools with one call. Creates the bridge, wires all providers,
|
|
25
|
-
|
|
25
|
+
* merges globally-registered feeds/commands, syncs worker manifests,
|
|
26
26
|
* and exposes the API on window.__sceneInspector.
|
|
27
27
|
*
|
|
28
28
|
* Returns the bridge instance.
|
|
@@ -34,19 +34,7 @@ export async function initDevTools(renderer, options = {}) {
|
|
|
34
34
|
const useSceneInspector = options.sceneInspector
|
|
35
35
|
|| !!options.bridgeClass
|
|
36
36
|
|| isSceneRenderer(renderer);
|
|
37
|
-
//
|
|
38
|
-
// (these merge with plugin-registered ones)
|
|
39
|
-
if (options.panels) {
|
|
40
|
-
options.panels.forEach((panel) => {
|
|
41
|
-
devtools.registerPanel(panel);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
if (options.overlayToggles) {
|
|
45
|
-
options.overlayToggles.forEach((toggle) => {
|
|
46
|
-
devtools.registerOverlayToggle(toggle);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
// 4. Sync worker manifests (fetches panels/feeds/commands from sim workers)
|
|
37
|
+
// 4. Sync worker manifests (fetches feeds/commands from sim workers)
|
|
50
38
|
if (options.workerHosts && options.workerHosts.length > 0) {
|
|
51
39
|
await syncWorkerManifests(options.workerHosts);
|
|
52
40
|
}
|
|
@@ -85,10 +73,8 @@ export async function initDevTools(renderer, options = {}) {
|
|
|
85
73
|
if (options.performanceMetricsProvider) {
|
|
86
74
|
wirePerformanceMetricsProvider(bridge, options.performanceMetricsProvider);
|
|
87
75
|
}
|
|
88
|
-
// 7. Merge globally-registered
|
|
89
|
-
//
|
|
90
|
-
// game-declared ones; we augment the __sceneInspector API with
|
|
91
|
-
// plugin-registered data feeds and commands.
|
|
76
|
+
// 7. Merge globally-registered feeds/commands/SAB stats into the bridge —
|
|
77
|
+
// augments the __sceneInspector API with plugin-registered methods.
|
|
92
78
|
mergeGlobalRegistrations(bridge);
|
|
93
79
|
// 8. Initialize the profiling system (if enabled)
|
|
94
80
|
if (options.profiling) {
|
|
@@ -134,35 +120,15 @@ function wirePerformanceMetricsProvider(bridge, provider) {
|
|
|
134
120
|
bridge.getPerformanceMetricsProvider = () => provider;
|
|
135
121
|
}
|
|
136
122
|
/**
|
|
137
|
-
* Merge globally-registered
|
|
123
|
+
* Merge globally-registered data feeds and commands from the `devtools`
|
|
138
124
|
* singleton into the bridge's __sceneInspector API.
|
|
139
125
|
*
|
|
140
|
-
* - Panels: merged into getPanelExtensions() (game panels + plugin panels)
|
|
141
126
|
* - Data feeds: added as methods on __sceneInspector (called synchronously)
|
|
142
127
|
* - Commands: added as methods on __sceneInspector (forwarded to worker or called directly)
|
|
143
128
|
* - SAB stats: added as methods on __sceneInspector (read from SAB)
|
|
144
129
|
*/
|
|
145
130
|
function mergeGlobalRegistrations(bridge) {
|
|
146
131
|
const manifest = devtools.getManifest();
|
|
147
|
-
// Augment getPanelExtensions to merge global registry panels
|
|
148
|
-
const originalGetPanels = bridge.getPanelExtensions?.bind(bridge);
|
|
149
|
-
bridge.getPanelExtensions = () => {
|
|
150
|
-
const gamePanels = originalGetPanels?.() ?? [];
|
|
151
|
-
// Merge: game panels take precedence on id collisions
|
|
152
|
-
const merged = new Map();
|
|
153
|
-
manifest.panels.forEach((p) => { merged.set(p.id, p); ; });
|
|
154
|
-
gamePanels.forEach((p) => { merged.set(p.id, p); ; });
|
|
155
|
-
return Array.from(merged.values()).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
156
|
-
};
|
|
157
|
-
// Augment getOverlayToggles to merge global registry toggles
|
|
158
|
-
const originalGetToggles = bridge.getOverlayToggles?.bind(bridge);
|
|
159
|
-
bridge.getOverlayToggles = () => {
|
|
160
|
-
const gameToggles = originalGetToggles?.() ?? [];
|
|
161
|
-
const merged = new Map();
|
|
162
|
-
manifest.toggles.forEach((t) => { merged.set(t.id, t); ; });
|
|
163
|
-
gameToggles.forEach((t) => { merged.set(t.id, t); ; });
|
|
164
|
-
return Array.from(merged.values());
|
|
165
|
-
};
|
|
166
132
|
// Add data feed methods to the __sceneInspector API
|
|
167
133
|
// These are called synchronously by the panel via callInspector()
|
|
168
134
|
const api = window.__sceneInspector;
|
|
@@ -27,7 +27,6 @@ export class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
27
27
|
getDebugOverlayProvider() {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
|
-
// getPanelExtensions() and getOverlayToggles() inherited from DevToolsDataBridge.
|
|
31
30
|
// --- Init ---
|
|
32
31
|
init(renderer) {
|
|
33
32
|
super.init(renderer);
|
|
@@ -510,9 +509,7 @@ export class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
510
509
|
const cosY = Math.cos(angleY), sinY = Math.sin(angleY);
|
|
511
510
|
const cosX = Math.cos(angleX), sinX = Math.sin(angleX);
|
|
512
511
|
const THUMB_SIZE = 192;
|
|
513
|
-
const canvas =
|
|
514
|
-
canvas.width = THUMB_SIZE;
|
|
515
|
-
canvas.height = THUMB_SIZE;
|
|
512
|
+
const canvas = new OffscreenCanvas(THUMB_SIZE, THUMB_SIZE);
|
|
516
513
|
const ctx = canvas.getContext("2d");
|
|
517
514
|
if (!ctx)
|
|
518
515
|
return null;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Renderer-side worker manifest sync — fetches the devtools manifest from
|
|
3
|
-
// one or more sim workers via worker RPC, then wires the worker-registered
|
|
4
|
-
// data feeds
|
|
3
|
+
// one or more sim workers via worker RPC, then wires the worker-registered
|
|
4
|
+
// data feeds and commands into the renderer-side __sceneInspector API.
|
|
5
5
|
//
|
|
6
6
|
// Data feeds from workers are read from the devtools SAB (zero-copy).
|
|
7
7
|
// Commands are forwarded via worker RPC.
|
|
8
|
-
// Panel declarations are merged into the global registry.
|
|
9
8
|
// ============================================================================
|
|
10
9
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
11
|
-
import { _devtoolsImpl
|
|
10
|
+
import { _devtoolsImpl } from "./api.js";
|
|
12
11
|
const log = createLogger("info");
|
|
13
12
|
/**
|
|
14
13
|
* Sync manifests from one or more workers into the renderer-side devtools
|
|
@@ -54,13 +53,6 @@ async function syncOneWorker(entry) {
|
|
|
54
53
|
// Worker may not have a devtools SAB (older workers)
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
|
-
// Register panels (namespaced by prefix to avoid collisions)
|
|
58
|
-
manifest.panels.forEach((panel) => {
|
|
59
|
-
const namespaced = prefix
|
|
60
|
-
? { ...panel, id: `${prefix}:${panel.id}` }
|
|
61
|
-
: panel;
|
|
62
|
-
devtools.registerPanel(namespaced);
|
|
63
|
-
});
|
|
64
56
|
// Register data feed readers — these read from the SAB synchronously
|
|
65
57
|
// We register them as main-realm data feeds that read from the worker SAB
|
|
66
58
|
if (sab) {
|
|
@@ -155,9 +155,7 @@ export class VisionTest {
|
|
|
155
155
|
}
|
|
156
156
|
async screenshotToDataURL(texture, width, height) {
|
|
157
157
|
const data = await this.captureTexture(texture, width, height);
|
|
158
|
-
const canvas =
|
|
159
|
-
canvas.width = width;
|
|
160
|
-
canvas.height = height;
|
|
158
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
161
159
|
const ctx = canvas.getContext("2d");
|
|
162
160
|
const imageData = ctx.createImageData(width, height);
|
|
163
161
|
imageData.data.set(data);
|
|
@@ -169,7 +169,14 @@ export function createBlitzUiNativeModule<S>(options: BlitzUiNativeOptions<S>):
|
|
|
169
169
|
const device = ctx.getDevice();
|
|
170
170
|
const view = ensureTexture(device);
|
|
171
171
|
const context = ctx.getSurface().getContext("webgpu");
|
|
172
|
-
|
|
172
|
+
// Join the in-flight frame, never start one: on a frame the
|
|
173
|
+
// game skipped (dirty-tracking), acquiring the surface here
|
|
174
|
+
// would present the UI blit over a blank swapchain image.
|
|
175
|
+
const peek = (context as { peekCurrentTexture?: () => GPUTexture | null } | null)
|
|
176
|
+
?.peekCurrentTexture;
|
|
177
|
+
const targetTex = typeof peek === "function"
|
|
178
|
+
? peek.call(context)
|
|
179
|
+
: context?.getCurrentTexture() ?? null;
|
|
173
180
|
const target = targetTex?.createView();
|
|
174
181
|
if (!view || !target || !targetTex) return;
|
|
175
182
|
|
|
@@ -525,8 +525,6 @@ async function collectWorkers(ctx: EngineProviderContext): Promise<PanelSnapshot
|
|
|
525
525
|
kind: "kv",
|
|
526
526
|
name: `Worker manifest (v${m.manifestVersion ?? "?"})`,
|
|
527
527
|
rows: [
|
|
528
|
-
kv("Panels", (m.panels ?? []).map((p: any) => p.id ?? p.name ?? "?").join(", ") || "(none)"),
|
|
529
|
-
kv("Toggles", (m.toggles ?? []).map((t: any) => t.id ?? "?").join(", ") || "(none)"),
|
|
530
528
|
kv("Data feeds", (m.dataFeeds ?? []).map((f: any) => `${f.name}@${f.writeRateHz}hz`).join(", ") || "(none)"),
|
|
531
529
|
kv("Commands", (m.commands ?? []).join(", ") || "(none)"),
|
|
532
530
|
],
|
|
@@ -86,9 +86,11 @@ export class ModelRenderer {
|
|
|
86
86
|
private reusableUniformsU32 = new Uint32Array(this.reusableUniforms.buffer);
|
|
87
87
|
private textureLoadVersion = new Map<string, number>();
|
|
88
88
|
|
|
89
|
-
// Skinning: a shared storage buffer of bone matrices (
|
|
90
|
-
//
|
|
91
|
-
// draws only.
|
|
89
|
+
// Skinning: a shared storage buffer of bone matrices (mat4 each), updated
|
|
90
|
+
// per-frame via updateSkinMatrices/updateSkinMatricesFor. Bound at @group(1)
|
|
91
|
+
// for skinned draws only. Each skinned nodeId owns a region of the buffer —
|
|
92
|
+
// the draw's `boneBase` uniform selects it so multiple skinned models can be
|
|
93
|
+
// animated independently in one frame.
|
|
92
94
|
private skinMatrixBuffer: GPUBuffer | null = null;
|
|
93
95
|
private skinBindGroupLayout: GPUBindGroupLayout | null = null;
|
|
94
96
|
private skinBindGroup: GPUBindGroup | null = null;
|
|
@@ -96,6 +98,16 @@ export class ModelRenderer {
|
|
|
96
98
|
private skinBindGroupSetThisFrame = false;
|
|
97
99
|
/** Current bone capacity (number of mat4s the skin buffer can hold). Grows on demand. */
|
|
98
100
|
private boneCapacity = ModelRenderer.INITIAL_BONE_CAPACITY;
|
|
101
|
+
/** nodeId → reserved bone region (offset+count in bones). */
|
|
102
|
+
private skinRegions = new Map<string, { offset: number; count: number }>();
|
|
103
|
+
/** nodeId → bone base index used by the skinning shader for that draw. */
|
|
104
|
+
private skinBaseByNode = new Map<string, number>();
|
|
105
|
+
/** Freed bone regions available for reuse (first-fit). */
|
|
106
|
+
private skinFreeList: Array<{ offset: number; count: number }> = [];
|
|
107
|
+
/** Next never-allocated bone index (high-water mark). */
|
|
108
|
+
private nextBoneOffset = 0;
|
|
109
|
+
/** Legacy caller region — updateSkinMatrices(m) without a nodeId. */
|
|
110
|
+
private static readonly DEFAULT_SKIN_NODE = "__skin_default";
|
|
99
111
|
|
|
100
112
|
// Bindless deps
|
|
101
113
|
private bindless: ModelRendererBindlessDeps | null = null;
|
|
@@ -822,6 +834,7 @@ export class ModelRenderer {
|
|
|
822
834
|
}
|
|
823
835
|
|
|
824
836
|
removeModel(nodeId: string): void {
|
|
837
|
+
this.releaseSkinRegion(nodeId);
|
|
825
838
|
const resources = this.modelResources.get(nodeId);
|
|
826
839
|
if (resources) {
|
|
827
840
|
for (let i = 0; i < resources.length; i++) {
|
|
@@ -1082,43 +1095,85 @@ export class ModelRenderer {
|
|
|
1082
1095
|
}
|
|
1083
1096
|
|
|
1084
1097
|
/**
|
|
1085
|
-
* Upload bone skin matrices for the current frame
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
* small for the current skeleton, it is recreated (grown) automatically.
|
|
1089
|
-
* Call once per frame before render() for skinned models.
|
|
1098
|
+
* Upload bone skin matrices for the current frame into the shared default
|
|
1099
|
+
* region. Kept for single-avatar callers — equivalent to
|
|
1100
|
+
* updateSkinMatricesFor("__skin_default", matrices).
|
|
1090
1101
|
*/
|
|
1091
1102
|
updateSkinMatrices(matrices: Float32Array): void {
|
|
1103
|
+
this.updateSkinMatricesFor(ModelRenderer.DEFAULT_SKIN_NODE, matrices);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Upload bone skin matrices for one nodeId's reserved region. The region is
|
|
1108
|
+
* (re)allocated automatically — freed regions from removeModel() are reused
|
|
1109
|
+
* first-fit. Render() looks up the node's region offset and passes it to the
|
|
1110
|
+
* skinning shader via the `boneBase` uniform, so multiple skinned models
|
|
1111
|
+
* animate independently.
|
|
1112
|
+
*/
|
|
1113
|
+
updateSkinMatricesFor(nodeId: string, matrices: Float32Array): void {
|
|
1092
1114
|
if (!this.skinMatrixBuffer) return;
|
|
1093
1115
|
|
|
1094
1116
|
const requiredBones = matrices.length / 16;
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
this.
|
|
1100
|
-
this.skinMatrixBuffer = this.device.createBuffer({
|
|
1101
|
-
size: newCapacity * 64,
|
|
1102
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
1103
|
-
});
|
|
1104
|
-
this.boneCapacity = newCapacity;
|
|
1105
|
-
// Recreate the bind group with the new buffer
|
|
1106
|
-
if (this.skinBindGroupLayout) {
|
|
1107
|
-
this.skinBindGroup = this.device.createBindGroup({
|
|
1108
|
-
layout: this.skinBindGroupLayout,
|
|
1109
|
-
entries: [{ binding: 0, resource: { buffer: this.skinMatrixBuffer } }],
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
this.skinBindGroupSetThisFrame = false; // force re-bind
|
|
1117
|
+
let region = this.skinRegions.get(nodeId);
|
|
1118
|
+
if (!region || region.count < requiredBones) {
|
|
1119
|
+
if (region) this.skinFreeList.push(region);
|
|
1120
|
+
region = this.allocSkinRegion(requiredBones);
|
|
1121
|
+
this.skinRegions.set(nodeId, region);
|
|
1113
1122
|
}
|
|
1123
|
+
this.skinBaseByNode.set(nodeId, region.offset);
|
|
1114
1124
|
|
|
1115
1125
|
this.device.queue.writeBuffer(
|
|
1116
1126
|
this.skinMatrixBuffer,
|
|
1117
|
-
|
|
1127
|
+
region.offset * 64,
|
|
1118
1128
|
matrices as Float32Array<ArrayBuffer>,
|
|
1119
1129
|
);
|
|
1120
1130
|
}
|
|
1121
1131
|
|
|
1132
|
+
/** Allocate a bone region for `count` bones — free-list first, then bump
|
|
1133
|
+
* allocation; grows the shared buffer when the capacity is exceeded. */
|
|
1134
|
+
private allocSkinRegion(count: number): { offset: number; count: number } {
|
|
1135
|
+
for (let i = 0; i < this.skinFreeList.length; i++) {
|
|
1136
|
+
const f = this.skinFreeList[i];
|
|
1137
|
+
if (f.count >= count) {
|
|
1138
|
+
this.skinFreeList.splice(i, 1);
|
|
1139
|
+
return { offset: f.offset, count: f.count };
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const needed = this.nextBoneOffset + count;
|
|
1143
|
+
if (needed > this.boneCapacity) this.growSkinBuffer(needed);
|
|
1144
|
+
const region = { offset: this.nextBoneOffset, count };
|
|
1145
|
+
this.nextBoneOffset += count;
|
|
1146
|
+
return region;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/** Recreate the skin matrix buffer at a larger capacity and rebind group(1).
|
|
1150
|
+
* Region offsets stay valid — callers rewrite their matrices next frame. */
|
|
1151
|
+
private growSkinBuffer(minBones: number): void {
|
|
1152
|
+
const newCapacity = Math.max(minBones, this.boneCapacity * 2);
|
|
1153
|
+
this.skinMatrixBuffer?.destroy();
|
|
1154
|
+
this.skinMatrixBuffer = this.device.createBuffer({
|
|
1155
|
+
size: newCapacity * 64,
|
|
1156
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
1157
|
+
});
|
|
1158
|
+
this.boneCapacity = newCapacity;
|
|
1159
|
+
if (this.skinBindGroupLayout) {
|
|
1160
|
+
this.skinBindGroup = this.device.createBindGroup({
|
|
1161
|
+
layout: this.skinBindGroupLayout,
|
|
1162
|
+
entries: [{ binding: 0, resource: { buffer: this.skinMatrixBuffer } }],
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
this.skinBindGroupSetThisFrame = false; // force re-bind
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/** Release a node's bone region back to the free list. */
|
|
1169
|
+
releaseSkinRegion(nodeId: string): void {
|
|
1170
|
+
const region = this.skinRegions.get(nodeId);
|
|
1171
|
+
if (!region) return;
|
|
1172
|
+
this.skinRegions.delete(nodeId);
|
|
1173
|
+
this.skinBaseByNode.delete(nodeId);
|
|
1174
|
+
this.skinFreeList.push(region);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1122
1177
|
render(
|
|
1123
1178
|
passEncoder: GPURenderPassEncoder,
|
|
1124
1179
|
nodeId: string,
|
|
@@ -1221,6 +1276,12 @@ export class ModelRenderer {
|
|
|
1221
1276
|
// fs_outline). Defaults: 0 width, black color.
|
|
1222
1277
|
uniforms[39] = 0.0;
|
|
1223
1278
|
uniforms[40] = 0.0; uniforms[41] = 0.0; uniforms[42] = 0.0;
|
|
1279
|
+
// boneBase (u32, slot 44) — index of this node's bone region inside the
|
|
1280
|
+
// shared skinMatrices storage buffer. Only read by vs_skinned.
|
|
1281
|
+
this.reusableUniformsU32[44] =
|
|
1282
|
+
this.skinBaseByNode.get(nodeId)
|
|
1283
|
+
?? this.skinBaseByNode.get(ModelRenderer.DEFAULT_SKIN_NODE)
|
|
1284
|
+
?? 0;
|
|
1224
1285
|
|
|
1225
1286
|
this.device.queue.writeBuffer(
|
|
1226
1287
|
this.uniformBuffer,
|
|
@@ -22,6 +22,14 @@ struct Uniforms {
|
|
|
22
22
|
// Inverted-hull outline parameters (used by vs_outline / fs_outline only).
|
|
23
23
|
outlineWidth: f32,
|
|
24
24
|
outlineColor: vec3<f32>,
|
|
25
|
+
// Base index of this draw's bone region inside the shared skinMatrices
|
|
26
|
+
// storage buffer (group(1)). Written by ModelRenderer from the node's
|
|
27
|
+
// reserved region — lets multiple skinned models animate independently.
|
|
28
|
+
// Only read by vs_skinned.
|
|
29
|
+
boneBase: u32,
|
|
30
|
+
_padSkin0: u32,
|
|
31
|
+
_padSkin1: u32,
|
|
32
|
+
_padSkin2: u32,
|
|
25
33
|
};
|
|
26
34
|
|
|
27
35
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
@@ -165,10 +173,11 @@ fn qrotate(q: vec4<f32>, v: vec3<f32>) -> vec3<f32> {
|
|
|
165
173
|
// If all weights are zero (vertex has no bone assignment), return identity so
|
|
166
174
|
// the vertex renders at its baked position instead of collapsing to the origin.
|
|
167
175
|
fn skinMatrix(j: vec4<u32>, w: vec4<f32>) -> mat4x4<f32> {
|
|
168
|
-
let
|
|
169
|
-
|
|
170
|
-
+ skinMatrices[j.
|
|
171
|
-
+ skinMatrices[j.
|
|
176
|
+
let base = uniforms.boneBase;
|
|
177
|
+
let m = skinMatrices[base + j.x] * w.x
|
|
178
|
+
+ skinMatrices[base + j.y] * w.y
|
|
179
|
+
+ skinMatrices[base + j.z] * w.z
|
|
180
|
+
+ skinMatrices[base + j.w] * w.w;
|
|
172
181
|
let wsum = w.x + w.y + w.z + w.w;
|
|
173
182
|
if (wsum < 0.001) {
|
|
174
183
|
return mat4x4<f32>(
|
|
@@ -43,3 +43,7 @@ export type { NetworkingLibConfig } from "./library";
|
|
|
43
43
|
|
|
44
44
|
export { LanDiscovery, MdnsBrowser } from "./discovery";
|
|
45
45
|
export type { LanBeacon, LanDiscoveryOptions, LanPeer, MdnsBrowseOptions, MdnsService } from "./discovery";
|
|
46
|
+
|
|
47
|
+
export { createWebSocketServer } from "./ws-server";
|
|
48
|
+
export type { WebSocketServerHandlers, WebSocketServerOptions, WsServer, WsServerConnection } from "./ws-server";
|
|
49
|
+
|