@woosh/meep-engine 3.31.0 → 3.32.0
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/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DebugDrawSystem.js +6 -2
- package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -1
- package/src/engine/graphics3/PathDisplaySystem.js +311 -307
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/RENDERER_CONTRACT.md +15 -2
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +5 -0
- package/src/shade/device/ShadeGPUCommandContext.d.ts +15 -2
- package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
- package/src/shade/device/ShadeGPUCommandContext.js +154 -25
- package/src/shade/renderer/GraphicsContext.d.ts +9 -1
- package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
- package/src/shade/renderer/GraphicsContext.js +54 -15
- package/src/shade/renderer/Renderer.d.ts +19 -4
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +113 -2
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -7
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
- package/src/shade/renderer/animation/GPUAnimationManager.js +147 -45
- package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts +11 -2
- package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts.map +1 -1
- package/src/shade/renderer/buffer/GPUBufferStagingAllocator.js +95 -13
- package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts +60 -0
- package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts.map +1 -0
- package/src/shade/renderer/buffer/GPUBufferUploadAllocator.js +202 -0
- package/src/shade/renderer/buffer/GPUUploadBlock.d.ts +68 -0
- package/src/shade/renderer/buffer/GPUUploadBlock.d.ts.map +1 -0
- package/src/shade/renderer/buffer/GPUUploadBlock.js +97 -0
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts +20 -7
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +20 -7
- package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts +5 -0
- package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.js +19 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +3 -1
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +3 -1
- package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +134 -88
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +57 -26
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +28 -20
- package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts +38 -0
- package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/graph_clear_raster_targets.js +105 -0
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +21 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +12 -20
- package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts +2 -1
- package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_import_typed_buffer.js +15 -28
- package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts +0 -73
- package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts.map +0 -1
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts +0 -59
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts.map +0 -1
- package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts +0 -22
- package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts.map +0 -1
- package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts +0 -26
- package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts.map +0 -1
- package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts +0 -25
- package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts.map +0 -1
- package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts +0 -26
- package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts.map +0 -1
- package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts +0 -29
- package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DebugDrawSystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/DebugDrawSystem.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IAkEI;;OAEG;IACH,sCAYC;IAhFD;;;;;;;;;OASG;IACH,YAFU,MAAM,CAED;IA8Cf;;;;OAIG;IACH,8BAEC;IAmBD,2CAMC;IAED,4CAYC;IAED;;OAEG;IACH,
|
|
1
|
+
{"version":3,"file":"DebugDrawSystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/DebugDrawSystem.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IAkEI;;OAEG;IACH,sCAYC;IAhFD;;;;;;;;;OASG;IACH,YAFU,MAAM,CAED;IA8Cf;;;;OAIG;IACH,8BAEC;IAmBD,2CAMC;IAED,4CAYC;IAED;;OAEG;IACH,kCA4BC;;CACJ;AAED;;;;;;;;GAQG;AACH;IAYI;;OAEG;IACH,oBAFW,eAAe,EAMzB;IAED,yBAEC;;CACJ;uBAxMsB,kBAAkB;iCADR,gDAAgD;gCAJjD,mDAAmD"}
|
|
@@ -159,14 +159,18 @@ export class DebugDrawSystem extends System {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const scene = frame.get(SceneColor);
|
|
162
|
+
const view = frame.get(ViewTextures);
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
const drawn = this.#renderer.graph_draw({
|
|
164
165
|
graph: frame.graph,
|
|
165
166
|
batch: this.#batch,
|
|
166
167
|
color: scene.color,
|
|
167
|
-
depth:
|
|
168
|
+
depth: view.depth,
|
|
168
169
|
camera: frame.view.camera.buffer
|
|
169
170
|
});
|
|
171
|
+
|
|
172
|
+
scene.color = drawn.color;
|
|
173
|
+
view.depth = drawn.depth;
|
|
170
174
|
}
|
|
171
175
|
}
|
|
172
176
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PathDisplaySystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/PathDisplaySystem.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IA8CI;;OAEG;IACH,sCAMC;IAtDD,mDAAmC;IAoCnC;;;;OAIG;IACH,8BAEC;IAaD,2CAIC;IAED,4CAYC;IAED;;;;OAIG;IACH,cAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAehB;IAED;;;;OAIG;IACH,gBAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAkBhB;IAED,6BAgBC;IAsFD;;OAEG;IACH,
|
|
1
|
+
{"version":3,"file":"PathDisplaySystem.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/PathDisplaySystem.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IA8CI;;OAEG;IACH,sCAMC;IAtDD,mDAAmC;IAoCnC;;;;OAIG;IACH,8BAEC;IAaD,2CAIC;IAED,4CAYC;IAED;;;;OAIG;IACH,cAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAehB;IAED;;;;OAIG;IACH,gBAJW,WAAW,QACX,IAAI,UACJ,MAAM,QAkBhB;IAED,6BAgBC;IAsFD;;OAEG;IACH,kCAmBC;;CACJ;AAED;;;GAGG;AACH;IAUI;;OAEG;IACH,oBAFW,iBAAiB,EAM3B;IAED,yBAEC;;CACJ;uBA7SsB,kBAAkB;iBAIxB,sCAAsC;4BAH3B,qCAAqC;iCAFhC,gDAAgD;gCAJjD,mDAAmD"}
|
|
@@ -1,307 +1,311 @@
|
|
|
1
|
-
import { assert } from "../../core/assert.js";
|
|
2
|
-
import { GPUDynamicMeshRenderer } from "../../shade/renderer/dynamic/GPUDynamicMeshRenderer.js";
|
|
3
|
-
import { Geometry } from "../../shade/renderer/geometry/Geometry.js";
|
|
4
|
-
import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
|
|
5
|
-
import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
|
|
6
|
-
import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
|
|
7
|
-
import { ViewTextures } from "../../shade/renderer/extension/ViewTextures.js";
|
|
8
|
-
import { DynamicMesh } from "../../shade/renderer/scene/DynamicMesh.js";
|
|
9
|
-
import { DynamicMeshBatch } from "../../shade/renderer/scene/DynamicMeshBatch.js";
|
|
10
|
-
import { System } from "../ecs/System.js";
|
|
11
|
-
import { PathDisplay } from "../graphics/ecs/path/PathDisplay.js";
|
|
12
|
-
import { PathDisplayEvents } from "../graphics/ecs/path/PathDisplayEvents.js";
|
|
13
|
-
import { PathDisplayType } from "../graphics/ecs/path/PathDisplayType.js";
|
|
14
|
-
import Path from "../navigation/ecs/components/Path.js";
|
|
15
|
-
import { PathEvents } from "../navigation/ecs/components/PathEvents.js";
|
|
16
|
-
import { build_tube_path_streams } from "./path/build_tube_path_streams.js";
|
|
17
|
-
import { tube_stream_write_geometry } from "./path/tube_stream_write_geometry.js";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Draws {@link PathDisplay}'s tubes under Shade, on the dynamic-geometry path.
|
|
21
|
-
*
|
|
22
|
-
* **A tube is dynamic content by definition** — the game's one path display is the combat targeting
|
|
23
|
-
* arc, rebuilt every time the cursor moves — so it goes down S4's dedicated non-meshlet path rather
|
|
24
|
-
* than becoming a `ShadedGeometry` behind the meshlet encode that D5/I-10 rule out (D21).
|
|
25
|
-
*
|
|
26
|
-
* **No owned entities.** The legacy system spawns a child entity per mask span because three needs
|
|
27
|
-
* an object-carrying entity per drawable; here the system holds the meshes itself, so link builds
|
|
28
|
-
* them, a path or style change rewrites them, and unlink releases them.
|
|
29
|
-
*
|
|
30
|
-
* **The geometry is kept and rewritten, never replaced.** GPU residency on this path is keyed on
|
|
31
|
-
* the geometry and survives leaving the batch, so a rebuild that produced a new one each time would
|
|
32
|
-
* strand a vertex buffer per cursor move.
|
|
33
|
-
*
|
|
34
|
-
* Tubes are all it draws. `PathDisplayType.Ribbon` retires with the three path (I-6c) and
|
|
35
|
-
* `PathDisplayType.Entity` builds marker entities that carry their own components — neither is
|
|
36
|
-
* this system's to render.
|
|
37
|
-
*/
|
|
38
|
-
export class PathDisplaySystem extends System {
|
|
39
|
-
dependencies = [PathDisplay, Path];
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @type {GraphicsEngine}
|
|
43
|
-
*/
|
|
44
|
-
#graphics;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @type {GPUDynamicMeshRenderer}
|
|
48
|
-
*/
|
|
49
|
-
#renderer = new GPUDynamicMeshRenderer();
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @type {DynamicMeshBatch}
|
|
53
|
-
*/
|
|
54
|
-
#batch = new DynamicMeshBatch();
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* @type {Map<number, {display: PathDisplay, path: Path, meshes: DynamicMesh[], listener: Function}>}
|
|
58
|
-
*/
|
|
59
|
-
#entries = new Map();
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Entities whose tubes are out of date. A path change and a style change both arrive as events,
|
|
63
|
-
* and the arc sends one of each per cursor move — coalescing to one rebuild a tick is what the
|
|
64
|
-
* queue is for.
|
|
65
|
-
*
|
|
66
|
-
* @type {Set<number>}
|
|
67
|
-
*/
|
|
68
|
-
#dirty = new Set();
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* @type {RenderExtension|null}
|
|
72
|
-
*/
|
|
73
|
-
#extension = null;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* What the pass draws. Read-only — the meshes follow the entities.
|
|
77
|
-
*
|
|
78
|
-
* @returns {DynamicMeshBatch}
|
|
79
|
-
*/
|
|
80
|
-
get batch() {
|
|
81
|
-
return this.#batch;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {GraphicsEngine} graphics
|
|
86
|
-
*/
|
|
87
|
-
constructor(graphics) {
|
|
88
|
-
super();
|
|
89
|
-
|
|
90
|
-
assert.defined(graphics, 'graphics');
|
|
91
|
-
|
|
92
|
-
this.#graphics = graphics;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async startup(entityManager) {
|
|
96
|
-
this.entityManager = entityManager;
|
|
97
|
-
|
|
98
|
-
this.#extension = this.#graphics.add_extension(new PathDisplayExtension(this));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async shutdown(entityManager) {
|
|
102
|
-
if (this.#extension !== null) {
|
|
103
|
-
this.#graphics.remove_extension(this.#extension);
|
|
104
|
-
|
|
105
|
-
this.#extension = null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
this.#renderer.destroy();
|
|
109
|
-
|
|
110
|
-
this.#batch.clear();
|
|
111
|
-
this.#entries.clear();
|
|
112
|
-
this.#dirty.clear();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @param {PathDisplay} display
|
|
117
|
-
* @param {Path} path
|
|
118
|
-
* @param {number} entity
|
|
119
|
-
*/
|
|
120
|
-
link(display, path, entity) {
|
|
121
|
-
const dataset = this.entityManager.dataset;
|
|
122
|
-
|
|
123
|
-
const listener = () => this.#dirty.add(entity);
|
|
124
|
-
|
|
125
|
-
dataset.addEntityEventListener(entity, PathEvents.Changed, listener);
|
|
126
|
-
dataset.addEntityEventListener(entity, PathDisplayEvents.Changed, listener);
|
|
127
|
-
|
|
128
|
-
const entry = { display, path, meshes: [], listener };
|
|
129
|
-
|
|
130
|
-
this.#entries.set(entity, entry);
|
|
131
|
-
|
|
132
|
-
this.#rebuild(entry);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @param {PathDisplay} display
|
|
137
|
-
* @param {Path} path
|
|
138
|
-
* @param {number} entity
|
|
139
|
-
*/
|
|
140
|
-
unlink(display, path, entity) {
|
|
141
|
-
const entry = this.#entries.get(entity);
|
|
142
|
-
|
|
143
|
-
if (entry === undefined) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const dataset = this.entityManager.dataset;
|
|
148
|
-
|
|
149
|
-
dataset.removeEntityEventListener(entity, PathEvents.Changed, entry.listener);
|
|
150
|
-
dataset.removeEntityEventListener(entity, PathDisplayEvents.Changed, entry.listener);
|
|
151
|
-
|
|
152
|
-
this.#release(entry, 0);
|
|
153
|
-
|
|
154
|
-
this.#entries.delete(entity);
|
|
155
|
-
this.#dirty.delete(entity);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
update(timeDelta) {
|
|
159
|
-
if (this.#dirty.size === 0) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for (const entity of this.#dirty) {
|
|
164
|
-
const entry = this.#entries.get(entity);
|
|
165
|
-
|
|
166
|
-
if (entry === undefined) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
this.#rebuild(entry);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
this.#dirty.clear();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @param {{display: PathDisplay, path: Path, meshes: DynamicMesh[]}} entry
|
|
178
|
-
*/
|
|
179
|
-
#rebuild(entry) {
|
|
180
|
-
const specs = entry.display.specs;
|
|
181
|
-
|
|
182
|
-
let written = 0;
|
|
183
|
-
|
|
184
|
-
for (let i = 0; i < specs.length; i++) {
|
|
185
|
-
const spec = specs[i];
|
|
186
|
-
|
|
187
|
-
if (spec.type !== PathDisplayType.Tube) {
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const style = spec.style;
|
|
192
|
-
|
|
193
|
-
const streams = build_tube_path_streams(entry.path, style);
|
|
194
|
-
|
|
195
|
-
const color = style.color;
|
|
196
|
-
|
|
197
|
-
for (let j = 0; j < streams.length; j++) {
|
|
198
|
-
const mesh = this.#obtain_mesh(entry, written);
|
|
199
|
-
|
|
200
|
-
tube_stream_write_geometry(mesh.geometry, streams[j]);
|
|
201
|
-
|
|
202
|
-
mesh.color[0] = color.r;
|
|
203
|
-
mesh.color[1] = color.g;
|
|
204
|
-
mesh.color[2] = color.b;
|
|
205
|
-
mesh.color[3] = style.opacity;
|
|
206
|
-
|
|
207
|
-
written++;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
this.#release(entry, written);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* The mesh drawing span `index` of this entity, created on first use.
|
|
216
|
-
*
|
|
217
|
-
* @param {{meshes: DynamicMesh[]}} entry
|
|
218
|
-
* @param {number} index
|
|
219
|
-
* @returns {DynamicMesh}
|
|
220
|
-
*/
|
|
221
|
-
#obtain_mesh(entry, index) {
|
|
222
|
-
const existing = entry.meshes[index];
|
|
223
|
-
|
|
224
|
-
if (existing !== undefined) {
|
|
225
|
-
return existing;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// the sweep produces world-space vertices, and what the draw applies is the mesh's own
|
|
229
|
-
// transform, identity until moved
|
|
230
|
-
const mesh = DynamicMesh.from(new Geometry());
|
|
231
|
-
|
|
232
|
-
entry.meshes.push(mesh);
|
|
233
|
-
|
|
234
|
-
this.#batch.add(mesh);
|
|
235
|
-
|
|
236
|
-
return mesh;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Give up every mesh from `count` on — a style whose mask lost a span, or an entity going away.
|
|
241
|
-
*
|
|
242
|
-
* @param {{meshes: DynamicMesh[]}} entry
|
|
243
|
-
* @param {number} count
|
|
244
|
-
*/
|
|
245
|
-
#release(entry, count) {
|
|
246
|
-
const meshes = entry.meshes;
|
|
247
|
-
|
|
248
|
-
for (let i = count; i < meshes.length; i++) {
|
|
249
|
-
const mesh = meshes[i];
|
|
250
|
-
|
|
251
|
-
this.#batch.remove(mesh);
|
|
252
|
-
|
|
253
|
-
// residency deliberately survives leaving the batch, so nothing else would free it
|
|
254
|
-
this.#renderer.release(mesh.geometry);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
meshes.length = count;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* @param {FrameContext} frame
|
|
262
|
-
*/
|
|
263
|
-
record(frame) {
|
|
264
|
-
if (this.#batch.count === 0) {
|
|
265
|
-
// nothing to draw, and the frame's colour is left exactly as it was
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const scene = frame.get(SceneColor);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
this.#system
|
|
306
|
-
}
|
|
307
|
-
|
|
1
|
+
import { assert } from "../../core/assert.js";
|
|
2
|
+
import { GPUDynamicMeshRenderer } from "../../shade/renderer/dynamic/GPUDynamicMeshRenderer.js";
|
|
3
|
+
import { Geometry } from "../../shade/renderer/geometry/Geometry.js";
|
|
4
|
+
import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
|
|
5
|
+
import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
|
|
6
|
+
import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
|
|
7
|
+
import { ViewTextures } from "../../shade/renderer/extension/ViewTextures.js";
|
|
8
|
+
import { DynamicMesh } from "../../shade/renderer/scene/DynamicMesh.js";
|
|
9
|
+
import { DynamicMeshBatch } from "../../shade/renderer/scene/DynamicMeshBatch.js";
|
|
10
|
+
import { System } from "../ecs/System.js";
|
|
11
|
+
import { PathDisplay } from "../graphics/ecs/path/PathDisplay.js";
|
|
12
|
+
import { PathDisplayEvents } from "../graphics/ecs/path/PathDisplayEvents.js";
|
|
13
|
+
import { PathDisplayType } from "../graphics/ecs/path/PathDisplayType.js";
|
|
14
|
+
import Path from "../navigation/ecs/components/Path.js";
|
|
15
|
+
import { PathEvents } from "../navigation/ecs/components/PathEvents.js";
|
|
16
|
+
import { build_tube_path_streams } from "./path/build_tube_path_streams.js";
|
|
17
|
+
import { tube_stream_write_geometry } from "./path/tube_stream_write_geometry.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Draws {@link PathDisplay}'s tubes under Shade, on the dynamic-geometry path.
|
|
21
|
+
*
|
|
22
|
+
* **A tube is dynamic content by definition** — the game's one path display is the combat targeting
|
|
23
|
+
* arc, rebuilt every time the cursor moves — so it goes down S4's dedicated non-meshlet path rather
|
|
24
|
+
* than becoming a `ShadedGeometry` behind the meshlet encode that D5/I-10 rule out (D21).
|
|
25
|
+
*
|
|
26
|
+
* **No owned entities.** The legacy system spawns a child entity per mask span because three needs
|
|
27
|
+
* an object-carrying entity per drawable; here the system holds the meshes itself, so link builds
|
|
28
|
+
* them, a path or style change rewrites them, and unlink releases them.
|
|
29
|
+
*
|
|
30
|
+
* **The geometry is kept and rewritten, never replaced.** GPU residency on this path is keyed on
|
|
31
|
+
* the geometry and survives leaving the batch, so a rebuild that produced a new one each time would
|
|
32
|
+
* strand a vertex buffer per cursor move.
|
|
33
|
+
*
|
|
34
|
+
* Tubes are all it draws. `PathDisplayType.Ribbon` retires with the three path (I-6c) and
|
|
35
|
+
* `PathDisplayType.Entity` builds marker entities that carry their own components — neither is
|
|
36
|
+
* this system's to render.
|
|
37
|
+
*/
|
|
38
|
+
export class PathDisplaySystem extends System {
|
|
39
|
+
dependencies = [PathDisplay, Path];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @type {GraphicsEngine}
|
|
43
|
+
*/
|
|
44
|
+
#graphics;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @type {GPUDynamicMeshRenderer}
|
|
48
|
+
*/
|
|
49
|
+
#renderer = new GPUDynamicMeshRenderer();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @type {DynamicMeshBatch}
|
|
53
|
+
*/
|
|
54
|
+
#batch = new DynamicMeshBatch();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @type {Map<number, {display: PathDisplay, path: Path, meshes: DynamicMesh[], listener: Function}>}
|
|
58
|
+
*/
|
|
59
|
+
#entries = new Map();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Entities whose tubes are out of date. A path change and a style change both arrive as events,
|
|
63
|
+
* and the arc sends one of each per cursor move — coalescing to one rebuild a tick is what the
|
|
64
|
+
* queue is for.
|
|
65
|
+
*
|
|
66
|
+
* @type {Set<number>}
|
|
67
|
+
*/
|
|
68
|
+
#dirty = new Set();
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @type {RenderExtension|null}
|
|
72
|
+
*/
|
|
73
|
+
#extension = null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What the pass draws. Read-only — the meshes follow the entities.
|
|
77
|
+
*
|
|
78
|
+
* @returns {DynamicMeshBatch}
|
|
79
|
+
*/
|
|
80
|
+
get batch() {
|
|
81
|
+
return this.#batch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {GraphicsEngine} graphics
|
|
86
|
+
*/
|
|
87
|
+
constructor(graphics) {
|
|
88
|
+
super();
|
|
89
|
+
|
|
90
|
+
assert.defined(graphics, 'graphics');
|
|
91
|
+
|
|
92
|
+
this.#graphics = graphics;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async startup(entityManager) {
|
|
96
|
+
this.entityManager = entityManager;
|
|
97
|
+
|
|
98
|
+
this.#extension = this.#graphics.add_extension(new PathDisplayExtension(this));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async shutdown(entityManager) {
|
|
102
|
+
if (this.#extension !== null) {
|
|
103
|
+
this.#graphics.remove_extension(this.#extension);
|
|
104
|
+
|
|
105
|
+
this.#extension = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.#renderer.destroy();
|
|
109
|
+
|
|
110
|
+
this.#batch.clear();
|
|
111
|
+
this.#entries.clear();
|
|
112
|
+
this.#dirty.clear();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {PathDisplay} display
|
|
117
|
+
* @param {Path} path
|
|
118
|
+
* @param {number} entity
|
|
119
|
+
*/
|
|
120
|
+
link(display, path, entity) {
|
|
121
|
+
const dataset = this.entityManager.dataset;
|
|
122
|
+
|
|
123
|
+
const listener = () => this.#dirty.add(entity);
|
|
124
|
+
|
|
125
|
+
dataset.addEntityEventListener(entity, PathEvents.Changed, listener);
|
|
126
|
+
dataset.addEntityEventListener(entity, PathDisplayEvents.Changed, listener);
|
|
127
|
+
|
|
128
|
+
const entry = { display, path, meshes: [], listener };
|
|
129
|
+
|
|
130
|
+
this.#entries.set(entity, entry);
|
|
131
|
+
|
|
132
|
+
this.#rebuild(entry);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {PathDisplay} display
|
|
137
|
+
* @param {Path} path
|
|
138
|
+
* @param {number} entity
|
|
139
|
+
*/
|
|
140
|
+
unlink(display, path, entity) {
|
|
141
|
+
const entry = this.#entries.get(entity);
|
|
142
|
+
|
|
143
|
+
if (entry === undefined) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const dataset = this.entityManager.dataset;
|
|
148
|
+
|
|
149
|
+
dataset.removeEntityEventListener(entity, PathEvents.Changed, entry.listener);
|
|
150
|
+
dataset.removeEntityEventListener(entity, PathDisplayEvents.Changed, entry.listener);
|
|
151
|
+
|
|
152
|
+
this.#release(entry, 0);
|
|
153
|
+
|
|
154
|
+
this.#entries.delete(entity);
|
|
155
|
+
this.#dirty.delete(entity);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
update(timeDelta) {
|
|
159
|
+
if (this.#dirty.size === 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const entity of this.#dirty) {
|
|
164
|
+
const entry = this.#entries.get(entity);
|
|
165
|
+
|
|
166
|
+
if (entry === undefined) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.#rebuild(entry);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.#dirty.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {{display: PathDisplay, path: Path, meshes: DynamicMesh[]}} entry
|
|
178
|
+
*/
|
|
179
|
+
#rebuild(entry) {
|
|
180
|
+
const specs = entry.display.specs;
|
|
181
|
+
|
|
182
|
+
let written = 0;
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < specs.length; i++) {
|
|
185
|
+
const spec = specs[i];
|
|
186
|
+
|
|
187
|
+
if (spec.type !== PathDisplayType.Tube) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const style = spec.style;
|
|
192
|
+
|
|
193
|
+
const streams = build_tube_path_streams(entry.path, style);
|
|
194
|
+
|
|
195
|
+
const color = style.color;
|
|
196
|
+
|
|
197
|
+
for (let j = 0; j < streams.length; j++) {
|
|
198
|
+
const mesh = this.#obtain_mesh(entry, written);
|
|
199
|
+
|
|
200
|
+
tube_stream_write_geometry(mesh.geometry, streams[j]);
|
|
201
|
+
|
|
202
|
+
mesh.color[0] = color.r;
|
|
203
|
+
mesh.color[1] = color.g;
|
|
204
|
+
mesh.color[2] = color.b;
|
|
205
|
+
mesh.color[3] = style.opacity;
|
|
206
|
+
|
|
207
|
+
written++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.#release(entry, written);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The mesh drawing span `index` of this entity, created on first use.
|
|
216
|
+
*
|
|
217
|
+
* @param {{meshes: DynamicMesh[]}} entry
|
|
218
|
+
* @param {number} index
|
|
219
|
+
* @returns {DynamicMesh}
|
|
220
|
+
*/
|
|
221
|
+
#obtain_mesh(entry, index) {
|
|
222
|
+
const existing = entry.meshes[index];
|
|
223
|
+
|
|
224
|
+
if (existing !== undefined) {
|
|
225
|
+
return existing;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// the sweep produces world-space vertices, and what the draw applies is the mesh's own
|
|
229
|
+
// transform, identity until moved
|
|
230
|
+
const mesh = DynamicMesh.from(new Geometry());
|
|
231
|
+
|
|
232
|
+
entry.meshes.push(mesh);
|
|
233
|
+
|
|
234
|
+
this.#batch.add(mesh);
|
|
235
|
+
|
|
236
|
+
return mesh;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Give up every mesh from `count` on — a style whose mask lost a span, or an entity going away.
|
|
241
|
+
*
|
|
242
|
+
* @param {{meshes: DynamicMesh[]}} entry
|
|
243
|
+
* @param {number} count
|
|
244
|
+
*/
|
|
245
|
+
#release(entry, count) {
|
|
246
|
+
const meshes = entry.meshes;
|
|
247
|
+
|
|
248
|
+
for (let i = count; i < meshes.length; i++) {
|
|
249
|
+
const mesh = meshes[i];
|
|
250
|
+
|
|
251
|
+
this.#batch.remove(mesh);
|
|
252
|
+
|
|
253
|
+
// residency deliberately survives leaving the batch, so nothing else would free it
|
|
254
|
+
this.#renderer.release(mesh.geometry);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
meshes.length = count;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @param {FrameContext} frame
|
|
262
|
+
*/
|
|
263
|
+
record(frame) {
|
|
264
|
+
if (this.#batch.count === 0) {
|
|
265
|
+
// nothing to draw, and the frame's colour is left exactly as it was
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const scene = frame.get(SceneColor);
|
|
270
|
+
const view = frame.get(ViewTextures);
|
|
271
|
+
|
|
272
|
+
const drawn = this.#renderer.graph_draw({
|
|
273
|
+
graph: frame.graph,
|
|
274
|
+
batch: this.#batch,
|
|
275
|
+
color: scene.color,
|
|
276
|
+
depth: view.depth,
|
|
277
|
+
camera: frame.view.camera.buffer
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
scene.color = drawn.color;
|
|
281
|
+
view.depth = drawn.depth;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* The targeting arc draws into the scene, so it belongs before the fog of war that conceals it and
|
|
287
|
+
* before the upscale that resolves it.
|
|
288
|
+
*/
|
|
289
|
+
export class PathDisplayExtension extends RenderExtension {
|
|
290
|
+
name = "path display";
|
|
291
|
+
|
|
292
|
+
phase = FramePhase.AfterTransparency;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @type {PathDisplaySystem}
|
|
296
|
+
*/
|
|
297
|
+
#system;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {PathDisplaySystem} system
|
|
301
|
+
*/
|
|
302
|
+
constructor(system) {
|
|
303
|
+
super();
|
|
304
|
+
|
|
305
|
+
this.#system = system;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
record(frame) {
|
|
309
|
+
this.#system.record(frame);
|
|
310
|
+
}
|
|
311
|
+
}
|