@vgai/engine 0.5.21 → 0.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +2 -4
- package/dist/adapter/adapter-surface.d.ts +1 -1
- package/dist/adapter/adapter-surface.js +1 -1
- package/dist/adapter/authoring.d.ts +19 -21
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -2
- package/dist/adapter/binding.d.ts +341 -0
- package/dist/adapter/binding.d.ts.map +1 -0
- package/dist/adapter/binding.js +148 -0
- package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -1
- package/dist/adapter/colyseus-networking-adapter.js +23 -4
- package/dist/adapter/entrypoint-selection-readers.d.ts +24 -0
- package/dist/adapter/entrypoint-selection-readers.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-readers.js +63 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -1
- package/dist/adapter/entrypoint-selection-source.js +1 -44
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +1 -52
- package/dist/adapter/host-context.d.ts +46 -15
- package/dist/adapter/host-context.d.ts.map +1 -1
- package/dist/adapter/host-context.js +2 -9
- package/dist/adapter/index.d.ts +11 -12
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +9 -10
- package/dist/adapter/ingest/game-contract.d.ts +0 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +0 -3
- package/dist/adapter/ingest/scene-capture.d.ts +19 -23
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +143 -17
- package/dist/adapter/ingest/visible-capture-window.d.ts +34 -0
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/manifest-interpreter.d.ts +59 -0
- package/dist/adapter/manifest-interpreter.d.ts.map +1 -0
- package/dist/adapter/manifest-interpreter.js +50 -0
- package/dist/adapter/native-debug-module.d.ts +71 -3
- package/dist/adapter/native-debug-module.d.ts.map +1 -1
- package/dist/adapter/native-debug-module.js +146 -10
- package/dist/adapter/rapier-physics-adapter.d.ts +31 -1
- package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -1
- package/dist/adapter/rapier-physics-adapter.js +50 -36
- package/dist/adapter/renderer-config.d.ts +3 -3
- package/dist/adapter/renderer-config.js +3 -3
- package/dist/adapter/root-adapter.d.ts +7 -7
- package/dist/adapter/root-adapter.js +4 -4
- package/dist/adapter/system-adapter.d.ts +45 -7
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +3 -3
- package/dist/adapter/system-seam-contract.js +3 -3
- package/dist/adapter/system-slot.d.ts +168 -0
- package/dist/adapter/system-slot.d.ts.map +1 -0
- package/dist/adapter/system-slot.js +229 -0
- package/dist/adapter/transform.d.ts +11 -1
- package/dist/adapter/transform.d.ts.map +1 -1
- package/dist/ai/navigation.d.ts +17 -1
- package/dist/ai/navigation.d.ts.map +1 -1
- package/dist/ai/navigation.js +26 -5
- package/dist/animation/anim-graph-types.d.ts +11 -3
- package/dist/animation/anim-graph-types.d.ts.map +1 -1
- package/dist/animation/theatre-clock-binding.d.ts +4 -4
- package/dist/animation/theatre-clock-binding.js +4 -4
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +2 -2
- package/dist/animation/xstate-animation-meta.d.ts +13 -0
- package/dist/animation/xstate-animation-meta.d.ts.map +1 -1
- package/dist/animation/xstate-animation-meta.js +14 -0
- package/dist/asset-formats/index.d.ts +4 -7
- package/dist/asset-formats/index.d.ts.map +1 -1
- package/dist/asset-formats/index.js +4 -7
- package/dist/asset-formats/render-env.d.ts +5 -8
- package/dist/asset-formats/render-env.d.ts.map +1 -1
- package/dist/asset-formats/render-env.js +3 -6
- package/dist/asset-parse-error.d.ts +2 -5
- package/dist/asset-parse-error.d.ts.map +1 -1
- package/dist/asset-parse-error.js +2 -5
- package/dist/audio/bus-mixer.d.ts +101 -0
- package/dist/audio/bus-mixer.d.ts.map +1 -0
- package/dist/audio/bus-mixer.js +115 -0
- package/dist/canvas-react/index.d.ts +5 -12
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +5 -12
- package/dist/canvas-react/pixi-react-root-factory.d.ts +34 -21
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +274 -36
- package/dist/core/countdown-timer.d.ts +118 -0
- package/dist/core/countdown-timer.d.ts.map +1 -0
- package/dist/core/countdown-timer.js +137 -0
- package/dist/core/deferred-commands.d.ts +127 -0
- package/dist/core/deferred-commands.d.ts.map +1 -0
- package/dist/core/deferred-commands.js +132 -0
- package/dist/core/frame-pacing.d.ts +1 -1
- package/dist/core/frame-pacing.js +2 -2
- package/dist/core/game-loop.d.ts +1 -3
- package/dist/core/game-loop.d.ts.map +1 -1
- package/dist/core/game-loop.js +1 -3
- package/dist/core/seeded-random.d.ts +1 -1
- package/dist/core/sim-clock.d.ts +19 -8
- package/dist/core/sim-clock.d.ts.map +1 -1
- package/dist/core/sim-clock.js +18 -7
- package/dist/core/types.d.ts +1 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/defaults.d.ts +6 -14
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +6 -14
- package/dist/dev/instruments.d.ts +38 -11
- package/dist/dev/instruments.d.ts.map +1 -1
- package/dist/dev/instruments.js +81 -11
- package/dist/dev/register-render-vitals.d.ts +1 -4
- package/dist/dev/register-render-vitals.d.ts.map +1 -1
- package/dist/dev/register-render-vitals.js +1 -4
- package/dist/dev/render-debug-adapter.d.ts +2 -3
- package/dist/dev/render-debug-adapter.d.ts.map +1 -1
- package/dist/dev/render-debug-adapter.js +2 -3
- package/dist/dev/render-vitals.d.ts +3 -3
- package/dist/dev/render-vitals.js +3 -3
- package/dist/dev/webgl-frame-capture.d.ts +1 -1
- package/dist/dev/webgl-frame-capture.js +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/input/host-pointer.d.ts +122 -0
- package/dist/input/host-pointer.d.ts.map +1 -0
- package/dist/input/host-pointer.js +160 -0
- package/dist/input/input-manager.d.ts +33 -5
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +48 -13
- package/dist/input/rebind-controller.d.ts.map +1 -1
- package/dist/input/rebind-controller.js +6 -8
- package/dist/input/schema.d.ts.map +1 -1
- package/dist/input/schema.js +56 -28
- package/dist/manifest/filename.d.ts.map +1 -1
- package/dist/manifest/filename.js +8 -12
- package/dist/manifest/load.d.ts +10 -10
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +9 -6
- package/dist/manifest/schema.d.ts +17 -21
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +45 -35
- package/dist/physics/collider-dimensions.d.ts +8 -12
- package/dist/physics/collider-dimensions.d.ts.map +1 -1
- package/dist/physics/collider-dimensions.js +8 -12
- package/dist/pixi/scene-capture.d.ts +11 -13
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/react/world-state.d.ts +1 -1
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/render/directional-shadow-fit.d.ts +88 -0
- package/dist/render/directional-shadow-fit.d.ts.map +1 -0
- package/dist/render/directional-shadow-fit.js +133 -0
- package/dist/render/environment-capture.d.ts +68 -0
- package/dist/render/environment-capture.d.ts.map +1 -0
- package/dist/render/environment-capture.js +90 -0
- package/dist/render/ibl-override-material.d.ts +97 -0
- package/dist/render/ibl-override-material.d.ts.map +1 -0
- package/dist/render/ibl-override-material.js +114 -0
- package/dist/render/light-camera-factory.d.ts +5 -7
- package/dist/render/light-camera-factory.d.ts.map +1 -1
- package/dist/render/light-camera-factory.js +8 -11
- package/dist/render/material-factory.js +1 -1
- package/dist/render/render-batch-system.d.ts.map +1 -1
- package/dist/render/render-features.d.ts +17 -6
- package/dist/render/render-features.d.ts.map +1 -1
- package/dist/render/render-features.js +0 -28
- package/dist/runtime/create-runtime.d.ts +34 -25
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +218 -93
- package/dist/runtime/debug-registry.d.ts +77 -8
- package/dist/runtime/debug-registry.d.ts.map +1 -1
- package/dist/runtime/debug-registry.js +3 -3
- package/dist/runtime/dev-build.d.ts +2 -2
- package/dist/runtime/dev-build.js +2 -2
- package/dist/runtime/game-input-seams.d.ts +51 -0
- package/dist/runtime/game-input-seams.d.ts.map +1 -0
- package/dist/runtime/game-input-seams.js +86 -0
- package/dist/runtime/game.d.ts +104 -40
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +119 -61
- package/dist/runtime/mount-game.d.ts +0 -16
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +26 -41
- package/dist/runtime/mount-manifest.d.ts +22 -14
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +22 -20
- package/dist/runtime/playtest.d.ts +22 -0
- package/dist/runtime/playtest.d.ts.map +1 -0
- package/dist/runtime/playtest.js +12 -0
- package/dist/runtime/state-bridge.d.ts +2 -3
- package/dist/runtime/state-bridge.d.ts.map +1 -1
- package/dist/runtime/state-bridge.js +1 -0
- package/dist/setup/setup-audio.d.ts +12 -0
- package/dist/setup/setup-audio.d.ts.map +1 -1
- package/dist/setup/setup-audio.js +9 -10
- package/dist/setup/setup-renderer.d.ts +2 -25
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +2 -31
- package/dist/world3d-react/index.d.ts +15 -25
- package/dist/world3d-react/index.d.ts.map +1 -1
- package/dist/world3d-react/index.js +15 -25
- package/dist/world3d-react/r3f-root-factory.d.ts +28 -34
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +403 -57
- package/dist/world3d-react/rapier-physics-bridge.d.ts +30 -17
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +143 -58
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +16 -14
- package/schemas/vgai-project.schema.json +35 -26
- package/src/adapter/adapter-module.ts +2 -4
- package/src/adapter/adapter-surface.ts +1 -1
- package/src/adapter/authoring.ts +19 -21
- package/src/adapter/binding.ts +468 -0
- package/src/adapter/colyseus-networking-adapter.ts +20 -5
- package/src/adapter/entrypoint-selection-readers.ts +66 -0
- package/src/adapter/entrypoint-selection-source.ts +1 -40
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +1 -48
- package/src/adapter/host-context.ts +45 -15
- package/src/adapter/index.ts +23 -11
- package/src/adapter/ingest/game-contract.ts +0 -3
- package/src/adapter/ingest/scene-capture.ts +190 -39
- package/src/adapter/ingest/visible-capture-window.ts +35 -0
- package/src/adapter/manifest-interpreter.ts +64 -0
- package/src/adapter/native-debug-module.ts +248 -15
- package/src/adapter/rapier-physics-adapter.ts +76 -20
- package/src/adapter/renderer-config.ts +3 -3
- package/src/adapter/root-adapter.ts +7 -7
- package/src/adapter/system-adapter.ts +43 -8
- package/src/adapter/system-seam-contract.ts +3 -3
- package/src/adapter/system-slot.ts +291 -0
- package/src/adapter/transform.ts +18 -1
- package/src/ai/navigation.ts +30 -3
- package/src/animation/anim-graph-types.ts +11 -3
- package/src/animation/theatre-clock-binding.ts +4 -4
- package/src/animation/xstate-animation-binding.ts +2 -2
- package/src/animation/xstate-animation-meta.ts +16 -0
- package/src/asset-formats/index.ts +4 -7
- package/src/asset-formats/render-env.ts +3 -6
- package/src/asset-parse-error.ts +2 -5
- package/src/audio/bus-mixer.ts +161 -0
- package/src/canvas-react/index.ts +5 -21
- package/src/canvas-react/pixi-react-root-factory.tsx +301 -39
- package/src/core/countdown-timer.ts +188 -0
- package/src/core/deferred-commands.ts +174 -0
- package/src/core/frame-pacing.ts +2 -2
- package/src/core/game-loop.ts +1 -3
- package/src/core/seeded-random.ts +1 -1
- package/src/core/sim-clock.ts +19 -8
- package/src/core/types.ts +1 -3
- package/src/defaults.ts +6 -14
- package/src/dev/instruments.ts +108 -11
- package/src/dev/register-render-vitals.ts +1 -4
- package/src/dev/render-debug-adapter.ts +2 -3
- package/src/dev/render-vitals.ts +3 -3
- package/src/dev/webgl-frame-capture.ts +1 -1
- package/src/index.ts +2 -5
- package/src/input/host-pointer.ts +230 -0
- package/src/input/input-manager.ts +49 -13
- package/src/input/input-types.ts +1 -1
- package/src/input/rebind-controller.ts +6 -8
- package/src/input/schema.ts +216 -188
- package/src/manifest/filename.ts +8 -12
- package/src/manifest/load.ts +17 -13
- package/src/manifest/schema.ts +55 -47
- package/src/physics/collider-dimensions.ts +8 -12
- package/src/pixi/scene-capture.ts +12 -15
- package/src/react/world-state.tsx +1 -1
- package/src/render/directional-shadow-fit.ts +156 -0
- package/src/render/environment-capture.ts +102 -0
- package/src/render/ibl-override-material.ts +170 -0
- package/src/render/light-camera-factory.ts +8 -11
- package/src/render/material-factory.ts +1 -1
- package/src/render/render-batch-system.ts +14 -6
- package/src/render/render-features.ts +17 -35
- package/src/runtime/create-runtime.ts +270 -108
- package/src/runtime/debug-registry.ts +84 -8
- package/src/runtime/dev-build.ts +2 -2
- package/src/runtime/game-input-seams.ts +108 -0
- package/src/runtime/game.ts +231 -85
- package/src/runtime/mount-game.ts +31 -57
- package/src/runtime/mount-manifest.ts +47 -36
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/state-bridge.ts +3 -3
- package/src/setup/setup-audio.ts +21 -11
- package/src/setup/setup-renderer.ts +2 -61
- package/src/world3d-react/index.ts +15 -31
- package/src/world3d-react/r3f-root-factory.tsx +451 -60
- package/src/world3d-react/rapier-physics-bridge.tsx +154 -58
- package/dist/adapter/setup-three-root-adapter.d.ts +0 -86
- package/dist/adapter/setup-three-root-adapter.d.ts.map +0 -1
- package/dist/adapter/setup-three-root-adapter.js +0 -908
- package/dist/animation/clip-map.d.ts +0 -12
- package/dist/animation/clip-map.d.ts.map +0 -1
- package/dist/animation/clip-map.js +0 -31
- package/dist/asset-registry.d.ts +0 -38
- package/dist/asset-registry.d.ts.map +0 -1
- package/dist/asset-registry.js +0 -66
- package/dist/canvas-react/engine-bridge.d.ts +0 -45
- package/dist/canvas-react/engine-bridge.d.ts.map +0 -1
- package/dist/canvas-react/engine-bridge.js +0 -45
- package/dist/canvas-react/pixi-react-adapter.d.ts +0 -77
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +0 -1
- package/dist/canvas-react/pixi-react-adapter.js +0 -294
- package/dist/canvas-react/world-context.d.ts +0 -98
- package/dist/canvas-react/world-context.d.ts.map +0 -1
- package/dist/canvas-react/world-context.js +0 -173
- package/dist/dev/debug-draw.d.ts +0 -24
- package/dist/dev/debug-draw.d.ts.map +0 -1
- package/dist/dev/debug-draw.js +0 -73
- package/dist/render/auto-batcher.d.ts +0 -34
- package/dist/render/auto-batcher.d.ts.map +0 -1
- package/dist/render/auto-batcher.js +0 -140
- package/dist/render/lod.d.ts +0 -13
- package/dist/render/lod.d.ts.map +0 -1
- package/dist/render/lod.js +0 -16
- package/dist/runtime/types.d.ts +0 -303
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js +0 -1
- package/dist/world3d-react/engine-bridge.d.ts +0 -47
- package/dist/world3d-react/engine-bridge.d.ts.map +0 -1
- package/dist/world3d-react/engine-bridge.js +0 -73
- package/dist/world3d-react/r3f-adapter.d.ts +0 -58
- package/dist/world3d-react/r3f-adapter.d.ts.map +0 -1
- package/dist/world3d-react/r3f-adapter.js +0 -634
- package/dist/world3d-react/world-context.d.ts +0 -182
- package/dist/world3d-react/world-context.d.ts.map +0 -1
- package/dist/world3d-react/world-context.js +0 -235
- package/src/adapter/setup-three-root-adapter.ts +0 -1032
- package/src/animation/clip-map.ts +0 -34
- package/src/asset-registry.ts +0 -89
- package/src/canvas-react/engine-bridge.ts +0 -59
- package/src/canvas-react/pixi-react-adapter.tsx +0 -356
- package/src/canvas-react/world-context.ts +0 -253
- package/src/dev/debug-draw.ts +0 -80
- package/src/render/auto-batcher.ts +0 -168
- package/src/render/lod.ts +0 -17
- package/src/runtime/types.ts +0 -328
- package/src/world3d-react/engine-bridge.ts +0 -86
- package/src/world3d-react/r3f-adapter.tsx +0 -717
- package/src/world3d-react/world-context.ts +0 -358
package/src/manifest/schema.ts
CHANGED
|
@@ -7,14 +7,9 @@
|
|
|
7
7
|
// has a `.describe()` (repo policy — powers `scripts/generate-schema.ts` and
|
|
8
8
|
// the T4.1 schema-walk coverage test).
|
|
9
9
|
//
|
|
10
|
-
// Naming:
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// `Schema` suffix; that sketch is explicitly a
|
|
14
|
-
// "Zod sketch" the doc tells implementers to adapt to real code style
|
|
15
|
-
// ("mirror the style in scene/schema/scene-file.ts" per CLAUDE.md), so the
|
|
16
|
-
// schema consts here are suffixed and the bare names are reserved for the
|
|
17
|
-
// inferred types, matching every other schema file in the repo.
|
|
10
|
+
// Naming: `XxxSchema` is the Zod schema, `Xxx` the inferred type. Every schema
|
|
11
|
+
// file in this repo reads that way, so the bare names stay reserved for the
|
|
12
|
+
// types.
|
|
18
13
|
//
|
|
19
14
|
// Adapter surface values mirror `AdapterSurface` in
|
|
20
15
|
// `packages/engine/src/runtime/game.ts` (`'three' | 'canvas' | 'dom'`)
|
|
@@ -56,7 +51,7 @@ export const GAME_MANIFEST_VERSION = 2 as const;
|
|
|
56
51
|
// which is exactly why react is a `dom` root rather than its own kind.
|
|
57
52
|
//
|
|
58
53
|
// The pre-rename spellings `threejs`/`pixijs`/`react` are REMOVED, not
|
|
59
|
-
// normalized (
|
|
54
|
+
// normalized (the legacy-removal doctrine at
|
|
60
55
|
// ARCHITECTURE-CORE §Vocabulary). Normalization was a second way to say the
|
|
61
56
|
// same thing: two spellings both parsed, so both kept being written, and
|
|
62
57
|
// SECOND readers grew (the dev server's surface probe, the hosted-editor
|
|
@@ -83,8 +78,8 @@ function removedSpellingMessage(bad: string, where: 'adapter' | 'adapter.surface
|
|
|
83
78
|
const canonical =
|
|
84
79
|
where === 'adapter' ? REMOVED_BARE_ADAPTER_SPELLING[bad] : REMOVED_SURFACE_SPELLING[bad];
|
|
85
80
|
return (
|
|
86
|
-
`Game manifest: \`${where}\`: "${bad}" — that spelling was REMOVED
|
|
87
|
-
'ARCHITECTURE-CORE §Vocabulary). The surface vocabulary is ' +
|
|
81
|
+
`Game manifest: \`${where}\`: "${bad}" — that spelling was REMOVED ` +
|
|
82
|
+
'(ARCHITECTURE-CORE §Vocabulary). The surface vocabulary is ' +
|
|
88
83
|
"'three' | 'canvas' | 'dom' — what the HOST HANDS the root, never which " +
|
|
89
84
|
`library it uses.\nFix: change \`${where}\` to "${canonical}".`
|
|
90
85
|
);
|
|
@@ -219,34 +214,6 @@ export const RootAdapterSchema = z
|
|
|
219
214
|
'FAILS by name. A game that needs a long boot (streamed world assets, a menu ' +
|
|
220
215
|
'the player must clear) raises it; the default is 10s.',
|
|
221
216
|
),
|
|
222
|
-
world: z
|
|
223
|
-
.object({
|
|
224
|
-
entry: z
|
|
225
|
-
.string()
|
|
226
|
-
.describe(
|
|
227
|
-
"Project-relative path to the game's OWN module exporting that component",
|
|
228
|
-
),
|
|
229
|
-
export: z
|
|
230
|
-
.string()
|
|
231
|
-
.optional()
|
|
232
|
-
.describe(
|
|
233
|
-
'Named export to read from that module; omit when the module default-exports it',
|
|
234
|
-
),
|
|
235
|
-
})
|
|
236
|
-
.strict()
|
|
237
|
-
.optional()
|
|
238
|
-
.describe(
|
|
239
|
-
"The component this root's WORLD is, in the game's own source — a three root " +
|
|
240
|
-
'whose scene is a React/R3F component tree names it here. Two readers, and ' +
|
|
241
|
-
'they mount different things: the editor mounts THIS component alone as the ' +
|
|
242
|
-
"root's design-time document (a still scene — its frameloop runs ONE bounded " +
|
|
243
|
-
'settle at mount, so physics-owned bodies rest at their authored spawn, and ' +
|
|
244
|
-
'is then frozen forever; nothing moves in Edit), while `entry` ' +
|
|
245
|
-
'above stays what PLAY mounts (the whole game, its own composition, its own ' +
|
|
246
|
-
'HUD and loop). Declaring it is what lets a scene be a scene in Edit rather ' +
|
|
247
|
-
"than the running game's mount surfaces. A game whose world is not a " +
|
|
248
|
-
'component omits it and keeps mounting through `entry` in both modes.',
|
|
249
|
-
),
|
|
250
217
|
})
|
|
251
218
|
.strict()
|
|
252
219
|
.describe(
|
|
@@ -306,6 +273,37 @@ export const AdapterRootSchema = z
|
|
|
306
273
|
"the HUD is styled on the UI board's story cards and on the game surface without the " +
|
|
307
274
|
'editor seeing a single one of those rules. The files themselves are never modified.',
|
|
308
275
|
),
|
|
276
|
+
world: z
|
|
277
|
+
.object({
|
|
278
|
+
entry: z
|
|
279
|
+
.string()
|
|
280
|
+
.describe("Project-relative path to the game's OWN module exporting that component"),
|
|
281
|
+
export: z
|
|
282
|
+
.string()
|
|
283
|
+
.optional()
|
|
284
|
+
.describe(
|
|
285
|
+
'Named export to read from that module; omit when the module default-exports it',
|
|
286
|
+
),
|
|
287
|
+
})
|
|
288
|
+
.strict()
|
|
289
|
+
.optional()
|
|
290
|
+
.describe(
|
|
291
|
+
"The component this root's WORLD is, in the game's own source. Two readers, and they " +
|
|
292
|
+
"mount different things: the EDITOR mounts THIS component alone as the root's " +
|
|
293
|
+
'design-time document, while `entry` above stays what PLAY mounts (the whole game — ' +
|
|
294
|
+
'its own composition, its own HUD, its own loop). It sits on the ROOT rather than ' +
|
|
295
|
+
'inside any one adapter because the split it names is not about ingest: a game whose ' +
|
|
296
|
+
'runtime entry is a SHELL over the thing an author actually edits has this shape ' +
|
|
297
|
+
'whether the source is vendored or first-party. Two shipped cases, one field: an ' +
|
|
298
|
+
'ingested game whose `entry` mounts game+HUD+loop while the named export is the world ' +
|
|
299
|
+
'alone; and a translated Unity/Godot port, whose `entry` is a scene HOST that mounts ' +
|
|
300
|
+
'the scene the built player starts on (`EditorBuildSettings` index 0 — routinely a ' +
|
|
301
|
+
'menu with no 3D content at all) while the named export opens the scene an author ' +
|
|
302
|
+
"edits. That is Unity's own split: build settings say where the PLAYER starts; the " +
|
|
303
|
+
'Scene view opens a scene, and Unity keeps that fact somewhere else entirely. A game ' +
|
|
304
|
+
'whose world is not a separate component omits this and keeps mounting through ' +
|
|
305
|
+
'`entry` in both modes.',
|
|
306
|
+
),
|
|
309
307
|
zOrder: z
|
|
310
308
|
.number()
|
|
311
309
|
.int()
|
|
@@ -424,6 +422,7 @@ export const GameManifestSchema = z
|
|
|
424
422
|
'identity pin, not a range',
|
|
425
423
|
),
|
|
426
424
|
})
|
|
425
|
+
.strict()
|
|
427
426
|
.describe('Engine version pin'),
|
|
428
427
|
roots: z
|
|
429
428
|
.array(AdapterRootSchema)
|
|
@@ -498,21 +497,25 @@ export const GameManifestSchema = z
|
|
|
498
497
|
.describe('Optional editor labels keyed by adapter-root id'),
|
|
499
498
|
groups: z
|
|
500
499
|
.array(
|
|
501
|
-
z
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
500
|
+
z
|
|
501
|
+
.object({
|
|
502
|
+
id: z.string().describe('Unique editor-only group id'),
|
|
503
|
+
label: z.string().describe('Editor-only group label'),
|
|
504
|
+
roots: z
|
|
505
|
+
.array(z.string())
|
|
506
|
+
.min(1)
|
|
507
|
+
.describe('Adapter-root ids shown under this editor-only group'),
|
|
508
|
+
})
|
|
509
|
+
.strict(),
|
|
509
510
|
)
|
|
510
511
|
.optional()
|
|
511
512
|
.describe('Optional editor-only hierarchy groups'),
|
|
512
513
|
})
|
|
514
|
+
.strict()
|
|
513
515
|
.optional()
|
|
514
516
|
.describe('Editor projection for native adapter roots'),
|
|
515
517
|
})
|
|
518
|
+
.strict()
|
|
516
519
|
.optional()
|
|
517
520
|
.describe('Editor-only authoring metadata; never runtime ownership'),
|
|
518
521
|
server: z
|
|
@@ -558,6 +561,7 @@ export const GameManifestSchema = z
|
|
|
558
561
|
'Deploy-baked managed-relay credentials. NEVER hand-authored in source — written only into the staged manifest by `vgai deploy` and fetched at runtime by the standalone client',
|
|
559
562
|
),
|
|
560
563
|
})
|
|
564
|
+
.strict()
|
|
561
565
|
.optional()
|
|
562
566
|
.describe('Multiplayer server config (Colyseus), if this game registers a room'),
|
|
563
567
|
resolution: z
|
|
@@ -565,6 +569,7 @@ export const GameManifestSchema = z
|
|
|
565
569
|
width: z.number().describe('Canvas width in pixels'),
|
|
566
570
|
height: z.number().describe('Canvas height in pixels'),
|
|
567
571
|
})
|
|
572
|
+
.strict()
|
|
568
573
|
.optional()
|
|
569
574
|
.describe('Canvas resolution'),
|
|
570
575
|
rendering: z
|
|
@@ -599,6 +604,7 @@ export const GameManifestSchema = z
|
|
|
599
604
|
'runtime reader is the debug-bridge installer (D18).',
|
|
600
605
|
),
|
|
601
606
|
})
|
|
607
|
+
.strict()
|
|
602
608
|
.optional()
|
|
603
609
|
.describe(
|
|
604
610
|
'Debug-bridge production gating (D18) — governs whether ?vgai-debug=1 installs ' +
|
|
@@ -631,6 +637,7 @@ export const GameManifestSchema = z
|
|
|
631
637
|
"omit to fall back to the runtime's own fixed default seed.",
|
|
632
638
|
),
|
|
633
639
|
})
|
|
640
|
+
.strict()
|
|
634
641
|
.optional()
|
|
635
642
|
.describe(
|
|
636
643
|
'Seeded-RNG determinism contract (D15) — governs ' +
|
|
@@ -689,6 +696,7 @@ export const GameManifestSchema = z
|
|
|
689
696
|
'deterministic under a seed.',
|
|
690
697
|
),
|
|
691
698
|
})
|
|
699
|
+
.strict()
|
|
692
700
|
.refine((budget) => Object.values(budget).some((value) => value !== undefined), {
|
|
693
701
|
message:
|
|
694
702
|
'Game manifest: `budget` must set at least one metric (frameCpuMsP95, physicsMsP95, ' +
|
|
@@ -5,17 +5,13 @@
|
|
|
5
5
|
* shape is defined in absolute world units at creation time. Three.js meshes,
|
|
6
6
|
* by contrast, scale visually for free via the normal Object3D hierarchy. To
|
|
7
7
|
* keep the physical collider matching what's on screen, the LOADER must bake
|
|
8
|
-
* the entity's composed WORLD scale (self × every transformed ancestor
|
|
9
|
-
*
|
|
10
|
-
* creation time.
|
|
8
|
+
* the entity's composed WORLD scale (self × every transformed ancestor) into
|
|
9
|
+
* the collider's dimensions at creation time.
|
|
11
10
|
*
|
|
12
|
-
* This is the ONE place that does that math, so
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Three.js parenting) while the Rapier body ignored scale entirely, so the
|
|
17
|
-
* two silently agreed by coincidence for uniform scale and disagreed for
|
|
18
|
-
* everything else.
|
|
11
|
+
* This is the ONE place that does that math, so every caller — the physics
|
|
12
|
+
* body creation path and the editor's collider gizmo alike — agrees on
|
|
13
|
+
* exactly the same numbers. Two copies silently agree by coincidence for
|
|
14
|
+
* uniform scale and disagree for everything else.
|
|
19
15
|
*
|
|
20
16
|
* Round colliders (ball/capsule) can only represent a uniform radius — Rapier
|
|
21
17
|
* has no ellipsoid/elliptical-capsule shape. A non-uniform world scale on a
|
|
@@ -40,8 +36,8 @@ export interface ColliderWorldDimensions {
|
|
|
40
36
|
|
|
41
37
|
/**
|
|
42
38
|
* Compute a collider's WORLD-space dimensions given the entity's composed
|
|
43
|
-
* world scale (self × ancestors, per-axis, in the entity's own local frame
|
|
44
|
-
* X/Y/Z order
|
|
39
|
+
* world scale (self × ancestors, per-axis, in the entity's own local frame,
|
|
40
|
+
* in X/Y/Z order).
|
|
45
41
|
*
|
|
46
42
|
* @param collider - the entity's authored (unscaled) collider definition.
|
|
47
43
|
* @param worldScale - composed world scale `[x, y, z]`.
|
|
@@ -19,25 +19,22 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
+
type CaptureWaitOptions,
|
|
22
23
|
documentVisibilityClock,
|
|
23
24
|
startVisibleCaptureWindow,
|
|
24
|
-
type VisibilityClock,
|
|
25
|
-
type VisibleCaptureWindow,
|
|
26
25
|
} from '../adapter/ingest/visible-capture-window';
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
* Options for {@link SceneCapture2DHandle.waitForCapture} — the
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Options for {@link SceneCapture2DHandle.waitForCapture} — the WAIT's options,
|
|
29
|
+
* not this lane's, so they carry no `2D` suffix and are not declared here.
|
|
30
|
+
* They configure `visible-capture-window.ts`'s window, which both surfaces
|
|
31
|
+
* share; this lane used to restate them field-for-field under a `2D` name.
|
|
32
|
+
* The suffix marks what a surface genuinely owns
|
|
33
|
+
* ({@link CapturedRuntime2D} is a Pixi stage/renderer/app, and stays), and a
|
|
34
|
+
* budget of visible milliseconds is not that. Passing a bare number is
|
|
35
|
+
* `{ timeoutMs }`, which is what every existing caller does.
|
|
32
36
|
*/
|
|
33
|
-
export
|
|
34
|
-
/** The capture window, in VISIBLE milliseconds (default 10s). */
|
|
35
|
-
timeoutMs?: number | undefined;
|
|
36
|
-
/** Injected in tests; defaults to the document's own visibility. */
|
|
37
|
-
visibility?: VisibilityClock | undefined;
|
|
38
|
-
/** Live view of the wait while it runs, `null` the moment it ends. */
|
|
39
|
-
onWait?: ((wait: VisibleCaptureWindow | null) => void) | undefined;
|
|
40
|
-
}
|
|
37
|
+
export type { CaptureWaitOptions };
|
|
41
38
|
|
|
42
39
|
/** A live Pixi runtime captured from an external PixiJS game. */
|
|
43
40
|
export interface CapturedRuntime2D {
|
|
@@ -68,7 +65,7 @@ export interface SceneCapture2DHandle {
|
|
|
68
65
|
* frame the tab draws after it comes forward is trapped exactly as it would
|
|
69
66
|
* have been at boot. Rejects only when the window is spent while VISIBLE.
|
|
70
67
|
*/
|
|
71
|
-
waitForCapture(options?: number |
|
|
68
|
+
waitForCapture(options?: number | CaptureWaitOptions): Promise<CapturedRuntime2D>;
|
|
72
69
|
getDrawCount(): number;
|
|
73
70
|
/** Pause/resume the captured game's own ticker (loop-host gating). */
|
|
74
71
|
setPaused(paused: boolean): void;
|
|
@@ -138,7 +135,7 @@ export function installSceneCapture2D(
|
|
|
138
135
|
else app.ticker.start();
|
|
139
136
|
},
|
|
140
137
|
waitForCapture(options) {
|
|
141
|
-
const opts:
|
|
138
|
+
const opts: CaptureWaitOptions =
|
|
142
139
|
typeof options === 'number' ? { timeoutMs: options } : (options ?? {});
|
|
143
140
|
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
144
141
|
if (captured) return Promise.resolve(captured);
|
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
} from 'react';
|
|
28
28
|
import type { z } from 'zod';
|
|
29
29
|
import type { RootStateObserver } from '../adapter';
|
|
30
|
+
import type { DebugCommandArgs } from '../runtime/debug-registry';
|
|
30
31
|
import { getDebugRegistry } from '../runtime/debug-registry';
|
|
31
32
|
import { createFrameSelectorCache, type Equals, shallow } from '../runtime/frame-selector-cache';
|
|
32
33
|
import type { Game } from '../runtime/game';
|
|
33
|
-
import type { DebugCommandArgs } from '../runtime/types';
|
|
34
34
|
|
|
35
35
|
/** Re-exported for convenience — the opt-in equality for selectors that
|
|
36
36
|
* return a fresh object/array/tuple every call (default is `Object.is`). */
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fit a directional light's orthographic shadow box to the camera, for one
|
|
3
|
+
* frame — the geometry three does not supply.
|
|
4
|
+
*
|
|
5
|
+
* `DirectionalLightShadow` is an ordinary `OrthographicCamera` parented to the
|
|
6
|
+
* light, and it sits wherever the light's authored transform puts it, forever.
|
|
7
|
+
* That is fine for a scene the authored box contains and wrong for every game
|
|
8
|
+
* whose camera travels: the moment the player walks past the box, the casters
|
|
9
|
+
* out there stop having shadows, and no bias, map size or intensity fixes it
|
|
10
|
+
* because the geometry is simply outside the frustum. Three's own addon CSM is
|
|
11
|
+
* the multi-cascade answer to a different question and supplies neither of the
|
|
12
|
+
* two pieces below.
|
|
13
|
+
*
|
|
14
|
+
* ## The box is the frustum slice's BOUNDING SPHERE
|
|
15
|
+
*
|
|
16
|
+
* The standard fit, and the reason it cannot be authored as a constant: take
|
|
17
|
+
* the eight corners of the camera's frustum between its near plane and
|
|
18
|
+
* `maxDistance`, put the box centre at their centroid, and size the box to the
|
|
19
|
+
* largest distance from that centroid to a corner. That radius is decided by
|
|
20
|
+
* the camera's FIELD OF VIEW and aspect as much as by `maxDistance` — at a
|
|
21
|
+
* 70-degree vertical fov over 1024x600, a 40-unit slice has half-extents of
|
|
22
|
+
* 47.8 x 28.0 at its far plane and the sphere around it has radius ~58.9,
|
|
23
|
+
* nearly three times `maxDistance / 2`. A bounding SPHERE (rather than the
|
|
24
|
+
* tight box) is what makes the radius independent of camera yaw, so turning in
|
|
25
|
+
* place cannot resize the shadow.
|
|
26
|
+
*
|
|
27
|
+
* The corners are read through the camera's own `projectionMatrixInverse`, so
|
|
28
|
+
* a perspective and an orthographic camera are exact with no branch on type —
|
|
29
|
+
* and an asymmetric frustum (`setViewOffset`) is fitted over its real corners
|
|
30
|
+
* rather than an assumed centre.
|
|
31
|
+
*
|
|
32
|
+
* ## Texel snapping is not a polish detail
|
|
33
|
+
*
|
|
34
|
+
* A shadow box that follows a camera continuously re-rasterizes the same
|
|
35
|
+
* static geometry at sub-texel offsets, and the depth-test result flips along
|
|
36
|
+
* every shadow edge from frame to frame — the edges CRAWL, which is far more
|
|
37
|
+
* distracting than the missing shadows the fit is for. Quantizing the centre
|
|
38
|
+
* to whole shadow texels, in the LIGHT's own basis (the shadow map's u/v axes
|
|
39
|
+
* are the light's local x/y), makes the rasterized footprint of a static
|
|
40
|
+
* caster identical between frames. Depth (local z) is deliberately NOT snapped:
|
|
41
|
+
* it does not affect which texel a fragment lands in.
|
|
42
|
+
*
|
|
43
|
+
* ## What the caller keeps
|
|
44
|
+
*
|
|
45
|
+
* This is one pure function over three's own objects: it reads a camera and a
|
|
46
|
+
* quaternion and returns three numbers-and-a-vector. It moves nothing, and in
|
|
47
|
+
* particular it does not touch the light — how a fitted centre reaches the
|
|
48
|
+
* shadow camera is the caller's, because three derives the shadow camera's
|
|
49
|
+
* placement FROM the light every frame (`LightShadow.updateMatrices` copies the
|
|
50
|
+
* light's world position and looks at `light.target`), so the thing that has to
|
|
51
|
+
* move is the light NODE and only the caller knows how its light is parented,
|
|
52
|
+
* aimed and torn down. The depth range (`shadow.camera.near`/`far`) is the
|
|
53
|
+
* caller's for the same reason: it is what `shadow.bias` is normalized against.
|
|
54
|
+
*
|
|
55
|
+
* {@link DirectionalShadowFit.texelWorldSize} is published because everything
|
|
56
|
+
* sized in texels moves with the box — a receiver-side `shadow.normalBias`
|
|
57
|
+
* derived from a stale texel is wrong by exactly the factor the box grew, and
|
|
58
|
+
* it surfaces as self-shadow acne on curved casters rather than as anything a
|
|
59
|
+
* reviewer would call a shadow bug.
|
|
60
|
+
*
|
|
61
|
+
* Consumers today: `top-down-strategy`'s camera-fitted sun, and
|
|
62
|
+
* `godot-compat`'s Godot 3 `SHADOW_ORTHOGONAL` fit.
|
|
63
|
+
*/
|
|
64
|
+
import { type Camera, Quaternion, Vector3 } from 'three';
|
|
65
|
+
|
|
66
|
+
/** The box a directional shadow is rendered through for one frame. */
|
|
67
|
+
export interface DirectionalShadowFit {
|
|
68
|
+
/** WORLD-space centre of the box, quantized to whole shadow texels in the light's basis. */
|
|
69
|
+
readonly center: Vector3;
|
|
70
|
+
/** Half-extent on both lateral axes — the frustum slice's bounding-sphere radius. */
|
|
71
|
+
readonly radius: number;
|
|
72
|
+
/** One shadow texel in world units at this radius: `2 * radius / mapSize`. */
|
|
73
|
+
readonly texelWorldSize: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const _inverseLight = new Quaternion();
|
|
77
|
+
const _corner = new Vector3();
|
|
78
|
+
const _centroid = new Vector3();
|
|
79
|
+
const _ndc = new Vector3();
|
|
80
|
+
/** The four lateral NDC corners; the slice's eight are these at two depths. */
|
|
81
|
+
const NDC_CORNERS: readonly (readonly [number, number])[] = [
|
|
82
|
+
[-1, -1],
|
|
83
|
+
[-1, 1],
|
|
84
|
+
[1, -1],
|
|
85
|
+
[1, 1],
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
/** View-space depth `-z` as this camera's NDC z, through the camera's OWN projection — so the
|
|
89
|
+
* slice is exact for a perspective and an orthographic camera alike, with no branch on type. */
|
|
90
|
+
function ndcDepthOf(camera: Camera, viewDepth: number): number {
|
|
91
|
+
return _ndc.set(0, 0, -viewDepth).applyMatrix4(camera.projectionMatrix).z;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The bounding sphere of `camera`'s frustum between its near plane and
|
|
96
|
+
* `maxDistance`, with the centre quantized to whole shadow texels in the
|
|
97
|
+
* LIGHT's basis.
|
|
98
|
+
*
|
|
99
|
+
* `camera` is read through its own `projectionMatrix`/`projectionMatrixInverse`/`matrixWorld`, so a
|
|
100
|
+
* caller must have brought those up to date (three does this for every rendered camera).
|
|
101
|
+
* `maxDistance` is the view depth the shadow covers, measured from the camera and clamped to its
|
|
102
|
+
* far plane — there is nothing to shadow past the geometry the camera draws.
|
|
103
|
+
* `lightOrientation` is the light's WORLD quaternion, whose local x/y are the shadow map's own axes
|
|
104
|
+
* and therefore the axes the snap quantizes along. A `mapSize` of zero or less disables snapping
|
|
105
|
+
* rather than dividing by it.
|
|
106
|
+
*/
|
|
107
|
+
export function fitDirectionalShadowToCamera(
|
|
108
|
+
camera: Camera,
|
|
109
|
+
lightOrientation: Quaternion,
|
|
110
|
+
maxDistance: number,
|
|
111
|
+
mapSize: number,
|
|
112
|
+
): DirectionalShadowFit {
|
|
113
|
+
const frustum = camera as Camera & { near?: number; far?: number };
|
|
114
|
+
const camNear = typeof frustum.near === 'number' ? frustum.near : 0;
|
|
115
|
+
const camFar = typeof frustum.far === 'number' ? frustum.far : maxDistance;
|
|
116
|
+
// The two clamps are degenerate-input guards: far at least a hair past near, near never past far.
|
|
117
|
+
let far = Math.min(maxDistance, camFar);
|
|
118
|
+
far = Math.max(far, camNear + 0.001);
|
|
119
|
+
const near = Math.min(camNear, far);
|
|
120
|
+
const nearNdc = ndcDepthOf(camera, near);
|
|
121
|
+
const farNdc = ndcDepthOf(camera, far);
|
|
122
|
+
|
|
123
|
+
// The centroid of the eight corners. Both planes contribute four symmetric corners, so for an
|
|
124
|
+
// ordinary camera this is `(near + far) / 2` down -Z — but it is computed rather than assumed,
|
|
125
|
+
// because `setViewOffset` makes a frustum asymmetric and the fit is over the real corners.
|
|
126
|
+
_centroid.set(0, 0, 0);
|
|
127
|
+
for (const ndcZ of [nearNdc, farNdc]) {
|
|
128
|
+
for (const [x, y] of NDC_CORNERS) {
|
|
129
|
+
_centroid.add(_corner.set(x, y, ndcZ).applyMatrix4(camera.projectionMatrixInverse));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_centroid.multiplyScalar(1 / 8);
|
|
133
|
+
|
|
134
|
+
let radius = 0;
|
|
135
|
+
for (const ndcZ of [nearNdc, farNdc]) {
|
|
136
|
+
for (const [x, y] of NDC_CORNERS) {
|
|
137
|
+
radius = Math.max(
|
|
138
|
+
radius,
|
|
139
|
+
_corner.set(x, y, ndcZ).applyMatrix4(camera.projectionMatrixInverse).distanceTo(_centroid),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// One texel of margin, so the snapped box never clips its own outermost texel: the snap below
|
|
144
|
+
// moves the centre by up to half a texel on each lateral axis.
|
|
145
|
+
if (mapSize > 2) radius *= mapSize / (mapSize - 2);
|
|
146
|
+
|
|
147
|
+
const center = _centroid.clone().applyMatrix4(camera.matrixWorld);
|
|
148
|
+
const texelWorldSize = mapSize > 0 ? (2 * radius) / mapSize : 0;
|
|
149
|
+
if (texelWorldSize > 0) {
|
|
150
|
+
center.applyQuaternion(_inverseLight.copy(lightOrientation).invert());
|
|
151
|
+
center.x = Math.round(center.x / texelWorldSize) * texelWorldSize;
|
|
152
|
+
center.y = Math.round(center.y / texelWorldSize) * texelWorldSize;
|
|
153
|
+
center.applyQuaternion(lightOrientation);
|
|
154
|
+
}
|
|
155
|
+
return { center, radius, texelWorldSize };
|
|
156
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restricting a scene for the duration of ONE environment capture, and asking
|
|
3
|
+
* a renderer whether it can run one at all.
|
|
4
|
+
*
|
|
5
|
+
* A cube capture renders the live scene into an offscreen target, so whatever
|
|
6
|
+
* it must exclude has to be turned off and turned back on around that single
|
|
7
|
+
* render — never left flipped, and never flipped through a renderer-wide
|
|
8
|
+
* switch (see {@link withCaptureShadows} for the measured reason). Both
|
|
9
|
+
* helpers restore in a `finally`, so a capture that throws does not leave the
|
|
10
|
+
* game altered.
|
|
11
|
+
*
|
|
12
|
+
* Consumers today: the `reflections` capability's native probe system, and
|
|
13
|
+
* `godot-compat`'s Godot 3 `ReflectionProbe`.
|
|
14
|
+
*/
|
|
15
|
+
import type { Light, Object3D, Scene, WebGLRenderer } from 'three';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hide everything a capture's cull mask excludes, for exactly one capture.
|
|
19
|
+
*
|
|
20
|
+
* The object's side of the test is three's OWN `Object3D.layers.mask`, never a
|
|
21
|
+
* parallel `userData` copy. An engine whose own per-object visibility layers
|
|
22
|
+
* are a 32-bit mask (Godot's `VisualInstance.layers` is the worked case) writes
|
|
23
|
+
* the authored mask straight onto the object and this reads it back. Both
|
|
24
|
+
* default an unannotated object to `1`, which no default mask excludes, so an
|
|
25
|
+
* ordinary scene is untouched.
|
|
26
|
+
*/
|
|
27
|
+
export function withCaptureMask(scene: Scene, mask: number, capture: () => void): void {
|
|
28
|
+
const hidden: { object: Object3D; visible: boolean }[] = [];
|
|
29
|
+
scene.traverse((object) => {
|
|
30
|
+
if ((object.layers.mask & mask) !== 0) return;
|
|
31
|
+
hidden.push({ object, visible: object.visible });
|
|
32
|
+
object.visible = false;
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
capture();
|
|
36
|
+
} finally {
|
|
37
|
+
for (const entry of hidden) entry.object.visible = entry.visible;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Suppress every shadow-casting light for the duration of ONE capture — as
|
|
43
|
+
* per-light `LightShadow.intensity`, never as `renderer.shadowMap.enabled`.
|
|
44
|
+
*
|
|
45
|
+
* **The distinction is not stylistic; the global flag silently deletes every
|
|
46
|
+
* shadow in the game.** `renderer.shadowMap.enabled` is a program PARAMETER
|
|
47
|
+
* (`WebGLPrograms.getParameters` → `shadowMapEnabled`), and
|
|
48
|
+
* `WebGLRenderer.setProgram`'s `needsProgramChange` list does NOT watch it —
|
|
49
|
+
* three requires a material version bump for a flip to reach a material that is
|
|
50
|
+
* already compiled. So flipping it around the capture does nothing for compiled
|
|
51
|
+
* materials and everything for the one material compiling INSIDE the window —
|
|
52
|
+
* and a probe system installing a shader override sets `material.needsUpdate =
|
|
53
|
+
* true` in the same `update()` call that runs the capture, so every patched
|
|
54
|
+
* material links with `USE_SHADOWMAP` absent and keeps that program for the
|
|
55
|
+
* rest of the run.
|
|
56
|
+
*
|
|
57
|
+
* Measured on the real Godot 3.6 binary against that lane's emitted port (a
|
|
58
|
+
* white ground, a floating box, one straight-down `DirectionalLight`,
|
|
59
|
+
* PanoramaSky ambient, Filmic white 6): with NO probe the port's shadow pixel
|
|
60
|
+
* is `92,109,181` against Godot's `92,109,181`; adding ONE `ReflectionProbe`
|
|
61
|
+
* moved the port's shadow pixel to `218,222,232` — byte-identical to its own
|
|
62
|
+
* LIT ground, i.e. the shadow was gone — while Godot's moved to `109,146,201`.
|
|
63
|
+
* Asking the capture not to touch shadows at all restored `108,146,200`, which
|
|
64
|
+
* is what named the mechanism. The defect was Godot-shaped; the mechanism is a
|
|
65
|
+
* three.js contract, which is why the fix lives here.
|
|
66
|
+
*
|
|
67
|
+
* `LightShadow.intensity` is a UNIFORM (`shadowmap_pars_fragment`:
|
|
68
|
+
* `mix(1.0, shadow, shadowIntensity)`), refreshed from the light on every
|
|
69
|
+
* `renderer.render()`, so zeroing it for the capture costs no recompile and
|
|
70
|
+
* cannot outlive the `finally` that restores it.
|
|
71
|
+
*/
|
|
72
|
+
export function withCaptureShadows(scene: Scene, enabled: boolean, capture: () => void): void {
|
|
73
|
+
if (enabled) {
|
|
74
|
+
capture();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const restored: { shadow: { intensity: number }; intensity: number }[] = [];
|
|
78
|
+
scene.traverse((object) => {
|
|
79
|
+
const light = object as Light & { shadow?: { intensity: number } };
|
|
80
|
+
if (!light.isLight || light.shadow === undefined) return;
|
|
81
|
+
restored.push({ shadow: light.shadow, intensity: light.shadow.intensity });
|
|
82
|
+
light.shadow.intensity = 0;
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
capture();
|
|
86
|
+
} finally {
|
|
87
|
+
for (const entry of restored) entry.shadow.intensity = entry.intensity;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether this renderer can run an environment capture at all.
|
|
93
|
+
*
|
|
94
|
+
* The editor's still design-time tree and headless runtimes deliberately supply
|
|
95
|
+
* no GPU. They must keep the authored scene and gameplay; only the
|
|
96
|
+
* renderer-owned capture is unavailable, so a probe system asks this and skips
|
|
97
|
+
* the capture rather than refusing to mount.
|
|
98
|
+
*/
|
|
99
|
+
export function canCaptureEnvironment(renderer: WebGLRenderer): boolean {
|
|
100
|
+
const candidate = renderer as WebGLRenderer & { compile?: unknown; coordinateSystem?: unknown };
|
|
101
|
+
return typeof candidate.compile === 'function' && candidate.coordinateSystem !== undefined;
|
|
102
|
+
}
|