@aethervtt/shard-render 0.0.1 → 0.2.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/dist/ablation.d.ts +74 -0
- package/dist/ablation.js +117 -0
- package/dist/assets.d.ts +3 -0
- package/dist/assets.js +43 -2
- package/dist/atmosphere-nodes.d.ts +9 -0
- package/dist/atmosphere-nodes.js +157 -30
- package/dist/atmosphere-plugin.js +8 -2
- package/dist/atmosphere-shaders.js +17 -5
- package/dist/baseline/atmosphere.d.ts +16 -0
- package/dist/baseline/atmosphere.js +301 -0
- package/dist/baseline/data-texture.d.ts +36 -0
- package/dist/baseline/data-texture.js +181 -0
- package/dist/baseline/environment.d.ts +14 -0
- package/dist/baseline/environment.js +368 -0
- package/dist/baseline/exposure.d.ts +25 -0
- package/dist/baseline/exposure.js +133 -0
- package/dist/baseline/lights.d.ts +33 -0
- package/dist/baseline/lights.js +133 -0
- package/dist/baseline/textures.d.ts +97 -0
- package/dist/baseline/textures.js +203 -0
- package/dist/camera.d.ts +2 -0
- package/dist/camera.js +4 -0
- package/dist/crowd.d.ts +21 -0
- package/dist/crowd.js +121 -0
- package/dist/culling-shaders.js +21 -2
- package/dist/culling.d.ts +11 -2
- package/dist/culling.js +44 -18
- package/dist/cutaway-plugin.d.ts +48 -0
- package/dist/cutaway-plugin.js +364 -0
- package/dist/cutaway.d.ts +27 -0
- package/dist/cutaway.js +49 -0
- package/dist/data-store.d.ts +69 -0
- package/dist/data-store.js +105 -0
- package/dist/debug-shaders.js +17 -3
- package/dist/debug-views.js +4 -2
- package/dist/decals-plugin.d.ts +5 -0
- package/dist/decals-plugin.js +54 -0
- package/dist/decals.d.ts +69 -0
- package/dist/decals.js +326 -0
- package/dist/deferred-shaders.js +2 -0
- package/dist/deferred.js +28 -4
- package/dist/deform.d.ts +5 -4
- package/dist/deform.js +4 -9
- package/dist/describe-plugin.d.ts +4 -2
- package/dist/describe-plugin.js +75 -3
- package/dist/display-nodes.js +37 -13
- package/dist/dynamic-resolution.d.ts +2 -0
- package/dist/dynamic-resolution.js +6 -1
- package/dist/environment-plugin.js +17 -2
- package/dist/environment-state.d.ts +7 -3
- package/dist/environment-state.js +20 -9
- package/dist/environment.js +7 -1
- package/dist/features.d.ts +49 -0
- package/dist/features.js +106 -0
- package/dist/foliage-budget.d.ts +64 -0
- package/dist/foliage-budget.js +108 -0
- package/dist/foliage-path.d.ts +19 -0
- package/dist/foliage-path.js +7 -0
- package/dist/foliage-shaders.d.ts +11 -0
- package/dist/foliage-shaders.js +504 -0
- package/dist/foliage.d.ts +194 -0
- package/dist/foliage.js +1027 -0
- package/dist/forward.d.ts +38 -2
- package/dist/forward.js +608 -186
- package/dist/fxaa.js +12 -2
- package/dist/gizmos-plugin.js +7 -0
- package/dist/gizmos.d.ts +3 -3
- package/dist/gizmos.js +11 -13
- package/dist/gpu-assets.d.ts +58 -4
- package/dist/gpu-assets.js +191 -8
- package/dist/graph.d.ts +45 -0
- package/dist/graph.js +169 -19
- package/dist/health.d.ts +58 -0
- package/dist/health.js +112 -0
- package/dist/index.d.ts +40 -11
- package/dist/index.js +343 -9
- package/dist/instances.d.ts +119 -8
- package/dist/instances.js +387 -78
- package/dist/interior-plugin.d.ts +121 -0
- package/dist/interior-plugin.js +465 -0
- package/dist/interior.d.ts +24 -0
- package/dist/interior.js +13 -0
- package/dist/layers.d.ts +35 -0
- package/dist/layers.js +49 -0
- package/dist/lens-plugin.d.ts +43 -0
- package/dist/lens-plugin.js +304 -0
- package/dist/lens.d.ts +41 -0
- package/dist/lens.js +99 -0
- package/dist/lights.d.ts +50 -5
- package/dist/lights.js +101 -22
- package/dist/material-pipelines.d.ts +37 -4
- package/dist/material-pipelines.js +102 -5
- package/dist/materials.d.ts +23 -0
- package/dist/materials.js +44 -1
- package/dist/outline-plugin.d.ts +48 -0
- package/dist/outline-plugin.js +529 -0
- package/dist/outline.d.ts +15 -0
- package/dist/outline.js +42 -0
- package/dist/overlay-registry.d.ts +3 -1
- package/dist/overlay-registry.js +3 -1
- package/dist/overlays.js +6 -2
- package/dist/perf-overlay.d.ts +9 -0
- package/dist/perf-overlay.js +125 -0
- package/dist/picking.d.ts +17 -0
- package/dist/picking.js +40 -11
- package/dist/pixel-perfect-plugin.js +10 -2
- package/dist/plugin.d.ts +69 -7
- package/dist/plugin.js +223 -25
- package/dist/post-common.d.ts +22 -2
- package/dist/post-common.js +82 -2
- package/dist/post-nodes.d.ts +7 -1
- package/dist/post-nodes.js +235 -111
- package/dist/post-plugin.js +5 -3
- package/dist/post-scene.d.ts +130 -0
- package/dist/post-scene.js +164 -0
- package/dist/post-shaders.js +85 -62
- package/dist/post.js +11 -1
- package/dist/projection.d.ts +24 -0
- package/dist/projection.js +87 -0
- package/dist/readback.d.ts +1 -0
- package/dist/readback.js +1 -0
- package/dist/render-scale.js +1 -0
- package/dist/ribbons-plugin.d.ts +5 -0
- package/dist/ribbons-plugin.js +59 -0
- package/dist/ribbons.d.ts +44 -0
- package/dist/ribbons.js +288 -0
- package/dist/scenario.d.ts +70 -0
- package/dist/scenario.js +125 -0
- package/dist/screen-effects.d.ts +97 -0
- package/dist/screen-effects.js +192 -0
- package/dist/shaders.d.ts +13 -0
- package/dist/shaders.js +189 -34
- package/dist/shadow-catcher.d.ts +9 -0
- package/dist/shadow-catcher.js +133 -0
- package/dist/shadows.d.ts +27 -2
- package/dist/shadows.js +100 -20
- package/dist/standard-fields.d.ts +1 -0
- package/dist/standard-fields.js +5 -0
- package/dist/standard.js +18 -0
- package/dist/stats.d.ts +45 -1
- package/dist/stats.js +91 -3
- package/dist/surface-model.d.ts +57 -0
- package/dist/surface-model.js +289 -0
- package/dist/surface-shaders.d.ts +3 -0
- package/dist/surface-shaders.js +160 -0
- package/dist/surface.d.ts +40 -0
- package/dist/surface.js +86 -0
- package/dist/target.d.ts +1 -13
- package/dist/target.js +1 -26
- package/dist/testing.d.ts +11 -0
- package/dist/testing.js +39 -1
- package/dist/tier.d.ts +13 -0
- package/dist/tier.js +14 -0
- package/dist/timer.d.ts +18 -1
- package/dist/timer.js +73 -20
- package/dist/variants.d.ts +37 -0
- package/dist/variants.js +104 -0
- package/dist/vfx-draw.d.ts +37 -0
- package/dist/vfx-draw.js +291 -0
- package/dist/vfx-state.d.ts +42 -0
- package/dist/vfx-state.js +81 -0
- package/dist/vfx.d.ts +69 -0
- package/dist/vfx.js +103 -0
- package/dist/view-visibility-plugin.d.ts +61 -0
- package/dist/view-visibility-plugin.js +267 -0
- package/dist/view-visibility.d.ts +28 -0
- package/dist/view-visibility.js +46 -0
- package/dist/view.d.ts +29 -1
- package/dist/view.js +57 -34
- package/package.json +23 -11
package/dist/culling.js
CHANGED
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
SLOT_MASK
|
|
8
8
|
} from "./instances.js";
|
|
9
9
|
import { Shaders } from "./plugin.js";
|
|
10
|
+
import { NO_HIDDEN } from "./view-visibility.js";
|
|
10
11
|
const ARGS_WORDS = 5;
|
|
11
|
-
const VIEW_FLOATS =
|
|
12
|
+
const VIEW_FLOATS = 36;
|
|
12
13
|
const BATCH_WORDS = 8;
|
|
13
14
|
const LOD_WORDS = 20;
|
|
14
15
|
const VIEW_CASTERS = 1;
|
|
@@ -21,6 +22,8 @@ class GpuCuller {
|
|
|
21
22
|
supported;
|
|
22
23
|
/** Turn off to force CPU culling (tests, debugging). */
|
|
23
24
|
enabled = true;
|
|
25
|
+
/** Views this frame with a hidden set (0070): the cull links its HIDDEN variant. */
|
|
26
|
+
hiddenViews = 0;
|
|
24
27
|
views = [];
|
|
25
28
|
viewCount = 0;
|
|
26
29
|
cameraViews = 0;
|
|
@@ -59,7 +62,7 @@ class GpuCuller {
|
|
|
59
62
|
pending = /* @__PURE__ */ new Set();
|
|
60
63
|
constructor(gpu) {
|
|
61
64
|
this.gpu = gpu;
|
|
62
|
-
this.supported = gpu.features.has("indirect-first-instance");
|
|
65
|
+
this.supported = gpu.tier === "full" && gpu.features.has("indirect-first-instance");
|
|
63
66
|
const storage = GPUBufferUsage.STORAGE;
|
|
64
67
|
this.viewBuffer = new GpuBuffer(gpu, { label: "cull/views", usage: storage, size: 4096 });
|
|
65
68
|
this.batchBuffer = new GpuBuffer(gpu, { label: "cull/batches", usage: storage, size: 2048 });
|
|
@@ -82,6 +85,7 @@ class GpuCuller {
|
|
|
82
85
|
beginFrame() {
|
|
83
86
|
this.viewCount = 0;
|
|
84
87
|
this.cameraViews = 0;
|
|
88
|
+
this.hiddenViews = 0;
|
|
85
89
|
}
|
|
86
90
|
/** The LOD state row of a camera (cameras choose; their shadow views reuse the choice). */
|
|
87
91
|
lodCamera(entity) {
|
|
@@ -110,7 +114,9 @@ class GpuCuller {
|
|
|
110
114
|
hasEye: false,
|
|
111
115
|
lodScale: 1,
|
|
112
116
|
flags: 0,
|
|
113
|
-
lodCamera: 0
|
|
117
|
+
lodCamera: 0,
|
|
118
|
+
layers: 4294967295,
|
|
119
|
+
hiddenBase: NO_HIDDEN
|
|
114
120
|
};
|
|
115
121
|
this.views.push(view);
|
|
116
122
|
}
|
|
@@ -122,6 +128,9 @@ class GpuCuller {
|
|
|
122
128
|
if (params.eye) view.eye.set(params.eye);
|
|
123
129
|
view.lodScale = params.lodScale;
|
|
124
130
|
view.lodCamera = lodCamera;
|
|
131
|
+
view.layers = params.layers ?? 4294967295;
|
|
132
|
+
view.hiddenBase = params.hiddenBase ?? NO_HIDDEN;
|
|
133
|
+
if (view.hiddenBase !== NO_HIDDEN) this.hiddenViews++;
|
|
125
134
|
view.flags = (params.require & InstanceFlags.Caster ? VIEW_CASTERS : 0) | (params.updateLod ? VIEW_UPDATE_LOD : 0) | (params.orthographic ? VIEW_ORTHO : 0) | (params.planes ? 0 : VIEW_NO_PLANES);
|
|
126
135
|
if (params.updateLod) this.cameraViews = v + 1;
|
|
127
136
|
list.cullView = v;
|
|
@@ -136,7 +145,8 @@ class GpuCuller {
|
|
|
136
145
|
let n = 0;
|
|
137
146
|
let m = 0;
|
|
138
147
|
for (const batch of store.sorted) {
|
|
139
|
-
if (batch.count === 0 || batch.transparent || !store.anyVisible(batch))
|
|
148
|
+
if (batch.count === 0 || batch.transparent || batch.ground || !store.anyVisible(batch))
|
|
149
|
+
continue;
|
|
140
150
|
if (!batch.ready) {
|
|
141
151
|
list.pending += batch.count;
|
|
142
152
|
continue;
|
|
@@ -157,9 +167,22 @@ class GpuCuller {
|
|
|
157
167
|
if (forwardOnly) forwardOnly.length = m;
|
|
158
168
|
return v;
|
|
159
169
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Uploads the frame's view, batch, and LOD tables, and sizes the buffers. Hidden sets (0070)
|
|
172
|
+
* ride in the LOD state buffer after its entries: the cull's storage bindings are at WebGPU's
|
|
173
|
+
* guaranteed 8 per stage.
|
|
174
|
+
*/
|
|
175
|
+
prepare(store, hidden) {
|
|
162
176
|
if (this.viewCount === 0) return;
|
|
177
|
+
const lodCams = Math.max(1, this.lodCameras.size);
|
|
178
|
+
const hiddenWords = hidden && hidden.sets.size > 0 ? hidden.data.length : 0;
|
|
179
|
+
if (this.lodStateCapacity !== store.capacity * lodCams || this.lodState.byteLength < (this.lodStateCapacity + hiddenWords) * 4) {
|
|
180
|
+
this.lodStateCapacity = store.capacity * lodCams;
|
|
181
|
+
this.lodState.ensureCapacity((this.lodStateCapacity + hiddenWords) * 4);
|
|
182
|
+
const fill = new Uint32Array(this.lodStateCapacity).fill(LOD_UNSET);
|
|
183
|
+
this.lodState.write(fill);
|
|
184
|
+
}
|
|
185
|
+
if (hiddenWords > 0) hidden.upload(this.lodState, this.lodStateCapacity * 4);
|
|
163
186
|
const batches = store.batches;
|
|
164
187
|
this.batchCount = batches.length;
|
|
165
188
|
if (this.batchData.length < batches.length * BATCH_WORDS) {
|
|
@@ -182,7 +205,7 @@ class GpuCuller {
|
|
|
182
205
|
this.batchData[o + 4] = batch.mesh.drawCount;
|
|
183
206
|
this.batchData[o + 5] = batch.mesh.indexed ? 1 + batch.mesh.baseVertex : 0;
|
|
184
207
|
this.batchData[o + 6] = batch.region;
|
|
185
|
-
this.batchData[o + 7] = (batch.transparent ? BATCH_TRANSPARENT : 0) | (batch.ready ? 0 : BATCH_NOT_READY);
|
|
208
|
+
this.batchData[o + 7] = (batch.transparent || batch.ground ? BATCH_TRANSPARENT : 0) | (batch.ready ? 0 : BATCH_NOT_READY);
|
|
186
209
|
}
|
|
187
210
|
this.regionSize = Math.max(1, region);
|
|
188
211
|
this.batchBuffer.write(this.batchData, 0, 0, Math.max(1, batches.length) * BATCH_WORDS);
|
|
@@ -220,6 +243,8 @@ class GpuCuller {
|
|
|
220
243
|
this.viewU32[o + 29] = view.lodCamera;
|
|
221
244
|
this.viewU32[o + 30] = v * batches.length;
|
|
222
245
|
this.viewU32[o + 31] = v * this.regionSize;
|
|
246
|
+
this.viewU32[o + 32] = view.layers;
|
|
247
|
+
this.viewU32[o + 33] = view.hiddenBase === NO_HIDDEN ? NO_HIDDEN : this.lodStateCapacity + view.hiddenBase;
|
|
223
248
|
for (const list of [view.list, view.forwardOnly]) {
|
|
224
249
|
if (!list) continue;
|
|
225
250
|
for (let d = 0; d < list.length; d++) {
|
|
@@ -232,12 +257,6 @@ class GpuCuller {
|
|
|
232
257
|
this.viewBuffer.write(this.viewData, 0, 0, this.viewCount * VIEW_FLOATS);
|
|
233
258
|
this.args.ensureCapacity(Math.max(1, this.viewCount * batches.length) * ARGS_WORDS * 4);
|
|
234
259
|
this.visible.ensureCapacity(this.viewCount * this.regionSize * 4);
|
|
235
|
-
const lodCams = Math.max(1, this.lodCameras.size);
|
|
236
|
-
if (this.lodStateCapacity !== store.capacity * lodCams) {
|
|
237
|
-
this.lodStateCapacity = store.capacity * lodCams;
|
|
238
|
-
const fill = new Uint32Array(this.lodStateCapacity).fill(LOD_UNSET);
|
|
239
|
-
this.lodState.write(fill);
|
|
240
|
-
}
|
|
241
260
|
this.frameViews = this.views.slice(0, this.viewCount);
|
|
242
261
|
this.slotCount = store.high;
|
|
243
262
|
this.slotCapacity = store.capacity;
|
|
@@ -249,7 +268,8 @@ class GpuCuller {
|
|
|
249
268
|
if (this.viewCount === 0) return;
|
|
250
269
|
const gpu = ctx.gpu;
|
|
251
270
|
const world = ctx.world;
|
|
252
|
-
const
|
|
271
|
+
const hidden = this.hiddenViews > 0;
|
|
272
|
+
const module = world.resource(Shaders).module(gpu, hidden ? HIDDEN_REQUEST : CULL_REQUEST);
|
|
253
273
|
if (!module) {
|
|
254
274
|
gpu.pipelines.skipped++;
|
|
255
275
|
return;
|
|
@@ -257,12 +277,12 @@ class GpuCuller {
|
|
|
257
277
|
const layout = cullLayout(gpu);
|
|
258
278
|
const pipelineLayout = gpu.layouts.pipelineLayout({ label: "cull", bindGroupLayouts: [layout] });
|
|
259
279
|
const reset = gpu.pipelines.compute({
|
|
260
|
-
label: "cull/reset",
|
|
280
|
+
label: hidden ? "cull/reset/hidden" : "cull/reset",
|
|
261
281
|
layout: pipelineLayout,
|
|
262
282
|
compute: { module, entryPoint: "reset" }
|
|
263
283
|
});
|
|
264
284
|
const cull = gpu.pipelines.compute({
|
|
265
|
-
label: "cull/instances",
|
|
285
|
+
label: hidden ? "cull/instances/hidden" : "cull/instances",
|
|
266
286
|
layout: pipelineLayout,
|
|
267
287
|
compute: { module, entryPoint: "cull" }
|
|
268
288
|
});
|
|
@@ -309,7 +329,7 @@ class GpuCuller {
|
|
|
309
329
|
label: "cull",
|
|
310
330
|
layout,
|
|
311
331
|
entries: [
|
|
312
|
-
{ binding: 0, resource: { buffer: store.instanceBuffer.
|
|
332
|
+
{ binding: 0, resource: { buffer: store.instanceBuffer.gpuBuffer } },
|
|
313
333
|
{ binding: 1, resource: { buffer: this.batchBuffer.buffer } },
|
|
314
334
|
{ binding: 2, resource: { buffer: this.lodBuffer.buffer } },
|
|
315
335
|
{ binding: 3, resource: { buffer: this.viewBuffer.buffer } },
|
|
@@ -317,7 +337,7 @@ class GpuCuller {
|
|
|
317
337
|
{ binding: 5, resource: { buffer: this.visible.buffer } },
|
|
318
338
|
{ binding: 6, resource: { buffer: this.lodState.buffer } },
|
|
319
339
|
{ binding: 7, resource: { buffer: params.buffer } },
|
|
320
|
-
{ binding: 8, resource: { buffer: store.deform.recordBuffer.
|
|
340
|
+
{ binding: 8, resource: { buffer: store.deform.recordBuffer.gpuBuffer } }
|
|
321
341
|
]
|
|
322
342
|
})
|
|
323
343
|
)
|
|
@@ -434,6 +454,8 @@ class GpuCuller {
|
|
|
434
454
|
while (this.pending.size > 0) await Promise.all(this.pending);
|
|
435
455
|
}
|
|
436
456
|
}
|
|
457
|
+
const CULL_REQUEST = { root: "shard::cull" };
|
|
458
|
+
const HIDDEN_REQUEST = { root: "shard::cull", defines: { HIDDEN: true } };
|
|
437
459
|
function cullLayout(gpu) {
|
|
438
460
|
const C = GPUShaderStage.COMPUTE;
|
|
439
461
|
const read = (binding) => ({
|
|
@@ -467,6 +489,9 @@ const Culler = defineResource("render/Culler", {
|
|
|
467
489
|
function cullTransparent(store, list, params, forward) {
|
|
468
490
|
store.cullTransparentMembers(list, params, forward);
|
|
469
491
|
}
|
|
492
|
+
function cullGround(store, list, params, overlay) {
|
|
493
|
+
store.cullGroundMembers(list, params, overlay);
|
|
494
|
+
}
|
|
470
495
|
function visibleSlots(store, list, mask = SLOT_MASK) {
|
|
471
496
|
const out = /* @__PURE__ */ new Set();
|
|
472
497
|
for (let d = 0; d < list.length; d++) {
|
|
@@ -492,6 +517,7 @@ async function readVisibleSlots(gpu, culler, list, mask = SLOT_MASK) {
|
|
|
492
517
|
export {
|
|
493
518
|
Culler,
|
|
494
519
|
GpuCuller,
|
|
520
|
+
cullGround,
|
|
495
521
|
cullTransparent,
|
|
496
522
|
readVisibleSlots,
|
|
497
523
|
visibleSlots
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Entity, type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { GpuBuffer } from '@aethervtt/shard-gpu';
|
|
3
|
+
export declare const CUTAWAY_SHADERS: Record<string, string>;
|
|
4
|
+
/** A camera's reveal points as uploaded, and the bind group over them. */
|
|
5
|
+
interface CutawayCamera {
|
|
6
|
+
data: Float32Array;
|
|
7
|
+
buffer: GpuBuffer | undefined;
|
|
8
|
+
bindGroup: GPUBindGroup | undefined;
|
|
9
|
+
/** The device generation and buffer version the bind group was made for. */
|
|
10
|
+
boundGeneration: number;
|
|
11
|
+
boundVersion: number;
|
|
12
|
+
/** Points in use (at most MAX_REVEAL_POINTS). */
|
|
13
|
+
count: number;
|
|
14
|
+
/** Points the component listed. */
|
|
15
|
+
listed: number;
|
|
16
|
+
frame: number;
|
|
17
|
+
/** Too many points was reported for this camera. */
|
|
18
|
+
warned: boolean;
|
|
19
|
+
/** The device generation its buffer was last written on (a new device loses the contents). */
|
|
20
|
+
written: number;
|
|
21
|
+
/** Per batch index: 1 when a point can cut one of its Cutaway instances this frame. */
|
|
22
|
+
cuts: Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
export interface CutawayState {
|
|
25
|
+
cameras: Map<Entity, CutawayCamera>;
|
|
26
|
+
frame: number;
|
|
27
|
+
layout: {
|
|
28
|
+
layout: GPUBindGroupLayout;
|
|
29
|
+
generation: number;
|
|
30
|
+
} | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare const CutawayCameras: import("@aethervtt/shard-core").ResourceDef<CutawayState>;
|
|
33
|
+
/**
|
|
34
|
+
* Packs each camera's CutawayView into its uniform, with each point's line of sight from the
|
|
35
|
+
* camera. Only a camera whose values changed uploads (a camera moving changes the lines); a still
|
|
36
|
+
* frame writes nothing and allocates nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare const extractCutaways: import("@aethervtt/shard-core").SystemDef<{
|
|
39
|
+
q: import("@aethervtt/shard-core").Query;
|
|
40
|
+
}>;
|
|
41
|
+
/** render.describe's cutaways section: per camera, its reveal points, radius, margin and edge. */
|
|
42
|
+
export declare function describeCutaways(world: World): Record<string, unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* Cutaways (0070): Cutaway renderables open around a camera's CutawayView points, in every camera
|
|
45
|
+
* pass and no shadow pass. Cameras without reveal points draw the plain variants.
|
|
46
|
+
*/
|
|
47
|
+
export declare const cutawayPlugin: import("@aethervtt/shard-runtime").Plugin;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { defineResource, defineSystem, Last } from "@aethervtt/shard-core";
|
|
2
|
+
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
3
|
+
import { definePlugin, LogResource } from "@aethervtt/shard-runtime";
|
|
4
|
+
import { Camera3d } from "./camera.js";
|
|
5
|
+
import { CutawayPath, CutawayView, MAX_REVEAL_POINTS } from "./cutaway.js";
|
|
6
|
+
import { addRenderFeatures } from "./features.js";
|
|
7
|
+
import {
|
|
8
|
+
INSTANCE_FLOATS,
|
|
9
|
+
InstanceFlags,
|
|
10
|
+
Instances,
|
|
11
|
+
prepareInstances
|
|
12
|
+
} from "./instances.js";
|
|
13
|
+
import { Gpu, RenderDescribers, RenderSet, Shaders } from "./plugin.js";
|
|
14
|
+
import { registerShaders } from "./shaders.js";
|
|
15
|
+
import { Cameras } from "./view.js";
|
|
16
|
+
const CUTAWAY_FLOATS = MAX_REVEAL_POINTS * 8 + 4;
|
|
17
|
+
const DIRS = MAX_REVEAL_POINTS * 4;
|
|
18
|
+
const PARAMS = MAX_REVEAL_POINTS * 8;
|
|
19
|
+
const CUTAWAY_SHADERS = {
|
|
20
|
+
"shard::cutaway": `
|
|
21
|
+
struct Cutaway {
|
|
22
|
+
/** Reveal points (xyz). */
|
|
23
|
+
points: array<vec4f, ${MAX_REVEAL_POINTS}>,
|
|
24
|
+
/**
|
|
25
|
+
* Each point's line of sight, unit (xyz): from the eye through it, or the view direction when
|
|
26
|
+
* orthographic. Made on the CPU once a frame, not per fragment.
|
|
27
|
+
*/
|
|
28
|
+
dirs: array<vec4f, ${MAX_REVEAL_POINTS}>,
|
|
29
|
+
/** radius, margin, edge (m), and how many points. */
|
|
30
|
+
params: vec4f,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@group(3) @binding(0) var<uniform> cutaway: Cutaway;
|
|
34
|
+
|
|
35
|
+
/** InstanceFlags.Cutaway in the record's flags word. */
|
|
36
|
+
const CUTAWAY_FLAG: u32 = 0x10000000u;
|
|
37
|
+
|
|
38
|
+
/** 0 to 1 across an edge band of width \`edge\` ending at 0 (a step when edge is 0). */
|
|
39
|
+
fn cutaway_ramp(inside: f32, edge: f32) -> f32 {
|
|
40
|
+
if (edge <= 0.0) { return select(0.0, 1.0, inside > 0.0); }
|
|
41
|
+
return clamp(inside / edge, 0.0, 1.0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* How cut a world point is, 0 (whole) to 1: within radius of the line from the eye through a
|
|
46
|
+
* reveal point (along the view direction when orthographic), and nearer the camera than the point
|
|
47
|
+
* by more than margin. The edge band ramps both.
|
|
48
|
+
*/
|
|
49
|
+
fn cutaway_amount(world: vec3f) -> f32 {
|
|
50
|
+
let radius = cutaway.params.x;
|
|
51
|
+
let margin = cutaway.params.y;
|
|
52
|
+
let edge = cutaway.params.z;
|
|
53
|
+
let count = u32(cutaway.params.w);
|
|
54
|
+
var amount = 0.0;
|
|
55
|
+
for (var i = 0u; i < count; i++) {
|
|
56
|
+
let d = world - cutaway.points[i].xyz;
|
|
57
|
+
let along = dot(d, cutaway.dirs[i].xyz);
|
|
58
|
+
// Not in front of the point by the margin, or outside the radius: this point cuts nothing.
|
|
59
|
+
let ahead = -along - margin;
|
|
60
|
+
if (ahead <= 0.0) { continue; }
|
|
61
|
+
let across2 = max(dot(d, d) - along * along, 0.0);
|
|
62
|
+
if (across2 >= radius * radius) { continue; }
|
|
63
|
+
amount = max(amount, min(cutaway_ramp(radius - sqrt(across2), edge), cutaway_ramp(ahead, edge)));
|
|
64
|
+
if (amount >= 1.0) { break; }
|
|
65
|
+
}
|
|
66
|
+
return amount;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** An ordered 4\xD74 dither threshold at a pixel, in (0, 1). */
|
|
70
|
+
fn cutaway_threshold(frag: vec2f) -> f32 {
|
|
71
|
+
let x = u32(frag.x);
|
|
72
|
+
let y = u32(frag.y);
|
|
73
|
+
let low = (((x ^ y) & 1u) << 1u) | (y & 1u);
|
|
74
|
+
let high = ((((x ^ y) >> 1u) & 1u) << 1u) | ((y >> 1u) & 1u);
|
|
75
|
+
return (f32(low * 4u + high) + 0.5) / 16.0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Removes the fragment of a Cutaway instance that stands in front of a reveal point. */
|
|
79
|
+
fn cutaway_clip(world: vec3f, frag: vec2f, flags: u32) {
|
|
80
|
+
if ((flags & CUTAWAY_FLAG) == 0u) { return; }
|
|
81
|
+
if (cutaway_amount(world) > cutaway_threshold(frag)) { discard; }
|
|
82
|
+
}`
|
|
83
|
+
};
|
|
84
|
+
const CutawayCameras = defineResource("render/CutawayCameras", {
|
|
85
|
+
description: "Each camera's reveal points as the cutaway variants read them (0070).",
|
|
86
|
+
init: () => ({ cameras: /* @__PURE__ */ new Map(), frame: 0, layout: void 0 })
|
|
87
|
+
});
|
|
88
|
+
function cameraOf(state, camera) {
|
|
89
|
+
let c = state.cameras.get(camera);
|
|
90
|
+
if (!c) {
|
|
91
|
+
c = {
|
|
92
|
+
data: new Float32Array(CUTAWAY_FLOATS),
|
|
93
|
+
buffer: void 0,
|
|
94
|
+
bindGroup: void 0,
|
|
95
|
+
boundGeneration: -1,
|
|
96
|
+
boundVersion: -1,
|
|
97
|
+
count: -1,
|
|
98
|
+
listed: 0,
|
|
99
|
+
frame: -1,
|
|
100
|
+
warned: false,
|
|
101
|
+
written: -1,
|
|
102
|
+
cuts: new Uint8Array(64)
|
|
103
|
+
};
|
|
104
|
+
state.cameras.set(camera, c);
|
|
105
|
+
}
|
|
106
|
+
return c;
|
|
107
|
+
}
|
|
108
|
+
function warnTooMany(world, camera, listed) {
|
|
109
|
+
world.tryResource(LogResource)?.log(
|
|
110
|
+
"warn",
|
|
111
|
+
`Camera ${camera} has ${listed} reveal points; only the first ${MAX_REVEAL_POINTS} cut`,
|
|
112
|
+
{
|
|
113
|
+
code: "render/too-many-reveal-points",
|
|
114
|
+
hint: `Give CutawayView at most ${MAX_REVEAL_POINTS} points: the party, or the selected tokens.`,
|
|
115
|
+
data: { camera, points: listed }
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function put(d, o, v) {
|
|
120
|
+
if (d[o] === Math.fround(v)) return false;
|
|
121
|
+
d[o] = v;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const extractCutaways = defineSystem({
|
|
125
|
+
name: "render/extract-cutaways",
|
|
126
|
+
description: "Packs cameras' reveal points for the cutaway variants (0070).",
|
|
127
|
+
setup: (world) => ({ q: world.query({ with: [Camera3d, CutawayView] }) }),
|
|
128
|
+
run: ({ q }, world) => {
|
|
129
|
+
const state = world.resource(CutawayCameras);
|
|
130
|
+
const gpu = world.resource(Gpu);
|
|
131
|
+
const cameras = world.resource(Cameras);
|
|
132
|
+
const store = world.resource(Instances);
|
|
133
|
+
const frame = ++state.frame;
|
|
134
|
+
for (const table of q.tables) {
|
|
135
|
+
const points = table.column(CutawayView, "points");
|
|
136
|
+
const radius = table.column(CutawayView, "radius");
|
|
137
|
+
const margin = table.column(CutawayView, "margin");
|
|
138
|
+
const edge = table.column(CutawayView, "edge");
|
|
139
|
+
for (let i = 0; i < table.count; i++) {
|
|
140
|
+
const camera = table.entities[i];
|
|
141
|
+
const c = cameraOf(state, camera);
|
|
142
|
+
c.frame = frame;
|
|
143
|
+
const list = points[i] ?? EMPTY;
|
|
144
|
+
const listed = list.length;
|
|
145
|
+
const count = Math.min(listed, MAX_REVEAL_POINTS);
|
|
146
|
+
if (listed > MAX_REVEAL_POINTS && !c.warned) {
|
|
147
|
+
c.warned = true;
|
|
148
|
+
warnTooMany(world, camera, listed);
|
|
149
|
+
}
|
|
150
|
+
c.listed = listed;
|
|
151
|
+
const cam = cameras.get(camera);
|
|
152
|
+
const n = cam ? count : 0;
|
|
153
|
+
const d = c.data;
|
|
154
|
+
let changed = c.count !== n;
|
|
155
|
+
for (let k = 0; k < n; k++) {
|
|
156
|
+
const p = list[k];
|
|
157
|
+
const o = k * 4;
|
|
158
|
+
changed = put(d, o, p[0]) || changed;
|
|
159
|
+
changed = put(d, o + 1, p[1]) || changed;
|
|
160
|
+
changed = put(d, o + 2, p[2]) || changed;
|
|
161
|
+
const f = cam.forward;
|
|
162
|
+
let x = f[0];
|
|
163
|
+
let y = f[1];
|
|
164
|
+
let z = f[2];
|
|
165
|
+
if (!cam.orthographic) {
|
|
166
|
+
const e = cam.position;
|
|
167
|
+
const dx = p[0] - e[0];
|
|
168
|
+
const dy = p[1] - e[1];
|
|
169
|
+
const dz = p[2] - e[2];
|
|
170
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
171
|
+
if (len > 1e-5) {
|
|
172
|
+
x = dx / len;
|
|
173
|
+
y = dy / len;
|
|
174
|
+
z = dz / len;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
changed = put(d, DIRS + o, x) || changed;
|
|
178
|
+
changed = put(d, DIRS + o + 1, y) || changed;
|
|
179
|
+
changed = put(d, DIRS + o + 2, z) || changed;
|
|
180
|
+
}
|
|
181
|
+
changed = put(d, PARAMS, radius[i]) || changed;
|
|
182
|
+
changed = put(d, PARAMS + 1, margin[i]) || changed;
|
|
183
|
+
changed = put(d, PARAMS + 2, edge[i]) || changed;
|
|
184
|
+
changed = put(d, PARAMS + 3, n) || changed;
|
|
185
|
+
c.count = n;
|
|
186
|
+
if (n > 0) markCuts(c, store);
|
|
187
|
+
if (n > 0 && (changed || c.written !== gpu.generation)) {
|
|
188
|
+
c.written = gpu.generation;
|
|
189
|
+
c.buffer ??= new GpuBuffer(gpu, {
|
|
190
|
+
label: `camera:${camera}/cutaway`,
|
|
191
|
+
usage: GPUBufferUsage.UNIFORM,
|
|
192
|
+
size: CUTAWAY_FLOATS * 4
|
|
193
|
+
});
|
|
194
|
+
c.buffer.write(d);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let live = 0;
|
|
199
|
+
for (const table of q.tables) live += table.count;
|
|
200
|
+
if (state.cameras.size > live) {
|
|
201
|
+
for (const [camera, c] of state.cameras) {
|
|
202
|
+
if (c.frame === frame) continue;
|
|
203
|
+
c.buffer?.destroy();
|
|
204
|
+
state.cameras.delete(camera);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const EMPTY = [];
|
|
210
|
+
const box = new Float32Array(6);
|
|
211
|
+
function slotBox(f, o, b) {
|
|
212
|
+
const cx = (b[0] + b[3]) * 0.5;
|
|
213
|
+
const cy = (b[1] + b[4]) * 0.5;
|
|
214
|
+
const cz = (b[2] + b[5]) * 0.5;
|
|
215
|
+
const ex = (b[3] - b[0]) * 0.5;
|
|
216
|
+
const ey = (b[4] - b[1]) * 0.5;
|
|
217
|
+
const ez = (b[5] - b[2]) * 0.5;
|
|
218
|
+
for (let r = 0; r < 3; r++) {
|
|
219
|
+
const m = o + r * 4;
|
|
220
|
+
box[r] = f[m] * cx + f[m + 1] * cy + f[m + 2] * cz + f[m + 3];
|
|
221
|
+
box[3 + r] = Math.abs(f[m]) * ex + Math.abs(f[m + 1]) * ey + Math.abs(f[m + 2]) * ez;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function reaches(d, k, radius, margin) {
|
|
225
|
+
const o = k * 4;
|
|
226
|
+
let t0 = 0;
|
|
227
|
+
let t1 = Number.POSITIVE_INFINITY;
|
|
228
|
+
for (let a = 0; a < 3; a++) {
|
|
229
|
+
const dir = -d[DIRS + o + a];
|
|
230
|
+
const start = d[o + a] + dir * margin;
|
|
231
|
+
const lo = box[a] - box[3 + a] - radius;
|
|
232
|
+
const hi = box[a] + box[3 + a] + radius;
|
|
233
|
+
if (Math.abs(dir) < 1e-9) {
|
|
234
|
+
if (start < lo || start > hi) return false;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
let near = (lo - start) / dir;
|
|
238
|
+
let far = (hi - start) / dir;
|
|
239
|
+
if (near > far) {
|
|
240
|
+
const t = near;
|
|
241
|
+
near = far;
|
|
242
|
+
far = t;
|
|
243
|
+
}
|
|
244
|
+
if (near > t0) t0 = near;
|
|
245
|
+
if (far < t1) t1 = far;
|
|
246
|
+
if (t0 > t1) return false;
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
function markCuts(c, store) {
|
|
251
|
+
const batches = store.batches;
|
|
252
|
+
if (c.cuts.length < batches.length) c.cuts = new Uint8Array(batches.length * 2);
|
|
253
|
+
const cuts = c.cuts;
|
|
254
|
+
const d = c.data;
|
|
255
|
+
const n = d[PARAMS + 3];
|
|
256
|
+
const radius = d[PARAMS];
|
|
257
|
+
const margin = d[PARAMS + 1];
|
|
258
|
+
for (let b = 0; b < batches.length; b++) {
|
|
259
|
+
const batch = batches[b];
|
|
260
|
+
cuts[b] = 0;
|
|
261
|
+
if (batch.cutaway === 0) continue;
|
|
262
|
+
if (batch.memberCount < batch.count) {
|
|
263
|
+
cuts[b] = 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
for (let m = 0; m < batch.memberCount && cuts[b] === 0; m++) {
|
|
267
|
+
const slot = batch.members[m];
|
|
268
|
+
const flags = store.flags[slot];
|
|
269
|
+
if ((flags & InstanceFlags.Cutaway) === 0) continue;
|
|
270
|
+
if (flags & InstanceFlags.Skinned) {
|
|
271
|
+
cuts[b] = 1;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
slotBox(store.f32, slot * INSTANCE_FLOATS, batch.mesh.bounds);
|
|
275
|
+
for (let k = 0; k < n; k++) {
|
|
276
|
+
if (reaches(d, k, radius, margin)) {
|
|
277
|
+
cuts[b] = 1;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function support(world) {
|
|
285
|
+
const state = world.resource(CutawayCameras);
|
|
286
|
+
const layout = (gpu) => {
|
|
287
|
+
if (!state.layout || state.layout.generation !== gpu.generation) {
|
|
288
|
+
state.layout = {
|
|
289
|
+
layout: gpu.layouts.bindGroupLayout({
|
|
290
|
+
label: "cutaway",
|
|
291
|
+
entries: [
|
|
292
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
293
|
+
]
|
|
294
|
+
}),
|
|
295
|
+
generation: gpu.generation
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return state.layout.layout;
|
|
299
|
+
};
|
|
300
|
+
return {
|
|
301
|
+
layout,
|
|
302
|
+
cuts(camera, batch) {
|
|
303
|
+
const c = state.cameras.get(camera);
|
|
304
|
+
return c !== void 0 && c.frame === state.frame && c.count > 0 && c.cuts[batch] === 1;
|
|
305
|
+
},
|
|
306
|
+
bindGroup(gpu, camera) {
|
|
307
|
+
const c = state.cameras.get(camera);
|
|
308
|
+
if (!c || c.frame !== state.frame || c.count <= 0 || !c.buffer) return void 0;
|
|
309
|
+
const buffer = c.buffer;
|
|
310
|
+
const gpuBuffer = buffer.buffer;
|
|
311
|
+
if (!c.bindGroup || c.boundGeneration !== gpu.generation || c.boundVersion !== buffer.version) {
|
|
312
|
+
c.bindGroup = gpu.device.createBindGroup({
|
|
313
|
+
label: "cutaway",
|
|
314
|
+
layout: layout(gpu),
|
|
315
|
+
entries: [{ binding: 0, resource: { buffer: gpuBuffer } }]
|
|
316
|
+
});
|
|
317
|
+
c.boundGeneration = gpu.generation;
|
|
318
|
+
c.boundVersion = buffer.version;
|
|
319
|
+
}
|
|
320
|
+
return c.bindGroup;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function describeCutaways(world) {
|
|
325
|
+
const out = {};
|
|
326
|
+
world.query({ with: [Camera3d, CutawayView] }).each((camera, row, table) => {
|
|
327
|
+
const points = table.column(CutawayView, "points")[row] ?? [];
|
|
328
|
+
out[`camera:${camera}`] = {
|
|
329
|
+
points: Array.from(points.slice(0, MAX_REVEAL_POINTS), (p) => [p[0], p[1], p[2]]),
|
|
330
|
+
ignored: Math.max(0, points.length - MAX_REVEAL_POINTS),
|
|
331
|
+
radius: table.column(CutawayView, "radius")[row],
|
|
332
|
+
margin: table.column(CutawayView, "margin")[row],
|
|
333
|
+
edge: table.column(CutawayView, "edge")[row]
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
const cutawayPlugin = definePlugin({
|
|
339
|
+
name: "render/cutaway",
|
|
340
|
+
dependencies: ["render/forward"],
|
|
341
|
+
provides: [CutawayCameras],
|
|
342
|
+
build(app) {
|
|
343
|
+
app.world.initResource(CutawayCameras);
|
|
344
|
+
app.insertResource(CutawayPath, support(app.world));
|
|
345
|
+
app.addSystems(Last, extractCutaways.inSet(RenderSet.Prepare).after(prepareInstances));
|
|
346
|
+
},
|
|
347
|
+
ready(app) {
|
|
348
|
+
registerShaders(app.world.resource(Shaders), CUTAWAY_SHADERS);
|
|
349
|
+
app.world.initResource(RenderDescribers).set("cutaways", describeCutaways);
|
|
350
|
+
addRenderFeatures(app.world, {
|
|
351
|
+
name: "render/cutaway",
|
|
352
|
+
description: "Cutaway surfaces opened around cameras' reveal points.",
|
|
353
|
+
nodes: [],
|
|
354
|
+
baseline: { strategy: "The same discard; points in a uniform block" }
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
export {
|
|
359
|
+
CUTAWAY_SHADERS,
|
|
360
|
+
CutawayCameras,
|
|
361
|
+
cutawayPlugin,
|
|
362
|
+
describeCutaways,
|
|
363
|
+
extractCutaways
|
|
364
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Entity } from '@aethervtt/shard-core';
|
|
2
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
3
|
+
/** Reveal points a camera can have; more are ignored with `render/too-many-reveal-points`. */
|
|
4
|
+
export declare const MAX_REVEAL_POINTS = 16;
|
|
5
|
+
export declare const Cutaway: import("@aethervtt/shard-core").ComponentDef<{}>;
|
|
6
|
+
export declare const CutawayView: import("@aethervtt/shard-core").ComponentDef<{
|
|
7
|
+
readonly points: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3[], "object">;
|
|
8
|
+
readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
9
|
+
readonly margin: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
10
|
+
readonly edge: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Set by `cutawayPlugin`: what the camera passes need to draw the cutaway variants of pipelines.
|
|
14
|
+
* Forward asks it per view, so core imports no cutaway code.
|
|
15
|
+
*/
|
|
16
|
+
export interface CutawaySupport {
|
|
17
|
+
/** The bind group layout cutaway pipelines take at group 3. */
|
|
18
|
+
layout(gpu: GpuContext): GPUBindGroupLayout;
|
|
19
|
+
/** The camera's reveal points, bound at group 3: undefined when it has none this frame. */
|
|
20
|
+
bindGroup(gpu: GpuContext, camera: Entity): GPUBindGroup | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a point of the camera can cut an instance of the batch this frame: its bounds reach a
|
|
23
|
+
* reveal cylinder. The others draw the plain variant (no discard, so no per-fragment cost).
|
|
24
|
+
*/
|
|
25
|
+
cuts(camera: Entity, batch: number): boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const CutawayPath: import("@aethervtt/shard-core").ResourceDef<CutawaySupport>;
|
package/dist/cutaway.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineComponent, defineResource, t } from "@aethervtt/shard-core";
|
|
2
|
+
import { Camera3d } from "./camera.js";
|
|
3
|
+
const MAX_REVEAL_POINTS = 16;
|
|
4
|
+
const Cutaway = defineComponent(
|
|
5
|
+
"render/Cutaway",
|
|
6
|
+
{},
|
|
7
|
+
{
|
|
8
|
+
description: "Tag on a renderable: it may be cut away around a camera's reveal points (CutawayView). Shadows never cut."
|
|
9
|
+
}
|
|
10
|
+
);
|
|
11
|
+
const CutawayView = defineComponent(
|
|
12
|
+
"render/CutawayView",
|
|
13
|
+
{
|
|
14
|
+
points: t.list(t.vec3, {
|
|
15
|
+
description: `World points to reveal, at most ${MAX_REVEAL_POINTS}: a token's position, the party.`
|
|
16
|
+
}),
|
|
17
|
+
radius: t.f32({
|
|
18
|
+
default: 2.5,
|
|
19
|
+
min: 0,
|
|
20
|
+
unit: "m",
|
|
21
|
+
description: "How far from each point's line of sight a Cutaway surface is cut."
|
|
22
|
+
}),
|
|
23
|
+
margin: t.f32({
|
|
24
|
+
default: 0.6,
|
|
25
|
+
min: 0,
|
|
26
|
+
unit: "m",
|
|
27
|
+
description: "How far in front of a point the cut starts, so the floor it stands on, and a stairwell rim, stay."
|
|
28
|
+
}),
|
|
29
|
+
edge: t.f32({
|
|
30
|
+
default: 0.3,
|
|
31
|
+
min: 0,
|
|
32
|
+
unit: "m",
|
|
33
|
+
description: "Width of the dithered rim; 0 is a hard cut."
|
|
34
|
+
})
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
description: "Cuts Cutaway surfaces away between this camera and its reveal points (0070): a disc in a roof from above, a hole in a wall at an angle. Every camera pass (picking too) sees the hole; shadows do not.",
|
|
38
|
+
requires: [Camera3d]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
const CutawayPath = defineResource("render/CutawayPath", {
|
|
42
|
+
description: "Present when cutaways (cutawayPlugin) are installed."
|
|
43
|
+
});
|
|
44
|
+
export {
|
|
45
|
+
Cutaway,
|
|
46
|
+
CutawayPath,
|
|
47
|
+
CutawayView,
|
|
48
|
+
MAX_REVEAL_POINTS
|
|
49
|
+
};
|