@vgai/engine 0.5.16 → 0.5.18
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/adapter/adapter-module.d.ts +1 -16
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +1 -11
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +0 -2
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +3 -6
- package/dist/adapter/renderer-config.d.ts +83 -0
- package/dist/adapter/renderer-config.d.ts.map +1 -0
- package/dist/adapter/renderer-config.js +121 -0
- package/dist/adapter/root-adapter.d.ts +6 -2
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-seam-contract.d.ts +5 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -1
- package/dist/adapter/root-seam-contract.js +1 -0
- package/dist/adapter/setup-three-root-adapter.js +9 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +92 -41
- package/dist/data/curve.d.ts +1 -1
- package/dist/data/curve.js +2 -2
- package/dist/dev/instruments.d.ts +4 -4
- package/dist/dev/instruments.js +4 -4
- package/dist/dev/register-render-vitals.d.ts +2 -2
- package/dist/dev/register-render-vitals.js +2 -2
- package/dist/dev/static-batch-advisor.d.ts +1 -1
- package/dist/dev/static-batch-advisor.js +2 -2
- package/dist/input/input-manager.d.ts +15 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +22 -0
- package/dist/manifest/load.d.ts +0 -3
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +0 -4
- package/dist/manifest/schema.d.ts +0 -2
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +3 -24
- package/dist/pixi/authoring.d.ts +33 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +86 -6
- package/dist/react/world-state.d.ts +12 -0
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/react/world-state.js +14 -0
- package/dist/render/soft-particle-depth.d.ts +3 -3
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +8 -13
- package/dist/runtime/create-runtime.d.ts +2 -16
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +36 -38
- package/dist/runtime/dev-build.d.ts +37 -0
- package/dist/runtime/dev-build.d.ts.map +1 -0
- package/dist/runtime/dev-build.js +49 -0
- package/dist/runtime/game.d.ts +9 -1
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +25 -1
- package/dist/runtime/mount-manifest.d.ts +0 -13
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +2 -21
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
- package/dist/world3d-react/engine-bridge.js +28 -3
- package/dist/world3d-react/index.d.ts +2 -2
- package/dist/world3d-react/index.d.ts.map +1 -1
- package/dist/world3d-react/index.js +2 -2
- package/dist/world3d-react/r3f-adapter.d.ts +2 -2
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +90 -15
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +12 -2
- package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
- package/dist/world3d-react/rapier-physics-bridge.js +3 -3
- package/dist/world3d-react/renderer-config.d.ts +7 -69
- package/dist/world3d-react/renderer-config.d.ts.map +1 -1
- package/dist/world3d-react/renderer-config.js +7 -108
- package/dist/world3d-react/world-context.d.ts +43 -0
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +89 -46
- package/dist-config/config.js.map +1 -1
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +3 -3
- package/schemas/vgai-project.schema.json +2 -7
- package/src/adapter/adapter-module.ts +1 -25
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +3 -8
- package/src/adapter/renderer-config.ts +178 -0
- package/src/adapter/root-adapter.ts +6 -2
- package/src/adapter/root-seam-contract.ts +1 -0
- package/src/adapter/setup-three-root-adapter.ts +9 -9
- package/src/canvas-react/pixi-react-adapter.tsx +98 -53
- package/src/data/curve.ts +2 -2
- package/src/dev/instruments.ts +4 -4
- package/src/dev/register-render-vitals.ts +2 -2
- package/src/dev/static-batch-advisor.ts +2 -2
- package/src/input/input-manager.ts +23 -0
- package/src/manifest/load.ts +0 -7
- package/src/manifest/schema.ts +3 -26
- package/src/pixi/authoring.ts +98 -6
- package/src/react/world-state.tsx +15 -0
- package/src/render/soft-particle-depth.ts +8 -13
- package/src/runtime/create-runtime.ts +38 -52
- package/src/runtime/dev-build.ts +49 -0
- package/src/runtime/game.ts +31 -2
- package/src/runtime/mount-manifest.ts +2 -33
- package/src/world3d-react/engine-bridge.ts +28 -3
- package/src/world3d-react/index.ts +5 -5
- package/src/world3d-react/r3f-adapter.tsx +89 -20
- package/src/world3d-react/r3f-root-factory.tsx +12 -2
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- package/src/world3d-react/renderer-config.ts +7 -164
- package/src/world3d-react/world-context.ts +100 -51
- package/dist/runtime/dev-layers.d.ts +0 -34
- package/dist/runtime/dev-layers.d.ts.map +0 -1
- package/dist/runtime/dev-layers.js +0 -46
- package/src/runtime/dev-layers.ts +0 -46
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/engine",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.18",
|
|
6
6
|
"description": "Readable TypeScript game engine and universal host for Three.js, PixiJS, and React games.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"game-engine",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"notes": "Grepped packages/engine/src for ccd/CCD/setCcdEnabled: zero matches. The engine constructs no rigid bodies itself — games build their own descs via ctx.RAPIER — and nothing engine-side ever enables ccd. Workaround: keep dynamic-body per-step travel small relative to the thinnest collider, or add intermediate colliders; a game may also call setCcdEnabled on its own bodies directly.",
|
|
17
17
|
"evidenceHashes": {
|
|
18
|
-
"packages/engine/src/adapter/setup-three-root-adapter.ts": "
|
|
18
|
+
"packages/engine/src/adapter/setup-three-root-adapter.ts": "14cef0975f38ecceb25b607ef62232c1002bdd172a3456571f50aa9e39263c0c"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
{
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"notes": "gamepad disconnect/reconnect needs no special-cased code: poll() re-reads navigator.getGamepads() and every read skips a null slot, so a disconnected gamepad's actions go neutral with a correct isJustReleased edge for free — see input-device-backends-f2.test.ts.",
|
|
44
44
|
"evidenceHashes": {
|
|
45
45
|
"packages/engine/src/input/input-types.ts": "62d9005609a72b55a8214c869d1b27825b1a17b9389f3f3f5d0b04725331b382",
|
|
46
|
-
"packages/engine/src/input/input-manager.ts": "
|
|
46
|
+
"packages/engine/src/input/input-manager.ts": "7d34683759d7ab7fd20ca6f5b5e646eb76a31b00c77977816f4ef863ccb06e62"
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
{
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"notes": "Limits: ARCHITECTURE-CORE.md. Any editor capability a mount does not reach stands warned per capability until it works.",
|
|
74
74
|
"evidenceHashes": {
|
|
75
75
|
"vendor/games/verify-unaltered.mjs": "257a362030a10f4d72a3280815069fbd83f436e1619391635b6f8df2ddd6c339",
|
|
76
|
-
"packages/engine/src/manifest/schema.ts": "
|
|
76
|
+
"packages/engine/src/manifest/schema.ts": "ce385bc9181b82f9d5044da6a41a025a5cb8ba1fc7156263665080c9fdf2b11c"
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
{
|
|
@@ -193,11 +193,6 @@
|
|
|
193
193
|
"description": "Whether play-mode pause/step applies to this root",
|
|
194
194
|
"type": "boolean"
|
|
195
195
|
},
|
|
196
|
-
"dev": {
|
|
197
|
-
"default": false,
|
|
198
|
-
"description": "Marks this root as a DEV LAYER: the game's own dev GUI, not shipped game content. Four readers act on it. `mountManifestRoots` mounts it only when dev layers are enabled (`devLayersEnabled`, runtime/dev-layers.ts), so a production build carries no dev GUI; the host stacks every dev root ABOVE every non-dev root (a dev layer sits topmost, and as a DOM layer it steals no input from the game while closed); the play compositor EXCLUDES dev layers from a capture unless the capture asks for them; and the editor mounts a dev root as edit-time CHROME rather than authorable content (no hierarchy entry, no OID stamping, no JSX write-back, not selectable).",
|
|
199
|
-
"type": "boolean"
|
|
200
|
-
},
|
|
201
196
|
"loop": {
|
|
202
197
|
"default": "gated",
|
|
203
198
|
"description": "gated: host-driven loop (default). self-driven: this root drives its own loop (composited, unsynchronized)",
|
|
@@ -214,7 +209,7 @@
|
|
|
214
209
|
],
|
|
215
210
|
"additionalProperties": false
|
|
216
211
|
},
|
|
217
|
-
"description": "The Game's explicit, non-empty adapter-root composition. There is no implicit root. Root ids are unique, and a project declares at most ONE world root per medium — one `three`, one `dom`, one canvas-surface root (first-party canvas, canvas ingest and canvas module are the same medium).
|
|
212
|
+
"description": "The Game's explicit, non-empty adapter-root composition. There is no implicit root. Root ids are unique, and a project declares at most ONE world root per medium — one `three`, one `dom`, one canvas-surface root (first-party canvas, canvas ingest and canvas module are the same medium)."
|
|
218
213
|
},
|
|
219
214
|
"authoring": {
|
|
220
215
|
"description": "Editor-only authoring metadata; never runtime ownership",
|
|
@@ -336,7 +331,7 @@
|
|
|
336
331
|
"properties": {
|
|
337
332
|
"antialias": {
|
|
338
333
|
"type": "boolean",
|
|
339
|
-
"description": "Whether every three root is built with a multisampled drawing buffer. This is the ONE render property a world cannot declare for itself (
|
|
334
|
+
"description": "Whether every three root is built with a multisampled drawing buffer. This is the ONE render property a world cannot declare for itself (adapter/renderer-config.ts): a WebGL context fixes its sample count at CREATION from this boolean, long before a world mounts, so it belongs to the PROJECT. The runtime reader is mount-manifest.ts, which threads it into createHostRenderer for each three root. WebGL exposes no sample COUNT — the implementation picks one (4x on every desktop browser measured), so a source engine that authored 8x/16x gets multisampling but not its exact count."
|
|
340
335
|
}
|
|
341
336
|
},
|
|
342
337
|
"required": [
|
|
@@ -100,18 +100,6 @@ export interface AdapterRegion {
|
|
|
100
100
|
readonly id: string;
|
|
101
101
|
/** What the host hands this root. */
|
|
102
102
|
readonly surface: AdapterSurface;
|
|
103
|
-
/**
|
|
104
|
-
* `true` = this region grades the game's own DEV LAYER (the manifest root's
|
|
105
|
-
* `dev: true`), not shipped game content.
|
|
106
|
-
*
|
|
107
|
-
* Carried here because a dev root occupies the same MEDIUM as the content
|
|
108
|
-
* root beside it — a dev GUI is a `dom` root next to the HUD's `dom` root —
|
|
109
|
-
* and anything that maps a subject to "the region on this surface" would
|
|
110
|
-
* otherwise be choosing between them by array order. A region consumer that
|
|
111
|
-
* means shipped content says so by skipping `dev` regions (the loader's
|
|
112
|
-
* `regionForSurface` does), rather than by guessing which one matched.
|
|
113
|
-
*/
|
|
114
|
-
readonly dev: boolean;
|
|
115
103
|
/** Projection library, by name. */
|
|
116
104
|
readonly projector: ProjectorName;
|
|
117
105
|
/**
|
|
@@ -235,12 +223,6 @@ const AdapterRegionSchema = z
|
|
|
235
223
|
.object({
|
|
236
224
|
id: z.string().min(1).describe('Manifest root id this region grades'),
|
|
237
225
|
surface: z.enum(['three', 'canvas', 'dom']).describe('Render surface the host hands this root'),
|
|
238
|
-
dev: z
|
|
239
|
-
.boolean()
|
|
240
|
-
.describe(
|
|
241
|
-
"true = this region grades the game's own dev layer, not shipped content (the " +
|
|
242
|
-
"manifest root's `dev: true`)",
|
|
243
|
-
),
|
|
244
226
|
projector: z.enum(PROJECTOR_NAMES).describe('Projection library serving this surface, by name'),
|
|
245
227
|
dialect: z
|
|
246
228
|
.string()
|
|
@@ -431,7 +413,6 @@ export function unmatchedRegionIncludeIds(
|
|
|
431
413
|
/** One root's region, before any declared parameters are merged onto it. */
|
|
432
414
|
function derivedRegion(root: ResolvedAdapterRoot): AdapterRegion {
|
|
433
415
|
const surface = root.surface;
|
|
434
|
-
const dev = root.dev;
|
|
435
416
|
const firstParty = root.adapter.type === 'builtin';
|
|
436
417
|
const projector: ProjectorName = surface === 'canvas' ? 'pixi' : surface;
|
|
437
418
|
const basis = NATIVE_REGION_BASIS;
|
|
@@ -439,7 +420,6 @@ function derivedRegion(root: ResolvedAdapterRoot): AdapterRegion {
|
|
|
439
420
|
return {
|
|
440
421
|
id: root.id,
|
|
441
422
|
surface,
|
|
442
|
-
dev,
|
|
443
423
|
projector,
|
|
444
424
|
dialect: null,
|
|
445
425
|
anchors: ['live-only'] as const,
|
|
@@ -450,7 +430,6 @@ function derivedRegion(root: ResolvedAdapterRoot): AdapterRegion {
|
|
|
450
430
|
return {
|
|
451
431
|
id: root.id,
|
|
452
432
|
surface,
|
|
453
|
-
dev,
|
|
454
433
|
projector,
|
|
455
434
|
dialect: 'jsx',
|
|
456
435
|
anchors: ['source-prop', 'source-structure'] as const,
|
|
@@ -460,7 +439,6 @@ function derivedRegion(root: ResolvedAdapterRoot): AdapterRegion {
|
|
|
460
439
|
return {
|
|
461
440
|
id: root.id,
|
|
462
441
|
surface,
|
|
463
|
-
dev,
|
|
464
442
|
projector,
|
|
465
443
|
dialect: surface === 'three' ? 'r3f' : 'pixi-react',
|
|
466
444
|
anchors: ['source-prop', 'source-structure', 'construction-literal'] as const,
|
|
@@ -474,9 +452,7 @@ function derivedRegion(root: ResolvedAdapterRoot): AdapterRegion {
|
|
|
474
452
|
|
|
475
453
|
/**
|
|
476
454
|
* The rule form of a finder's region targeting: every region the host mounts
|
|
477
|
-
* from an exported composition
|
|
478
|
-
* DEV layer. A dev root is the game's own dev GUI — the manifest already says
|
|
479
|
-
* so (`dev: true`), so skipping it reads a declaration rather than guessing.
|
|
455
|
+
* from an exported composition — a root with an `entry`.
|
|
480
456
|
*/
|
|
481
457
|
export const EXPORTED_COMPOSITION_REGIONS = 'exported-composition-regions';
|
|
482
458
|
|
|
@@ -53,8 +53,6 @@ export interface EntrypointSource {
|
|
|
53
53
|
readonly path: string;
|
|
54
54
|
/** The module's bytes; `null` when they could not be read. */
|
|
55
55
|
readonly source: string | null;
|
|
56
|
-
/** The root's declared `dev` flag — a dev layer is the game's own dev GUI. */
|
|
57
|
-
readonly dev: boolean;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
export interface ScenesFromEntrypointSelectionInput {
|
|
@@ -284,11 +282,8 @@ function sourceFor(
|
|
|
284
282
|
/**
|
|
285
283
|
* Which entrypoints this selection targets.
|
|
286
284
|
*
|
|
287
|
-
* The RULE form is every exported-composition region
|
|
288
|
-
*
|
|
289
|
-
* GUI is edit-time chrome, not authorable game content — never a name
|
|
290
|
-
* heuristic. A named region with no matching entrypoint becomes a note, not a
|
|
291
|
-
* silent omission.
|
|
285
|
+
* The RULE form is every exported-composition region. A named region with no
|
|
286
|
+
* matching entrypoint becomes a note, not a silent omission.
|
|
292
287
|
*/
|
|
293
288
|
function targetedEntrypoints(
|
|
294
289
|
params: ScenesFromEntrypointSelectionParams,
|
|
@@ -296,7 +291,7 @@ function targetedEntrypoints(
|
|
|
296
291
|
notes: string[],
|
|
297
292
|
): EntrypointSource[] {
|
|
298
293
|
if (params.regions === EXPORTED_COMPOSITION_REGIONS) {
|
|
299
|
-
return entrypoints
|
|
294
|
+
return [...entrypoints];
|
|
300
295
|
}
|
|
301
296
|
const found: EntrypointSource[] = [];
|
|
302
297
|
for (const regionId of params.regions) {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapter/renderer-config.ts — the seam a WORLD uses to state how its own frame is rendered.
|
|
3
|
+
*
|
|
4
|
+
* It lives in the ADAPTER SEAM, not in a surface entry, because `WorldRendererConfig` IS part of
|
|
5
|
+
* the root contract: `MountedThreeRoot.rendererConfig` reports it (`./root-adapter.ts`) and
|
|
6
|
+
* `./root-seam-contract.ts` already lists it as a contract field. The seam defines the shape; the
|
|
7
|
+
* surface entries and the editor CONFORM to it — `world3d-react/r3f-adapter.tsx` is the declarer,
|
|
8
|
+
* `runtime/create-runtime.ts` the host, `editor/src/components/ViewportPanel.tsx` the editor's
|
|
9
|
+
* applier. Do NOT move it back under `world3d-react/`: nothing here touches react, and a core
|
|
10
|
+
* adapter file reaching into a react entry is exactly what `react-core-import-ban.test.ts` forbids
|
|
11
|
+
* (that ban carries no type-only carve-out on purpose — unlike its pixi sibling, whose carve-out
|
|
12
|
+
* exists only because `PIXI.Container` is a third-party type core cannot relocate).
|
|
13
|
+
*
|
|
14
|
+
* The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
|
|
15
|
+
* engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
|
|
16
|
+
* a world authored against them and WRONG for a world that was authored against a different
|
|
17
|
+
* engine's pipeline — an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
|
|
18
|
+
* at all, so ACES quietly desaturates and darkens every colour its author picked.
|
|
19
|
+
*
|
|
20
|
+
* So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
|
|
21
|
+
* host's renderer for the life of the mount, restoring what it found on dispose. Three properties
|
|
22
|
+
* of that shape are load-bearing:
|
|
23
|
+
*
|
|
24
|
+
* - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
|
|
25
|
+
* property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
|
|
26
|
+
* module-level three global, so one world's declaration cannot change how the editor's own
|
|
27
|
+
* viewport, another root, or a thumbnail bake renders.
|
|
28
|
+
* - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
|
|
29
|
+
* is never written, so declaring a tone mapping does not silently reset the clear colour.
|
|
30
|
+
* - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
|
|
31
|
+
* back what it found would leak its pipeline into whatever mounts next.
|
|
32
|
+
*
|
|
33
|
+
* This is deliberately NOT a general render-settings system. It carries what a world can honestly
|
|
34
|
+
* state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
|
|
35
|
+
* (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
|
|
36
|
+
* declared here, because there would be no honest moment to apply it.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import type * as THREE from 'three';
|
|
40
|
+
|
|
41
|
+
/** The tone-mapping operators three exposes, named as data rather than as three's numeric enum. */
|
|
42
|
+
export type WorldToneMapping =
|
|
43
|
+
| 'none'
|
|
44
|
+
| 'linear'
|
|
45
|
+
| 'reinhard'
|
|
46
|
+
| 'cineon'
|
|
47
|
+
| 'aces'
|
|
48
|
+
| 'agx'
|
|
49
|
+
| 'neutral';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The output transfer function the frame is written with.
|
|
53
|
+
*
|
|
54
|
+
* - `srgb` — three's own default and this engine's: linear lighting, sRGB encode on output.
|
|
55
|
+
* - `srgb-linear` — NO output transform. This is what a gamma-space renderer needs: the shading
|
|
56
|
+
* result is already in display space and encoding it a second time washes the frame out.
|
|
57
|
+
*/
|
|
58
|
+
export type WorldOutputColorSpace = 'srgb' | 'srgb-linear';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The shadow-map filter, named as data rather than as three's numeric enum.
|
|
62
|
+
*
|
|
63
|
+
* This is a renderer INSTANCE property (`WebGLRenderer.shadowMap.type`), not a context attribute,
|
|
64
|
+
* so unlike MSAA it has an honest moment at which a world can ask for it — which is the whole test
|
|
65
|
+
* this file's header states. A source engine that declares its own shadow filter (Godot 3's
|
|
66
|
+
* `rendering/quality/shadows/filter_mode`) would otherwise inherit whatever the host built with.
|
|
67
|
+
*/
|
|
68
|
+
export type WorldShadowMapType = 'basic' | 'pcf' | 'pcf-soft' | 'vsm';
|
|
69
|
+
|
|
70
|
+
/** What a world may declare about the renderer that draws it. Every field is optional; see header. */
|
|
71
|
+
export interface WorldRendererConfig {
|
|
72
|
+
readonly toneMapping?: WorldToneMapping | undefined;
|
|
73
|
+
readonly toneMappingExposure?: number | undefined;
|
|
74
|
+
readonly outputColorSpace?: WorldOutputColorSpace | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* `WebGLRenderer.shadowMap.type`. Writing it after a shadow map has already been built needs
|
|
77
|
+
* `shadowMap.needsUpdate`, which this function sets — three caches the compiled depth material
|
|
78
|
+
* per type and would otherwise keep filtering with the previous one.
|
|
79
|
+
*/
|
|
80
|
+
readonly shadowMapType?: WorldShadowMapType | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* The colour the frame is cleared to, as a CSS hex string. The renderer's existing clear ALPHA
|
|
83
|
+
* is preserved: a stacked canvas is transparent on purpose (`create-runtime.ts` gives every
|
|
84
|
+
* non-bottom root `alpha: true`), and forcing it opaque here would hide every layer below.
|
|
85
|
+
*/
|
|
86
|
+
readonly clearColor?: string | undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Apply `config` to `renderer`, returning the restore function that puts back what was there.
|
|
91
|
+
*
|
|
92
|
+
* `three` is passed in rather than imported for values so the enum constants come from the HOST's
|
|
93
|
+
* three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
|
|
94
|
+
*/
|
|
95
|
+
export function applyWorldRendererConfig(
|
|
96
|
+
three: typeof THREE,
|
|
97
|
+
renderer: THREE.WebGLRenderer,
|
|
98
|
+
config: WorldRendererConfig,
|
|
99
|
+
): () => void {
|
|
100
|
+
// A host that mounts a world WITHOUT rasterizing it hands the adapter a duck-typed renderer —
|
|
101
|
+
// the editor's design session (`createDesignTimeRenderer`: four members, deliberately never
|
|
102
|
+
// widened) and jsdom test harnesses both do. Such a surface has no colour pipeline to configure:
|
|
103
|
+
// the frame the user sees is drawn by a DIFFERENT renderer (the editor's own), so applying the
|
|
104
|
+
// world's config there is meaningless — and calling `getClearColor` on it is a TypeError that
|
|
105
|
+
// unmounts the whole world at edit time (measured: every Godot port's edit viewport blanked with
|
|
106
|
+
// '"world" failed to mount — renderer.getClearColor is not a function'). Detect the real
|
|
107
|
+
// `WebGLRenderer` surface by the one method this function must call, and no-op otherwise.
|
|
108
|
+
if (typeof renderer.getClearColor !== 'function') {
|
|
109
|
+
return () => {};
|
|
110
|
+
}
|
|
111
|
+
const toneMappings: Record<WorldToneMapping, THREE.ToneMapping> = {
|
|
112
|
+
none: three.NoToneMapping,
|
|
113
|
+
linear: three.LinearToneMapping,
|
|
114
|
+
reinhard: three.ReinhardToneMapping,
|
|
115
|
+
cineon: three.CineonToneMapping,
|
|
116
|
+
aces: three.ACESFilmicToneMapping,
|
|
117
|
+
agx: three.AgXToneMapping,
|
|
118
|
+
neutral: three.NeutralToneMapping,
|
|
119
|
+
};
|
|
120
|
+
const colorSpaces: Record<WorldOutputColorSpace, THREE.ColorSpace> = {
|
|
121
|
+
srgb: three.SRGBColorSpace,
|
|
122
|
+
'srgb-linear': three.LinearSRGBColorSpace,
|
|
123
|
+
};
|
|
124
|
+
const shadowMapTypes: Record<WorldShadowMapType, THREE.ShadowMapType> = {
|
|
125
|
+
basic: three.BasicShadowMap,
|
|
126
|
+
pcf: three.PCFShadowMap,
|
|
127
|
+
'pcf-soft': three.PCFSoftShadowMap,
|
|
128
|
+
vsm: three.VSMShadowMap,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const restores: (() => void)[] = [];
|
|
132
|
+
|
|
133
|
+
if (config.toneMapping !== undefined) {
|
|
134
|
+
const previous = renderer.toneMapping;
|
|
135
|
+
renderer.toneMapping = toneMappings[config.toneMapping];
|
|
136
|
+
restores.push(() => {
|
|
137
|
+
renderer.toneMapping = previous;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (config.toneMappingExposure !== undefined) {
|
|
141
|
+
const previous = renderer.toneMappingExposure;
|
|
142
|
+
renderer.toneMappingExposure = config.toneMappingExposure;
|
|
143
|
+
restores.push(() => {
|
|
144
|
+
renderer.toneMappingExposure = previous;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (config.outputColorSpace !== undefined) {
|
|
148
|
+
const previous = renderer.outputColorSpace;
|
|
149
|
+
renderer.outputColorSpace = colorSpaces[config.outputColorSpace];
|
|
150
|
+
restores.push(() => {
|
|
151
|
+
renderer.outputColorSpace = previous;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (config.shadowMapType !== undefined && renderer.shadowMap !== undefined) {
|
|
155
|
+
const previous = renderer.shadowMap.type;
|
|
156
|
+
renderer.shadowMap.type = shadowMapTypes[config.shadowMapType];
|
|
157
|
+
renderer.shadowMap.needsUpdate = true;
|
|
158
|
+
restores.push(() => {
|
|
159
|
+
renderer.shadowMap.type = previous;
|
|
160
|
+
renderer.shadowMap.needsUpdate = true;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (config.clearColor !== undefined) {
|
|
164
|
+
const previousColor = new three.Color();
|
|
165
|
+
renderer.getClearColor(previousColor);
|
|
166
|
+
// Alpha is READ BACK and re-passed, never assumed: see `clearColor`'s doc above.
|
|
167
|
+
const alpha = renderer.getClearAlpha();
|
|
168
|
+
renderer.setClearColor(new three.Color(config.clearColor), alpha);
|
|
169
|
+
restores.push(() => {
|
|
170
|
+
renderer.setClearColor(previousColor, alpha);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return () => {
|
|
175
|
+
// Reverse order, so a field written twice (it cannot be, today) unwinds correctly.
|
|
176
|
+
for (let i = restores.length - 1; i >= 0; i--) restores[i]?.();
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Container } from 'pixi.js';
|
|
11
11
|
import type * as THREE from 'three';
|
|
12
|
-
import type { WorldRendererConfig } from '../world3d-react/renderer-config';
|
|
13
12
|
import type { AdapterSurface } from './adapter-surface';
|
|
14
13
|
import type { AuthoringAdapter } from './authoring';
|
|
15
14
|
import type { HostContextFor } from './host-context';
|
|
15
|
+
import type { WorldRendererConfig } from './renderer-config';
|
|
16
16
|
import type { SystemAdapters } from './system-adapter';
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -72,6 +72,10 @@ export interface MountedRootBase {
|
|
|
72
72
|
|
|
73
73
|
resize?(width: number, height: number): void;
|
|
74
74
|
dispose(): void;
|
|
75
|
+
/** Resolves after an asynchronous native reconciler has run every component
|
|
76
|
+
* cleanup started by {@link dispose}. Omitted by synchronously-disposing
|
|
77
|
+
* roots. Hosts use this before auditing/reclaiming realm-owned resources. */
|
|
78
|
+
readonly disposeComplete?: Promise<void>;
|
|
75
79
|
|
|
76
80
|
/** Optional capability providers — absence = "not supported", host degrades. */
|
|
77
81
|
readonly authoring?: AuthoringAdapter;
|
|
@@ -94,7 +98,7 @@ export interface MountedThreeRoot extends MountedRootBase {
|
|
|
94
98
|
/**
|
|
95
99
|
* The colour pipeline this world was authored for, REPORTED rather than
|
|
96
100
|
* applied — the adapter has already applied it to the renderer its own host
|
|
97
|
-
* handed it (
|
|
101
|
+
* handed it (`./renderer-config.ts`).
|
|
98
102
|
*
|
|
99
103
|
* It is here because the host that MOUNTS a world is not always the host
|
|
100
104
|
* that DRAWS it. The editor's design session mounts against a
|
|
@@ -24,6 +24,7 @@ export const MOUNTED_ROOT_BASE_SHAPE = defineSeamShape<MountedRootBase>()({
|
|
|
24
24
|
step: { optional: true, kind: 'function', required: 'effect' },
|
|
25
25
|
resize: { optional: true, kind: 'function', required: 'effect' },
|
|
26
26
|
dispose: { optional: false, kind: 'function', required: 'effect' },
|
|
27
|
+
disposeComplete: { optional: true, kind: 'value', required: 'effect' },
|
|
27
28
|
authoring: { optional: true, kind: 'value', required: 'operation' },
|
|
28
29
|
systems: { optional: true, kind: 'value', required: 'operation' },
|
|
29
30
|
observe: { optional: true, kind: 'value', required: 'effect' },
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
type DebugRegistry,
|
|
65
65
|
getDebugRegistry,
|
|
66
66
|
} from '../runtime/debug-registry';
|
|
67
|
-
import {
|
|
67
|
+
import { devBuildEnabled } from '../runtime/dev-build';
|
|
68
68
|
import { disposeDebrisSubtree, type RootFrameHooks } from '../runtime/game';
|
|
69
69
|
import type { EditorPreview, GameCleanup, GameContext, GameSetupFn } from '../runtime/types';
|
|
70
70
|
import { type AudioContext as GameAudio, setupAudio } from '../setup/setup-audio';
|
|
@@ -591,8 +591,8 @@ export class SetupThreeRootAdapter implements RootAdapter {
|
|
|
591
591
|
// registry, with no capability to install and nothing for a game to write.
|
|
592
592
|
//
|
|
593
593
|
// THE GATE, in one place. Three conditions, and the reason for each:
|
|
594
|
-
// 1. `
|
|
595
|
-
// context" (`runtime/dev-
|
|
594
|
+
// 1. `devBuildEnabled()` — the ONE owner of "is this a dev/editor
|
|
595
|
+
// context" (`runtime/dev-build.ts`). A ship build registers nothing,
|
|
596
596
|
// subscribes to nothing, and never enables the profiler.
|
|
597
597
|
// 2. a `Game` shell exists — the readings are folded out of that game's
|
|
598
598
|
// profiler frames, and a bare mount has no profiler to fold.
|
|
@@ -620,12 +620,12 @@ export class SetupThreeRootAdapter implements RootAdapter {
|
|
|
620
620
|
// The packaged runtime bakes `import.meta.env.DEV = false` into its
|
|
621
621
|
// prebuilt bundle, which is a fact about how the EDITOR was built, not
|
|
622
622
|
// about whether this mount is a dev session — found live: a
|
|
623
|
-
// package-native project's vitals never seeded while its
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
if (!host.game || headless || !
|
|
623
|
+
// package-native project's vitals never seeded while its capabilities
|
|
624
|
+
// and every other project-graph module ran fine. The override argument
|
|
625
|
+
// is `devBuildEnabled`'s own documented seam for exactly this
|
|
626
|
+
// caller-knows-better case; a standalone/ship mount (no editorPreview)
|
|
627
|
+
// still resolves purely from the build env.
|
|
628
|
+
if (!host.game || headless || !devBuildEnabled(editorPreview ? true : undefined)) return;
|
|
629
629
|
renderVitals?.dispose();
|
|
630
630
|
host.game.profiler.enabled = true;
|
|
631
631
|
renderVitals = registerRenderVitals({
|