@aura3d/engine 2.0.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -11
- package/dist/animation/AnimationAction.d.ts.map +1 -1
- package/dist/animation/AnimationAction.js +20 -2
- package/dist/animation/AnimationAction.js.map +1 -1
- package/dist/animation/AnimationLayer.d.ts +1 -0
- package/dist/animation/AnimationLayer.d.ts.map +1 -1
- package/dist/animation/AnimationLayer.js +24 -3
- package/dist/animation/AnimationLayer.js.map +1 -1
- package/dist/animation/AnimationMixer.d.ts +7 -1
- package/dist/animation/AnimationMixer.d.ts.map +1 -1
- package/dist/animation/AnimationMixer.js +105 -34
- package/dist/animation/AnimationMixer.js.map +1 -1
- package/dist/animation/AnimationTrack.d.ts.map +1 -1
- package/dist/animation/AnimationTrack.js +13 -0
- package/dist/animation/AnimationTrack.js.map +1 -1
- package/dist/animation/FootIk.d.ts +19 -0
- package/dist/animation/FootIk.d.ts.map +1 -1
- package/dist/animation/FootIk.js +113 -7
- package/dist/animation/FootIk.js.map +1 -1
- package/dist/animation/HumanoidRetargeting.d.ts +35 -2
- package/dist/animation/HumanoidRetargeting.d.ts.map +1 -1
- package/dist/animation/HumanoidRetargeting.js +59 -9
- package/dist/animation/HumanoidRetargeting.js.map +1 -1
- package/dist/animation/LocomotionEvidence.d.ts +34 -0
- package/dist/animation/LocomotionEvidence.d.ts.map +1 -0
- package/dist/animation/LocomotionEvidence.js +48 -0
- package/dist/animation/LocomotionEvidence.js.map +1 -0
- package/dist/animation/LocomotionKit.d.ts +6 -0
- package/dist/animation/LocomotionKit.d.ts.map +1 -1
- package/dist/animation/LocomotionKit.js +6 -0
- package/dist/animation/LocomotionKit.js.map +1 -1
- package/dist/animation/RootMotion.d.ts +36 -0
- package/dist/animation/RootMotion.d.ts.map +1 -1
- package/dist/animation/RootMotion.js +58 -0
- package/dist/animation/RootMotion.js.map +1 -1
- package/dist/animation/SpringBones.d.ts +28 -0
- package/dist/animation/SpringBones.d.ts.map +1 -1
- package/dist/animation/SpringBones.js +24 -0
- package/dist/animation/SpringBones.js.map +1 -1
- package/dist/animation/index.d.ts +1 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/animation/index.js +1 -0
- package/dist/animation/index.js.map +1 -1
- package/dist/assets/GLTFAnimationRuntime.d.ts +175 -2
- package/dist/assets/GLTFAnimationRuntime.d.ts.map +1 -1
- package/dist/assets/GLTFAnimationRuntime.js +615 -5
- package/dist/assets/GLTFAnimationRuntime.js.map +1 -1
- package/dist/assets/GLTFCompressionDecoders.d.ts +1 -0
- package/dist/assets/GLTFCompressionDecoders.d.ts.map +1 -1
- package/dist/assets/GLTFCompressionDecoders.js +20 -5
- package/dist/assets/GLTFCompressionDecoders.js.map +1 -1
- package/dist/assets/GLTFExtensionSupport.js +2 -2
- package/dist/assets/GLTFExtensionSupport.js.map +1 -1
- package/dist/assets/GLTFLoader.d.ts +360 -1
- package/dist/assets/GLTFLoader.d.ts.map +1 -1
- package/dist/assets/GLTFLoader.js +218 -8
- package/dist/assets/GLTFLoader.js.map +1 -1
- package/dist/assets/KTX2BasisTextureTranscoder.d.ts +39 -0
- package/dist/assets/KTX2BasisTextureTranscoder.d.ts.map +1 -1
- package/dist/assets/KTX2BasisTextureTranscoder.js +78 -0
- package/dist/assets/KTX2BasisTextureTranscoder.js.map +1 -1
- package/dist/assets/TexturePipeline.d.ts +20 -0
- package/dist/assets/TexturePipeline.d.ts.map +1 -1
- package/dist/assets/TexturePipeline.js +20 -0
- package/dist/assets/TexturePipeline.js.map +1 -1
- package/dist/assets/TextureStreaming.d.ts +30 -0
- package/dist/assets/TextureStreaming.d.ts.map +1 -1
- package/dist/assets/TextureStreaming.js +53 -0
- package/dist/assets/TextureStreaming.js.map +1 -1
- package/dist/assets/browser-index.d.ts +7 -3
- package/dist/assets/browser-index.d.ts.map +1 -1
- package/dist/assets/browser-index.js +3 -1
- package/dist/assets/browser-index.js.map +1 -1
- package/dist/assets/gltf-runtime.d.ts +1 -1
- package/dist/assets/gltf-runtime.d.ts.map +1 -1
- package/dist/assets/gltf-runtime.js.map +1 -1
- package/dist/assets/index.d.ts +7 -7
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +3 -3
- package/dist/assets/index.js.map +1 -1
- package/dist/assets/loaders/HDRLoader.d.ts +40 -0
- package/dist/assets/loaders/HDRLoader.d.ts.map +1 -1
- package/dist/assets/loaders/HDRLoader.js +158 -0
- package/dist/assets/loaders/HDRLoader.js.map +1 -1
- package/dist/audio/AudioSource.d.ts +2 -0
- package/dist/audio/AudioSource.d.ts.map +1 -1
- package/dist/audio/AudioSource.js +7 -0
- package/dist/audio/AudioSource.js.map +1 -1
- package/dist/audio/Footsteps.d.ts +47 -0
- package/dist/audio/Footsteps.d.ts.map +1 -0
- package/dist/audio/Footsteps.js +73 -0
- package/dist/audio/Footsteps.js.map +1 -0
- package/dist/audio/GameMixer.d.ts +69 -0
- package/dist/audio/GameMixer.d.ts.map +1 -0
- package/dist/audio/GameMixer.js +141 -0
- package/dist/audio/GameMixer.js.map +1 -0
- package/dist/audio/PositionalEmitter.d.ts +95 -0
- package/dist/audio/PositionalEmitter.d.ts.map +1 -0
- package/dist/audio/PositionalEmitter.js +199 -0
- package/dist/audio/PositionalEmitter.js.map +1 -0
- package/dist/audio/index.d.ts +6 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +3 -0
- package/dist/audio/index.js.map +1 -1
- package/dist/aura3d-cli/pull-bridge/profiles.d.ts +12 -2
- package/dist/aura3d-cli/pull-bridge/profiles.d.ts.map +1 -1
- package/dist/aura3d-cli/pull-bridge/profiles.js +35 -4
- package/dist/aura3d-cli/pull-bridge/profiles.js.map +1 -1
- package/dist/aura3d-cli/pull-bridge/provenance.d.ts +15 -1
- package/dist/aura3d-cli/pull-bridge/provenance.d.ts.map +1 -1
- package/dist/aura3d-cli/pull-bridge/provenance.js +60 -1
- package/dist/aura3d-cli/pull-bridge/provenance.js.map +1 -1
- package/dist/aura3d-cli/pull-bridge/scoring.d.ts +10 -1
- package/dist/aura3d-cli/pull-bridge/scoring.d.ts.map +1 -1
- package/dist/aura3d-cli/pull-bridge/scoring.js +9 -1
- package/dist/aura3d-cli/pull-bridge/scoring.js.map +1 -1
- package/dist/aura3d-cli/pull-bridge/search.js +2 -2
- package/dist/aura3d-cli/pull-bridge/search.js.map +1 -1
- package/dist/aura3d-cli/pull-bridge.d.ts +15 -3
- package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -1
- package/dist/aura3d-cli/pull-bridge.js +68 -5
- package/dist/aura3d-cli/pull-bridge.js.map +1 -1
- package/dist/controls/ArcballControls.d.ts +92 -0
- package/dist/controls/ArcballControls.d.ts.map +1 -0
- package/dist/controls/ArcballControls.js +286 -0
- package/dist/controls/ArcballControls.js.map +1 -0
- package/dist/controls/DragControls.d.ts +13 -0
- package/dist/controls/DragControls.d.ts.map +1 -1
- package/dist/controls/DragControls.js +24 -1
- package/dist/controls/DragControls.js.map +1 -1
- package/dist/controls/FlyControls.d.ts +9 -0
- package/dist/controls/FlyControls.d.ts.map +1 -1
- package/dist/controls/FlyControls.js +20 -2
- package/dist/controls/FlyControls.js.map +1 -1
- package/dist/controls/FocusFrame.d.ts +34 -0
- package/dist/controls/FocusFrame.d.ts.map +1 -0
- package/dist/controls/FocusFrame.js +82 -0
- package/dist/controls/FocusFrame.js.map +1 -0
- package/dist/controls/HoverOutline.d.ts +46 -0
- package/dist/controls/HoverOutline.d.ts.map +1 -0
- package/dist/controls/HoverOutline.js +87 -0
- package/dist/controls/HoverOutline.js.map +1 -0
- package/dist/controls/InteractionControls.d.ts +7 -0
- package/dist/controls/InteractionControls.d.ts.map +1 -1
- package/dist/controls/InteractionControls.js +12 -1
- package/dist/controls/InteractionControls.js.map +1 -1
- package/dist/controls/MapControls.d.ts +1 -1
- package/dist/controls/MapControls.d.ts.map +1 -1
- package/dist/controls/MapControls.js +3 -3
- package/dist/controls/MapControls.js.map +1 -1
- package/dist/controls/NativeControlTypes.d.ts +17 -0
- package/dist/controls/NativeControlTypes.d.ts.map +1 -1
- package/dist/controls/NativeControlTypes.js.map +1 -1
- package/dist/controls/OrbitControls.d.ts +26 -3
- package/dist/controls/OrbitControls.d.ts.map +1 -1
- package/dist/controls/OrbitControls.js +48 -11
- package/dist/controls/OrbitControls.js.map +1 -1
- package/dist/controls/Picking.d.ts +15 -0
- package/dist/controls/Picking.d.ts.map +1 -1
- package/dist/controls/Picking.js +62 -23
- package/dist/controls/Picking.js.map +1 -1
- package/dist/controls/PointerLockControls.d.ts.map +1 -1
- package/dist/controls/PointerLockControls.js +4 -0
- package/dist/controls/PointerLockControls.js.map +1 -1
- package/dist/controls/SelectionManager.d.ts +8 -0
- package/dist/controls/SelectionManager.d.ts.map +1 -1
- package/dist/controls/SelectionManager.js +11 -0
- package/dist/controls/SelectionManager.js.map +1 -1
- package/dist/controls/TrackballControls.d.ts +7 -0
- package/dist/controls/TrackballControls.d.ts.map +1 -1
- package/dist/controls/TrackballControls.js +21 -5
- package/dist/controls/TrackballControls.js.map +1 -1
- package/dist/controls/TransformControls.d.ts +8 -0
- package/dist/controls/TransformControls.d.ts.map +1 -1
- package/dist/controls/TransformControls.js +15 -2
- package/dist/controls/TransformControls.js.map +1 -1
- package/dist/controls/index.d.ts +6 -0
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +3 -0
- package/dist/controls/index.js.map +1 -1
- package/dist/create-aura3d/index.js +1 -1
- package/dist/editor-runtime/RootEditorSurface.d.ts +65 -0
- package/dist/editor-runtime/RootEditorSurface.d.ts.map +1 -0
- package/dist/editor-runtime/RootEditorSurface.js +62 -0
- package/dist/editor-runtime/RootEditorSurface.js.map +1 -0
- package/dist/editor-runtime/index.d.ts +2 -0
- package/dist/editor-runtime/index.d.ts.map +1 -1
- package/dist/editor-runtime/index.js +1 -0
- package/dist/editor-runtime/index.js.map +1 -1
- package/dist/engine/agent-api/AnimationController.d.ts +14 -2
- package/dist/engine/agent-api/AnimationController.d.ts.map +1 -1
- package/dist/engine/agent-api/AnimationController.js +10 -1
- package/dist/engine/agent-api/AnimationController.js.map +1 -1
- package/dist/engine/agent-api/AnimationDebugOverlay.d.ts +56 -0
- package/dist/engine/agent-api/AnimationDebugOverlay.d.ts.map +1 -0
- package/dist/engine/agent-api/AnimationDebugOverlay.js +148 -0
- package/dist/engine/agent-api/AnimationDebugOverlay.js.map +1 -0
- package/dist/engine/agent-api/AnimationMixerBuilders.d.ts +41 -0
- package/dist/engine/agent-api/AnimationMixerBuilders.d.ts.map +1 -0
- package/dist/engine/agent-api/AnimationMixerBuilders.js +146 -0
- package/dist/engine/agent-api/AnimationMixerBuilders.js.map +1 -0
- package/dist/engine/agent-api/AssetDecoders.d.ts +17 -0
- package/dist/engine/agent-api/AssetDecoders.d.ts.map +1 -0
- package/dist/engine/agent-api/AssetDecoders.js +18 -0
- package/dist/engine/agent-api/AssetDecoders.js.map +1 -0
- package/dist/engine/agent-api/CameraProjection.d.ts +2 -0
- package/dist/engine/agent-api/CameraProjection.d.ts.map +1 -0
- package/dist/engine/agent-api/CameraProjection.js +2 -0
- package/dist/engine/agent-api/CameraProjection.js.map +1 -0
- package/dist/engine/agent-api/Decals.d.ts +174 -0
- package/dist/engine/agent-api/Decals.d.ts.map +1 -0
- package/dist/engine/agent-api/Decals.js +350 -0
- package/dist/engine/agent-api/Decals.js.map +1 -0
- package/dist/engine/agent-api/DeferredFrameResources.d.ts +2 -0
- package/dist/engine/agent-api/DeferredFrameResources.d.ts.map +1 -0
- package/dist/engine/agent-api/DeferredFrameResources.js +2 -0
- package/dist/engine/agent-api/DeferredFrameResources.js.map +1 -0
- package/dist/engine/agent-api/FootPlanting.d.ts +81 -0
- package/dist/engine/agent-api/FootPlanting.d.ts.map +1 -0
- package/dist/engine/agent-api/FootPlanting.js +61 -0
- package/dist/engine/agent-api/FootPlanting.js.map +1 -0
- package/dist/engine/agent-api/GameAppRuntime.d.ts +19 -0
- package/dist/engine/agent-api/GameAppRuntime.d.ts.map +1 -1
- package/dist/engine/agent-api/GameAppRuntime.js +37 -1
- package/dist/engine/agent-api/GameAppRuntime.js.map +1 -1
- package/dist/engine/agent-api/GameCameraRigs.d.ts +185 -0
- package/dist/engine/agent-api/GameCameraRigs.d.ts.map +1 -0
- package/dist/engine/agent-api/GameCameraRigs.js +419 -0
- package/dist/engine/agent-api/GameCameraRigs.js.map +1 -0
- package/dist/engine/agent-api/GameFeel.d.ts +100 -0
- package/dist/engine/agent-api/GameFeel.d.ts.map +1 -0
- package/dist/engine/agent-api/GameFeel.js +226 -0
- package/dist/engine/agent-api/GameFeel.js.map +1 -0
- package/dist/engine/agent-api/LabelTelemetry.d.ts +114 -0
- package/dist/engine/agent-api/LabelTelemetry.d.ts.map +1 -0
- package/dist/engine/agent-api/LabelTelemetry.js +130 -0
- package/dist/engine/agent-api/LabelTelemetry.js.map +1 -0
- package/dist/engine/agent-api/ModelInstanceMatrices.d.ts +2 -0
- package/dist/engine/agent-api/ModelInstanceMatrices.d.ts.map +1 -0
- package/dist/engine/agent-api/ModelInstanceMatrices.js +2 -0
- package/dist/engine/agent-api/ModelInstanceMatrices.js.map +1 -0
- package/dist/engine/agent-api/NavigationCrowds.d.ts +174 -0
- package/dist/engine/agent-api/NavigationCrowds.d.ts.map +1 -0
- package/dist/engine/agent-api/NavigationCrowds.js +176 -0
- package/dist/engine/agent-api/NavigationCrowds.js.map +1 -0
- package/dist/engine/agent-api/PhysicsRuntime.d.ts +61 -5
- package/dist/engine/agent-api/PhysicsRuntime.d.ts.map +1 -1
- package/dist/engine/agent-api/PhysicsRuntime.js +53 -21
- package/dist/engine/agent-api/PhysicsRuntime.js.map +1 -1
- package/dist/engine/agent-api/PngTexturePixels.d.ts +9 -0
- package/dist/engine/agent-api/PngTexturePixels.d.ts.map +1 -0
- package/dist/engine/agent-api/PngTexturePixels.js +53 -0
- package/dist/engine/agent-api/PngTexturePixels.js.map +1 -0
- package/dist/engine/agent-api/RootDiagnosticSnapshot.d.ts +2 -0
- package/dist/engine/agent-api/RootDiagnosticSnapshot.d.ts.map +1 -0
- package/dist/engine/agent-api/RootDiagnosticSnapshot.js +2 -0
- package/dist/engine/agent-api/RootDiagnosticSnapshot.js.map +1 -0
- package/dist/engine/agent-api/RootFrameClock.d.ts +2 -0
- package/dist/engine/agent-api/RootFrameClock.d.ts.map +1 -0
- package/dist/engine/agent-api/RootFrameClock.js +2 -0
- package/dist/engine/agent-api/RootFrameClock.js.map +1 -0
- package/dist/engine/agent-api/RootGeometry.d.ts +16 -1
- package/dist/engine/agent-api/RootGeometry.d.ts.map +1 -1
- package/dist/engine/agent-api/RootGeometry.js.map +1 -1
- package/dist/engine/agent-api/RootPerformanceQuality.d.ts +2 -0
- package/dist/engine/agent-api/RootPerformanceQuality.d.ts.map +1 -0
- package/dist/engine/agent-api/RootPerformanceQuality.js +2 -0
- package/dist/engine/agent-api/RootPerformanceQuality.js.map +1 -0
- package/dist/engine/agent-api/RootRuntimeSupport.d.ts +61 -0
- package/dist/engine/agent-api/RootRuntimeSupport.d.ts.map +1 -0
- package/dist/engine/agent-api/RootRuntimeSupport.js +127 -0
- package/dist/engine/agent-api/RootRuntimeSupport.js.map +1 -0
- package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +28 -0
- package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
- package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
- package/dist/engine/agent-api/Scatter.d.ts +18 -0
- package/dist/engine/agent-api/Scatter.d.ts.map +1 -0
- package/dist/engine/agent-api/Scatter.js +18 -0
- package/dist/engine/agent-api/Scatter.js.map +1 -0
- package/dist/engine/agent-api/TouchControlBinding.d.ts +32 -0
- package/dist/engine/agent-api/TouchControlBinding.d.ts.map +1 -1
- package/dist/engine/agent-api/TouchControlBinding.js +36 -0
- package/dist/engine/agent-api/TouchControlBinding.js.map +1 -1
- package/dist/engine/agent-api/VehicleChassis.d.ts.map +1 -1
- package/dist/engine/agent-api/VehicleChassis.js +76 -13
- package/dist/engine/agent-api/VehicleChassis.js.map +1 -1
- package/dist/engine/agent-api/WorldLabelRenderer.d.ts +13 -2
- package/dist/engine/agent-api/WorldLabelRenderer.d.ts.map +1 -1
- package/dist/engine/agent-api/WorldLabelRenderer.js +58 -26
- package/dist/engine/agent-api/WorldLabelRenderer.js.map +1 -1
- package/dist/engine/agent-api/index.d.ts +804 -7
- package/dist/engine/agent-api/index.d.ts.map +1 -1
- package/dist/engine/agent-api/index.js +2326 -195
- package/dist/engine/agent-api/index.js.map +1 -1
- package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
- package/dist/engine/game/GameAudio.d.ts +66 -0
- package/dist/engine/game/GameAudio.d.ts.map +1 -1
- package/dist/engine/game/GameAudio.js +179 -44
- package/dist/engine/game/GameAudio.js.map +1 -1
- package/dist/engine/instances-model/InstancedModel.d.ts +111 -0
- package/dist/engine/instances-model/InstancedModel.d.ts.map +1 -0
- package/dist/engine/instances-model/InstancedModel.js +108 -0
- package/dist/engine/instances-model/InstancedModel.js.map +1 -0
- package/dist/engine/material-physical/PhysicalMaterialSpec.d.ts +72 -0
- package/dist/engine/material-physical/PhysicalMaterialSpec.d.ts.map +1 -0
- package/dist/engine/material-physical/PhysicalMaterialSpec.js +95 -0
- package/dist/engine/material-physical/PhysicalMaterialSpec.js.map +1 -0
- package/dist/engine/production-runtime/GameAppRuntime.d.ts +1 -1
- package/dist/engine/production-runtime/GameAppRuntime.d.ts.map +1 -1
- package/dist/engine/production-runtime/GameRenderPreset.d.ts +76 -0
- package/dist/engine/production-runtime/GameRenderPreset.d.ts.map +1 -1
- package/dist/engine/production-runtime/GameRenderPreset.js +167 -0
- package/dist/engine/production-runtime/GameRenderPreset.js.map +1 -1
- package/dist/engine/production-runtime/RootGpuParticleWorkload.d.ts +34 -0
- package/dist/engine/production-runtime/RootGpuParticleWorkload.d.ts.map +1 -0
- package/dist/engine/production-runtime/RootGpuParticleWorkload.js +117 -0
- package/dist/engine/production-runtime/RootGpuParticleWorkload.js.map +1 -0
- package/dist/engine/production-runtime/RootRenderSourceBridge.d.ts +2 -0
- package/dist/engine/production-runtime/RootRenderSourceBridge.d.ts.map +1 -0
- package/dist/engine/production-runtime/RootRenderSourceBridge.js +2 -0
- package/dist/engine/production-runtime/RootRenderSourceBridge.js.map +1 -0
- package/dist/engine/production-runtime/TypedGLBActor.d.ts +28 -2
- package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
- package/dist/engine/production-runtime/TypedGLBActor.js +46 -1
- package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
- package/dist/engine/production-runtime/index.d.ts +6 -4
- package/dist/engine/production-runtime/index.d.ts.map +1 -1
- package/dist/engine/production-runtime/index.js +7 -3
- package/dist/engine/production-runtime/index.js.map +1 -1
- package/dist/input/ActionMap.d.ts +40 -0
- package/dist/input/ActionMap.d.ts.map +1 -1
- package/dist/input/ActionMap.js +179 -0
- package/dist/input/ActionMap.js.map +1 -1
- package/dist/input/ComboDetector.d.ts +49 -0
- package/dist/input/ComboDetector.d.ts.map +1 -0
- package/dist/input/ComboDetector.js +102 -0
- package/dist/input/ComboDetector.js.map +1 -0
- package/dist/input/Haptics.d.ts +46 -0
- package/dist/input/Haptics.d.ts.map +1 -0
- package/dist/input/Haptics.js +67 -0
- package/dist/input/Haptics.js.map +1 -0
- package/dist/input/InputSystem.d.ts.map +1 -1
- package/dist/input/InputSystem.js +1 -0
- package/dist/input/InputSystem.js.map +1 -1
- package/dist/input/PointerDevice.d.ts +1 -0
- package/dist/input/PointerDevice.d.ts.map +1 -1
- package/dist/input/PointerDevice.js +1 -1
- package/dist/input/PointerDevice.js.map +1 -1
- package/dist/input/TouchLayouts.d.ts +35 -0
- package/dist/input/TouchLayouts.d.ts.map +1 -0
- package/dist/input/TouchLayouts.js +88 -0
- package/dist/input/TouchLayouts.js.map +1 -0
- package/dist/input/WebXRSessionController.d.ts +53 -0
- package/dist/input/WebXRSessionController.d.ts.map +1 -1
- package/dist/input/WebXRSessionController.js +88 -2
- package/dist/input/WebXRSessionController.js.map +1 -1
- package/dist/input/controls/OrbitControls.d.ts +44 -1
- package/dist/input/controls/OrbitControls.d.ts.map +1 -1
- package/dist/input/controls/OrbitControls.js +151 -12
- package/dist/input/controls/OrbitControls.js.map +1 -1
- package/dist/input/index.d.ts +8 -2
- package/dist/input/index.d.ts.map +1 -1
- package/dist/input/index.js +3 -0
- package/dist/input/index.js.map +1 -1
- package/dist/lean/base.d.ts +1 -1
- package/dist/lean/base.d.ts.map +1 -1
- package/dist/lean/base.js +1 -1
- package/dist/lean/base.js.map +1 -1
- package/dist/lean/game.d.ts +98 -0
- package/dist/lean/game.d.ts.map +1 -1
- package/dist/lean/game.js +192 -0
- package/dist/lean/game.js.map +1 -1
- package/dist/lean/index.js +1 -1
- package/dist/lean/index.js.map +1 -1
- package/dist/materials/GameReadyMaterialLibrary.d.ts +39 -0
- package/dist/materials/GameReadyMaterialLibrary.d.ts.map +1 -0
- package/dist/materials/GameReadyMaterialLibrary.js +253 -0
- package/dist/materials/GameReadyMaterialLibrary.js.map +1 -0
- package/dist/materials/MaterialValidation.d.ts +21 -0
- package/dist/materials/MaterialValidation.d.ts.map +1 -1
- package/dist/materials/MaterialValidation.js +105 -0
- package/dist/materials/MaterialValidation.js.map +1 -1
- package/dist/materials/browser-index.d.ts +2 -0
- package/dist/materials/browser-index.d.ts.map +1 -1
- package/dist/materials/browser-index.js +1 -0
- package/dist/materials/browser-index.js.map +1 -1
- package/dist/materials/node.d.ts +2 -2
- package/dist/materials/node.d.ts.map +1 -1
- package/dist/materials/node.js +1 -1
- package/dist/materials/node.js.map +1 -1
- package/dist/navigation-recast/index.d.ts +116 -0
- package/dist/navigation-recast/index.d.ts.map +1 -0
- package/dist/navigation-recast/index.js +247 -0
- package/dist/navigation-recast/index.js.map +1 -0
- package/dist/physics/CollisionVolumes.d.ts +59 -0
- package/dist/physics/CollisionVolumes.d.ts.map +1 -1
- package/dist/physics/CollisionVolumes.js +87 -0
- package/dist/physics/CollisionVolumes.js.map +1 -1
- package/dist/physics/PhysicsDebugDraw.d.ts +37 -0
- package/dist/physics/PhysicsDebugDraw.d.ts.map +1 -1
- package/dist/physics/PhysicsDebugDraw.js +38 -1
- package/dist/physics/PhysicsDebugDraw.js.map +1 -1
- package/dist/physics/PhysicsWorld.d.ts +15 -0
- package/dist/physics/PhysicsWorld.d.ts.map +1 -1
- package/dist/physics/PhysicsWorld.js +8 -1
- package/dist/physics/PhysicsWorld.js.map +1 -1
- package/dist/physics/solverless.d.ts +1 -1
- package/dist/physics/solverless.d.ts.map +1 -1
- package/dist/physics/solverless.js.map +1 -1
- package/dist/physics-rapier/HeightfieldLayout.d.ts +12 -0
- package/dist/physics-rapier/HeightfieldLayout.d.ts.map +1 -0
- package/dist/physics-rapier/HeightfieldLayout.js +19 -0
- package/dist/physics-rapier/HeightfieldLayout.js.map +1 -0
- package/dist/physics-rapier/index.d.ts +7 -1
- package/dist/physics-rapier/index.d.ts.map +1 -1
- package/dist/physics-rapier/index.js +3 -2
- package/dist/physics-rapier/index.js.map +1 -1
- package/dist/react/index.d.ts +136 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +251 -10
- package/dist/react/index.js.map +1 -1
- package/dist/rendering/AtmosphereWetness.d.ts +61 -0
- package/dist/rendering/AtmosphereWetness.d.ts.map +1 -0
- package/dist/rendering/AtmosphereWetness.js +99 -0
- package/dist/rendering/AtmosphereWetness.js.map +1 -0
- package/dist/rendering/CascadedShadowMaps.d.ts.map +1 -1
- package/dist/rendering/CascadedShadowMaps.js +3 -1
- package/dist/rendering/CascadedShadowMaps.js.map +1 -1
- package/dist/rendering/ClusteredForwardLighting.d.ts +25 -1
- package/dist/rendering/ClusteredForwardLighting.d.ts.map +1 -1
- package/dist/rendering/ClusteredForwardLighting.js +80 -3
- package/dist/rendering/ClusteredForwardLighting.js.map +1 -1
- package/dist/rendering/DayNightSky.d.ts +63 -0
- package/dist/rendering/DayNightSky.d.ts.map +1 -0
- package/dist/rendering/DayNightSky.js +156 -0
- package/dist/rendering/DayNightSky.js.map +1 -0
- package/dist/rendering/EnvironmentPresetPack.d.ts +30 -0
- package/dist/rendering/EnvironmentPresetPack.d.ts.map +1 -0
- package/dist/rendering/EnvironmentPresetPack.js +49 -0
- package/dist/rendering/EnvironmentPresetPack.js.map +1 -0
- package/dist/rendering/ExtensionScalarAtlas.d.ts +18 -0
- package/dist/rendering/ExtensionScalarAtlas.d.ts.map +1 -0
- package/dist/rendering/ExtensionScalarAtlas.js +107 -0
- package/dist/rendering/ExtensionScalarAtlas.js.map +1 -0
- package/dist/rendering/ForwardPass.d.ts +65 -0
- package/dist/rendering/ForwardPass.d.ts.map +1 -1
- package/dist/rendering/ForwardPass.js +196 -2
- package/dist/rendering/ForwardPass.js.map +1 -1
- package/dist/rendering/InstancedPBRMaterial.d.ts +1 -0
- package/dist/rendering/InstancedPBRMaterial.d.ts.map +1 -1
- package/dist/rendering/InstancedPBRMaterial.js +3 -0
- package/dist/rendering/InstancedPBRMaterial.js.map +1 -1
- package/dist/rendering/InstancingDiagnostics.d.ts +69 -0
- package/dist/rendering/InstancingDiagnostics.d.ts.map +1 -0
- package/dist/rendering/InstancingDiagnostics.js +98 -0
- package/dist/rendering/InstancingDiagnostics.js.map +1 -0
- package/dist/rendering/LeanWebGL2Device.d.ts +265 -0
- package/dist/rendering/LeanWebGL2Device.d.ts.map +1 -0
- package/dist/rendering/LeanWebGL2Device.js +4001 -0
- package/dist/rendering/LeanWebGL2Device.js.map +1 -0
- package/dist/rendering/LightingRig.d.ts +19 -1
- package/dist/rendering/LightingRig.d.ts.map +1 -1
- package/dist/rendering/LightingRig.js +61 -1
- package/dist/rendering/LightingRig.js.map +1 -1
- package/dist/rendering/MaterialBinding.js +1 -1
- package/dist/rendering/MaterialBinding.js.map +1 -1
- package/dist/rendering/MeshConsolidation.d.ts +12 -0
- package/dist/rendering/MeshConsolidation.d.ts.map +1 -1
- package/dist/rendering/MeshConsolidation.js +28 -0
- package/dist/rendering/MeshConsolidation.js.map +1 -1
- package/dist/rendering/MorphTargetPlan.d.ts +29 -2
- package/dist/rendering/MorphTargetPlan.d.ts.map +1 -1
- package/dist/rendering/MorphTargetPlan.js +23 -4
- package/dist/rendering/MorphTargetPlan.js.map +1 -1
- package/dist/rendering/NormalMappedPBRMaterial.d.ts.map +1 -1
- package/dist/rendering/NormalMappedPBRMaterial.js +22 -2
- package/dist/rendering/NormalMappedPBRMaterial.js.map +1 -1
- package/dist/rendering/OceanSurface.d.ts +75 -0
- package/dist/rendering/OceanSurface.d.ts.map +1 -1
- package/dist/rendering/OceanSurface.js +211 -0
- package/dist/rendering/OceanSurface.js.map +1 -1
- package/dist/rendering/PBRMaterial.d.ts +1 -0
- package/dist/rendering/PBRMaterial.d.ts.map +1 -1
- package/dist/rendering/PBRMaterial.js +23 -2
- package/dist/rendering/PBRMaterial.js.map +1 -1
- package/dist/rendering/PbrReference.d.ts +48 -0
- package/dist/rendering/PbrReference.d.ts.map +1 -1
- package/dist/rendering/PbrReference.js +126 -0
- package/dist/rendering/PbrReference.js.map +1 -1
- package/dist/rendering/PlanarReflection.d.ts +221 -0
- package/dist/rendering/PlanarReflection.d.ts.map +1 -0
- package/dist/rendering/PlanarReflection.js +541 -0
- package/dist/rendering/PlanarReflection.js.map +1 -0
- package/dist/rendering/PostProcessPass.d.ts +32 -0
- package/dist/rendering/PostProcessPass.d.ts.map +1 -1
- package/dist/rendering/PostProcessPass.js +79 -36
- package/dist/rendering/PostProcessPass.js.map +1 -1
- package/dist/rendering/ProjectionMath.d.ts +3 -0
- package/dist/rendering/ProjectionMath.d.ts.map +1 -0
- package/dist/rendering/ProjectionMath.js +26 -0
- package/dist/rendering/ProjectionMath.js.map +1 -0
- package/dist/rendering/ReflectionSurfaces.d.ts +17 -0
- package/dist/rendering/ReflectionSurfaces.d.ts.map +1 -1
- package/dist/rendering/ReflectionSurfaces.js +103 -1
- package/dist/rendering/ReflectionSurfaces.js.map +1 -1
- package/dist/rendering/RenderDevice.d.ts +92 -0
- package/dist/rendering/RenderDevice.d.ts.map +1 -1
- package/dist/rendering/RenderDevice.js +85 -0
- package/dist/rendering/RenderDevice.js.map +1 -1
- package/dist/rendering/Renderer.d.ts +9 -0
- package/dist/rendering/Renderer.d.ts.map +1 -1
- package/dist/rendering/Renderer.js +97 -9
- package/dist/rendering/Renderer.js.map +1 -1
- package/dist/rendering/RendererPostprocessPlan.d.ts +64 -3
- package/dist/rendering/RendererPostprocessPlan.d.ts.map +1 -1
- package/dist/rendering/RendererPostprocessPlan.js +116 -12
- package/dist/rendering/RendererPostprocessPlan.js.map +1 -1
- package/dist/rendering/RendererVisualPipelineReport.d.ts.map +1 -1
- package/dist/rendering/RendererVisualPipelineReport.js +3 -1
- package/dist/rendering/RendererVisualPipelineReport.js.map +1 -1
- package/dist/rendering/Sampler.d.ts +29 -0
- package/dist/rendering/Sampler.d.ts.map +1 -1
- package/dist/rendering/Sampler.js +47 -0
- package/dist/rendering/Sampler.js.map +1 -1
- package/dist/rendering/ScreenSpaceReflectionPass.d.ts +43 -0
- package/dist/rendering/ScreenSpaceReflectionPass.d.ts.map +1 -0
- package/dist/rendering/ScreenSpaceReflectionPass.js +58 -0
- package/dist/rendering/ScreenSpaceReflectionPass.js.map +1 -0
- package/dist/rendering/SdfText.d.ts +250 -0
- package/dist/rendering/SdfText.d.ts.map +1 -0
- package/dist/rendering/SdfText.js +551 -0
- package/dist/rendering/SdfText.js.map +1 -0
- package/dist/rendering/ShaderChunks.d.ts.map +1 -1
- package/dist/rendering/ShaderChunks.js +30 -12
- package/dist/rendering/ShaderChunks.js.map +1 -1
- package/dist/rendering/ShaderLibrary.d.ts.map +1 -1
- package/dist/rendering/ShaderLibrary.js +601 -186
- package/dist/rendering/ShaderLibrary.js.map +1 -1
- package/dist/rendering/ShaderLibraryCore.d.ts +3 -0
- package/dist/rendering/ShaderLibraryCore.d.ts.map +1 -1
- package/dist/rendering/ShaderLibraryCore.js +73 -12
- package/dist/rendering/ShaderLibraryCore.js.map +1 -1
- package/dist/rendering/ShadowMap.d.ts +24 -0
- package/dist/rendering/ShadowMap.d.ts.map +1 -1
- package/dist/rendering/ShadowMap.js +43 -0
- package/dist/rendering/ShadowMap.js.map +1 -1
- package/dist/rendering/SkinnedLitMaterial.d.ts.map +1 -1
- package/dist/rendering/SkinnedLitMaterial.js +2 -0
- package/dist/rendering/SkinnedLitMaterial.js.map +1 -1
- package/dist/rendering/SkinningBounds.d.ts +10 -0
- package/dist/rendering/SkinningBounds.d.ts.map +1 -1
- package/dist/rendering/SkinningBounds.js +27 -0
- package/dist/rendering/SkinningBounds.js.map +1 -1
- package/dist/rendering/SpriteFlipbook.d.ts +60 -0
- package/dist/rendering/SpriteFlipbook.d.ts.map +1 -0
- package/dist/rendering/SpriteFlipbook.js +128 -0
- package/dist/rendering/SpriteFlipbook.js.map +1 -0
- package/dist/rendering/TemporalHistory.d.ts +37 -0
- package/dist/rendering/TemporalHistory.d.ts.map +1 -0
- package/dist/rendering/TemporalHistory.js +135 -0
- package/dist/rendering/TemporalHistory.js.map +1 -0
- package/dist/rendering/TemporalMath.d.ts +3 -0
- package/dist/rendering/TemporalMath.d.ts.map +1 -0
- package/dist/rendering/TemporalMath.js +9 -0
- package/dist/rendering/TemporalMath.js.map +1 -0
- package/dist/rendering/TerrainTiles.d.ts +120 -0
- package/dist/rendering/TerrainTiles.d.ts.map +1 -0
- package/dist/rendering/TerrainTiles.js +221 -0
- package/dist/rendering/TerrainTiles.js.map +1 -0
- package/dist/rendering/TexturedPBRMaterial.d.ts +4 -0
- package/dist/rendering/TexturedPBRMaterial.d.ts.map +1 -1
- package/dist/rendering/TexturedPBRMaterial.js +88 -5
- package/dist/rendering/TexturedPBRMaterial.js.map +1 -1
- package/dist/rendering/TexturedUnlitMaterial.d.ts.map +1 -1
- package/dist/rendering/TexturedUnlitMaterial.js +20 -2
- package/dist/rendering/TexturedUnlitMaterial.js.map +1 -1
- package/dist/rendering/UnlitMaterial.d.ts.map +1 -1
- package/dist/rendering/UnlitMaterial.js +20 -2
- package/dist/rendering/UnlitMaterial.js.map +1 -1
- package/dist/rendering/VertexFormat.d.ts +6 -0
- package/dist/rendering/VertexFormat.d.ts.map +1 -1
- package/dist/rendering/VertexFormat.js +12 -0
- package/dist/rendering/VertexFormat.js.map +1 -1
- package/dist/rendering/VolumetricFog.d.ts +55 -0
- package/dist/rendering/VolumetricFog.d.ts.map +1 -0
- package/dist/rendering/VolumetricFog.js +91 -0
- package/dist/rendering/VolumetricFog.js.map +1 -0
- package/dist/rendering/WaterSurface.d.ts +77 -0
- package/dist/rendering/WaterSurface.d.ts.map +1 -0
- package/dist/rendering/WaterSurface.js +133 -0
- package/dist/rendering/WaterSurface.js.map +1 -0
- package/dist/rendering/WebGL2Device.d.ts +73 -0
- package/dist/rendering/WebGL2Device.d.ts.map +1 -1
- package/dist/rendering/WebGL2Device.js +836 -79
- package/dist/rendering/WebGL2Device.js.map +1 -1
- package/dist/rendering/WebGPUDevice.d.ts +62 -1
- package/dist/rendering/WebGPUDevice.d.ts.map +1 -1
- package/dist/rendering/WebGPUDevice.js +844 -51
- package/dist/rendering/WebGPUDevice.js.map +1 -1
- package/dist/rendering/WebGPUExtensionAtlas.d.ts +6 -0
- package/dist/rendering/WebGPUExtensionAtlas.d.ts.map +1 -0
- package/dist/rendering/WebGPUExtensionAtlas.js +225 -0
- package/dist/rendering/WebGPUExtensionAtlas.js.map +1 -0
- package/dist/rendering/WebGPUSkinningLimits.d.ts +40 -0
- package/dist/rendering/WebGPUSkinningLimits.d.ts.map +1 -1
- package/dist/rendering/WebGPUSkinningLimits.js +32 -0
- package/dist/rendering/WebGPUSkinningLimits.js.map +1 -1
- package/dist/rendering/effects/CollisionModule.d.ts +9 -0
- package/dist/rendering/effects/CollisionModule.d.ts.map +1 -1
- package/dist/rendering/effects/CollisionModule.js +10 -0
- package/dist/rendering/effects/CollisionModule.js.map +1 -1
- package/dist/rendering/effects/ColorModule.d.ts +7 -0
- package/dist/rendering/effects/ColorModule.d.ts.map +1 -1
- package/dist/rendering/effects/ColorModule.js +23 -0
- package/dist/rendering/effects/ColorModule.js.map +1 -1
- package/dist/rendering/effects/ForceModule.d.ts +35 -0
- package/dist/rendering/effects/ForceModule.d.ts.map +1 -1
- package/dist/rendering/effects/ForceModule.js +49 -0
- package/dist/rendering/effects/ForceModule.js.map +1 -1
- package/dist/rendering/effects/GPUParticleBackend.d.ts +188 -0
- package/dist/rendering/effects/GPUParticleBackend.d.ts.map +1 -1
- package/dist/rendering/effects/GPUParticleBackend.js +710 -1
- package/dist/rendering/effects/GPUParticleBackend.js.map +1 -1
- package/dist/rendering/effects/HeightfieldModule.d.ts +58 -0
- package/dist/rendering/effects/HeightfieldModule.d.ts.map +1 -0
- package/dist/rendering/effects/HeightfieldModule.js +129 -0
- package/dist/rendering/effects/HeightfieldModule.js.map +1 -0
- package/dist/rendering/effects/LightingModule.d.ts +34 -0
- package/dist/rendering/effects/LightingModule.d.ts.map +1 -0
- package/dist/rendering/effects/LightingModule.js +54 -0
- package/dist/rendering/effects/LightingModule.js.map +1 -0
- package/dist/rendering/effects/ParticleDiagnostics.d.ts +8 -0
- package/dist/rendering/effects/ParticleDiagnostics.d.ts.map +1 -1
- package/dist/rendering/effects/ParticleDiagnostics.js +3 -0
- package/dist/rendering/effects/ParticleDiagnostics.js.map +1 -1
- package/dist/rendering/effects/ParticleModule.d.ts +8 -0
- package/dist/rendering/effects/ParticleModule.d.ts.map +1 -1
- package/dist/rendering/effects/ParticleModule.js.map +1 -1
- package/dist/rendering/effects/ParticleRenderer.d.ts +20 -0
- package/dist/rendering/effects/ParticleRenderer.d.ts.map +1 -1
- package/dist/rendering/effects/ParticleRenderer.js +57 -7
- package/dist/rendering/effects/ParticleRenderer.js.map +1 -1
- package/dist/rendering/effects/ParticleSystem.d.ts +45 -1
- package/dist/rendering/effects/ParticleSystem.d.ts.map +1 -1
- package/dist/rendering/effects/ParticleSystem.js +373 -4
- package/dist/rendering/effects/ParticleSystem.js.map +1 -1
- package/dist/rendering/effects/ResidentGPUParticleRenderer.d.ts +158 -0
- package/dist/rendering/effects/ResidentGPUParticleRenderer.d.ts.map +1 -0
- package/dist/rendering/effects/ResidentGPUParticleRenderer.js +576 -0
- package/dist/rendering/effects/ResidentGPUParticleRenderer.js.map +1 -0
- package/dist/rendering/effects/SizeModule.d.ts +4 -0
- package/dist/rendering/effects/SizeModule.d.ts.map +1 -1
- package/dist/rendering/effects/SizeModule.js +16 -0
- package/dist/rendering/effects/SizeModule.js.map +1 -1
- package/dist/rendering/effects/SubEmitterModule.d.ts +61 -0
- package/dist/rendering/effects/SubEmitterModule.d.ts.map +1 -0
- package/dist/rendering/effects/SubEmitterModule.js +119 -0
- package/dist/rendering/effects/SubEmitterModule.js.map +1 -0
- package/dist/rendering/effects/TrailModule.d.ts +38 -0
- package/dist/rendering/effects/TrailModule.d.ts.map +1 -1
- package/dist/rendering/effects/TrailModule.js +95 -2
- package/dist/rendering/effects/TrailModule.js.map +1 -1
- package/dist/rendering/effects/TurbulenceModule.d.ts +43 -0
- package/dist/rendering/effects/TurbulenceModule.d.ts.map +1 -0
- package/dist/rendering/effects/TurbulenceModule.js +130 -0
- package/dist/rendering/effects/TurbulenceModule.js.map +1 -0
- package/dist/rendering/effects/index.d.ts +4 -0
- package/dist/rendering/effects/index.d.ts.map +1 -1
- package/dist/rendering/effects/index.js +4 -0
- package/dist/rendering/effects/index.js.map +1 -1
- package/dist/rendering/extension-scalar-atlas.d.ts +3 -0
- package/dist/rendering/extension-scalar-atlas.d.ts.map +1 -0
- package/dist/rendering/extension-scalar-atlas.js +2 -0
- package/dist/rendering/extension-scalar-atlas.js.map +1 -0
- package/dist/rendering/index.d.ts +58 -19
- package/dist/rendering/index.d.ts.map +1 -1
- package/dist/rendering/index.js +32 -10
- package/dist/rendering/index.js.map +1 -1
- package/dist/rendering/lean/LeanProductionRenderer.d.ts.map +1 -1
- package/dist/rendering/lean/LeanProductionRenderer.js +2 -2
- package/dist/rendering/lean/LeanProductionRenderer.js.map +1 -1
- package/dist/rendering/lean-core-runtime.d.ts +13 -0
- package/dist/rendering/lean-core-runtime.d.ts.map +1 -0
- package/dist/rendering/lean-core-runtime.js +10 -0
- package/dist/rendering/lean-core-runtime.js.map +1 -0
- package/dist/rendering/materials/MaterialExtensions.js +3 -3
- package/dist/rendering/materials/MaterialExtensions.js.map +1 -1
- package/dist/rendering/postprocess/NativeBloomPyramid.d.ts +44 -0
- package/dist/rendering/postprocess/NativeBloomPyramid.d.ts.map +1 -0
- package/dist/rendering/postprocess/NativeBloomPyramid.js +84 -0
- package/dist/rendering/postprocess/NativeBloomPyramid.js.map +1 -0
- package/dist/rendering/postprocess/NativeLdrEffectLuts.d.ts +10 -1
- package/dist/rendering/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
- package/dist/rendering/postprocess/NativeLdrEffectLuts.js +19 -2
- package/dist/rendering/postprocess/NativeLdrEffectLuts.js.map +1 -1
- package/dist/rendering/postprocess/index.d.ts +2 -0
- package/dist/rendering/postprocess/index.d.ts.map +1 -1
- package/dist/rendering/postprocess/index.js +1 -0
- package/dist/rendering/postprocess/index.js.map +1 -1
- package/dist/rendering/production-runtime/PBRHDRPipeline.d.ts +22 -0
- package/dist/rendering/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
- package/dist/rendering/production-runtime/PBRHDRPipeline.js +125 -0
- package/dist/rendering/production-runtime/PBRHDRPipeline.js.map +1 -1
- package/dist/rendering/production-runtime/ProductionRendererTypes.d.ts +1 -0
- package/dist/rendering/production-runtime/ProductionRendererTypes.d.ts.map +1 -1
- package/dist/rendering/production-runtime/ProductionRendererTypes.js.map +1 -1
- package/dist/rendering/production-runtime/ProductionRuntimeRenderer.d.ts +3 -0
- package/dist/rendering/production-runtime/ProductionRuntimeRenderer.d.ts.map +1 -1
- package/dist/rendering/production-runtime/ProductionRuntimeRenderer.js +8 -1
- package/dist/rendering/production-runtime/ProductionRuntimeRenderer.js.map +1 -1
- package/dist/rendering/production-runtime/ProductionWebGL2Renderer.d.ts +4 -0
- package/dist/rendering/production-runtime/ProductionWebGL2Renderer.d.ts.map +1 -1
- package/dist/rendering/production-runtime/ProductionWebGL2Renderer.js +15 -0
- package/dist/rendering/production-runtime/ProductionWebGL2Renderer.js.map +1 -1
- package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts +58 -0
- package/dist/rendering/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
- package/dist/rendering/production-runtime/ProductionWebGPURenderer.js +127 -0
- package/dist/rendering/production-runtime/ProductionWebGPURenderer.js.map +1 -1
- package/dist/rendering/production-runtime/framegraph/FrameGraph.d.ts +4 -2
- package/dist/rendering/production-runtime/framegraph/FrameGraph.d.ts.map +1 -1
- package/dist/rendering/production-runtime/framegraph/FrameGraph.js +62 -4
- package/dist/rendering/production-runtime/framegraph/FrameGraph.js.map +1 -1
- package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.d.ts +32 -0
- package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.d.ts.map +1 -0
- package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.js +111 -0
- package/dist/rendering/production-runtime/framegraph/NativeFrameGraphBindings.js.map +1 -0
- package/dist/rendering/production-runtime/framegraph/RenderPass.d.ts +23 -0
- package/dist/rendering/production-runtime/framegraph/RenderPass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/framegraph/RenderPass.js +87 -1
- package/dist/rendering/production-runtime/framegraph/RenderPass.js.map +1 -1
- package/dist/rendering/production-runtime/index.d.ts +7 -4
- package/dist/rendering/production-runtime/index.d.ts.map +1 -1
- package/dist/rendering/production-runtime/index.js +4 -2
- package/dist/rendering/production-runtime/index.js.map +1 -1
- package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts +18 -1
- package/dist/rendering/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/passes/DepthPrepass.js +41 -2
- package/dist/rendering/production-runtime/passes/DepthPrepass.js.map +1 -1
- package/dist/rendering/production-runtime/passes/FramegraphTopology.d.ts +13 -0
- package/dist/rendering/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
- package/dist/rendering/production-runtime/passes/FramegraphTopology.js +59 -0
- package/dist/rendering/production-runtime/passes/FramegraphTopology.js.map +1 -0
- package/dist/rendering/production-runtime/passes/OpaquePass.d.ts +19 -1
- package/dist/rendering/production-runtime/passes/OpaquePass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/passes/OpaquePass.js +43 -2
- package/dist/rendering/production-runtime/passes/OpaquePass.js.map +1 -1
- package/dist/rendering/production-runtime/passes/ShadowPass.d.ts +19 -1
- package/dist/rendering/production-runtime/passes/ShadowPass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/passes/ShadowPass.js +45 -2
- package/dist/rendering/production-runtime/passes/ShadowPass.js.map +1 -1
- package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts +17 -1
- package/dist/rendering/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/passes/SkyboxPass.js +41 -2
- package/dist/rendering/production-runtime/passes/SkyboxPass.js.map +1 -1
- package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts +26 -1
- package/dist/rendering/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/passes/ToneMappingPass.js +62 -2
- package/dist/rendering/production-runtime/passes/ToneMappingPass.js.map +1 -1
- package/dist/rendering/production-runtime/passes/TransparentPass.d.ts +19 -1
- package/dist/rendering/production-runtime/passes/TransparentPass.d.ts.map +1 -1
- package/dist/rendering/production-runtime/passes/TransparentPass.js +47 -2
- package/dist/rendering/production-runtime/passes/TransparentPass.js.map +1 -1
- package/dist/rendering/reflection-surfaces.d.ts +5 -0
- package/dist/rendering/reflection-surfaces.d.ts.map +1 -0
- package/dist/rendering/reflection-surfaces.js +3 -0
- package/dist/rendering/reflection-surfaces.js.map +1 -0
- package/dist/rendering/shadows/CascadeHysteresis.d.ts +67 -0
- package/dist/rendering/shadows/CascadeHysteresis.d.ts.map +1 -0
- package/dist/rendering/shadows/CascadeHysteresis.js +104 -0
- package/dist/rendering/shadows/CascadeHysteresis.js.map +1 -0
- package/dist/rendering/shadows/ContactShadows.d.ts +52 -0
- package/dist/rendering/shadows/ContactShadows.d.ts.map +1 -1
- package/dist/rendering/shadows/ContactShadows.js +122 -0
- package/dist/rendering/shadows/ContactShadows.js.map +1 -1
- package/dist/rendering/shadows/SpotShadowMaps.d.ts +77 -0
- package/dist/rendering/shadows/SpotShadowMaps.d.ts.map +1 -0
- package/dist/rendering/shadows/SpotShadowMaps.js +106 -0
- package/dist/rendering/shadows/SpotShadowMaps.js.map +1 -0
- package/dist/rendering/webgpu/WebGPUPostProcess.d.ts +2 -0
- package/dist/rendering/webgpu/WebGPUPostProcess.d.ts.map +1 -1
- package/dist/rendering/webgpu/WebGPUPostProcess.js +1 -0
- package/dist/rendering/webgpu/WebGPUPostProcess.js.map +1 -1
- package/dist/rendering/webgpu/WebGPUPostShaders.d.ts +77 -0
- package/dist/rendering/webgpu/WebGPUPostShaders.d.ts.map +1 -0
- package/dist/rendering/webgpu/WebGPUPostShaders.js +261 -0
- package/dist/rendering/webgpu/WebGPUPostShaders.js.map +1 -0
- package/dist/rendering/webgpu/WebGPUTemporal.d.ts +17 -0
- package/dist/rendering/webgpu/WebGPUTemporal.d.ts.map +1 -0
- package/dist/rendering/webgpu/WebGPUTemporal.js +64 -0
- package/dist/rendering/webgpu/WebGPUTemporal.js.map +1 -0
- package/dist/rendering/webgpu.d.ts +6 -0
- package/dist/rendering/webgpu.d.ts.map +1 -1
- package/dist/rendering/webgpu.js +4 -0
- package/dist/rendering/webgpu.js.map +1 -1
- package/dist/scene/Scene.d.ts +8 -1
- package/dist/scene/Scene.d.ts.map +1 -1
- package/dist/scene/Scene.js +12 -2
- package/dist/scene/Scene.js.map +1 -1
- package/dist/scripting/VisualGraphContext.d.ts +31 -0
- package/dist/scripting/VisualGraphContext.d.ts.map +1 -1
- package/dist/scripting/VisualGraphExecutor.d.ts.map +1 -1
- package/dist/scripting/VisualGraphExecutor.js +110 -0
- package/dist/scripting/VisualGraphExecutor.js.map +1 -1
- package/dist/scripting/VisualNodeCatalog.d.ts +1 -1
- package/dist/scripting/VisualNodeCatalog.d.ts.map +1 -1
- package/dist/scripting/VisualNodeCatalog.js +20 -0
- package/dist/scripting/VisualNodeCatalog.js.map +1 -1
- package/dist/scripting/VisualScriptingRoot.d.ts +96 -0
- package/dist/scripting/VisualScriptingRoot.d.ts.map +1 -0
- package/dist/scripting/VisualScriptingRoot.js +150 -0
- package/dist/scripting/VisualScriptingRoot.js.map +1 -0
- package/dist/scripting/index.d.ts +3 -1
- package/dist/scripting/index.d.ts.map +1 -1
- package/dist/scripting/index.js +1 -0
- package/dist/scripting/index.js.map +1 -1
- package/package.json +40 -25
- package/templates/cinematic-scene/package.json +2 -2
- package/templates/cinematic-scene/src/main.ts +14 -2
- package/templates/mini-game/package.json +2 -2
- package/templates/product-viewer/package.json +2 -2
- package/templates/production-asset-inspector/package.json +1 -1
- package/templates/production-material-studio/package.json +1 -1
- package/templates/production-product-configurator/package.json +1 -1
- package/templates/production-product-viewer/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyRootParticleQuality, composeModelInstanceMatrices, createCameraProjection, DeferredFrameResources, getRootPerformanceBaseSize, getRootPerformanceQuality, getRootRenderSource, hasRootRenderableContent, includeRootSourceMetadata, initializeRootPerformanceQuality, readRootDiagnosticSnapshot, resolveCameraClipping, resolveRootRenderTime, setRootPerformanceQuality, supportsRootParticleQuality, validateRootPerformanceQuality } from "./RootRuntimeSupport.js";
|
|
1
2
|
/*
|
|
2
3
|
* WS-2.2 — module-scope physics values come from the SOLVERLESS entry.
|
|
3
4
|
*
|
|
@@ -22,10 +23,11 @@ import { PhysicsDebugDraw, PhysicsStepper, ScenePhysicsBridge, Shape as PhysicsS
|
|
|
22
23
|
* of which arrived just to reach one class.
|
|
23
24
|
*/
|
|
24
25
|
import { PhysicsWorld } from "../../physics/world.js";
|
|
25
|
-
import { createExternalParityEnvironmentLighting, Geometry, IndexBuffer, InstancedPBRMaterial, PBRMaterial, ProductionRuntimeRenderer, srgbToLinearChannel, VertexBuffer, VertexFormat } from "../../rendering/index.js";
|
|
26
|
+
import { createBeamDescriptor, createDayNightSky, createDualProbeEnvironmentLightingResources, createExternalParityEnvironmentLighting, createProductionEnvironmentLightingResources, createProductionPbrHdrPipelineFromRadiance, createSdfFontAtlas, createSdfTextQuadMesh, createSpotShadowProjection, createWaterSurface, createWeatherState, describeSdfTextPixelBacking, describeWetMaterial, layoutSdfText, rasterizeSdfTextLabelImage, resolveFlipbookUv, resolveSdfTextFrameOpacity, resolveVolumetricFog, selectSpotShadowAtlasTier, sampleOceanFixture, Geometry, IndexBuffer, InstancedPBRMaterial, PBRMaterial, ProductionRuntimeRenderer, resolveSamplerAnisotropy, Sampler, srgbToLinearChannel, Texture, TexturedPBRMaterial, VertexBuffer, VertexFormat } from "../../rendering/index.js";
|
|
26
27
|
import { DirectionalLight, PointLight, SpotLight } from "../../scene/index.js";
|
|
27
|
-
import { lookAtMat4, multiplyMat4
|
|
28
|
+
import { identityMat4, lookAtMat4, multiplyMat4 } from "../../scene/MathTypes.js";
|
|
28
29
|
import { AURA_NORMALIZED_MODEL_MAX_DIMENSION, boundsFromAsset, boundsHeight, boundsMaxDimension, boundsSize } from "./SceneGroundingUtils.js";
|
|
30
|
+
import { collectLabelTelemetry, labelTelemetryRoleFor, summarizeTextBuckets } from "./LabelTelemetry.js";
|
|
29
31
|
import { createWorldLabelLayer } from "./WorldLabelRenderer.js";
|
|
30
32
|
import { createAuraText3DGeometry, defineAuraCustomGeometry, selectAuraRootLodLevel } from "./RootGeometry.js";
|
|
31
33
|
export * from "./RootGeometry.js";
|
|
@@ -49,8 +51,18 @@ export * from "./PhysicsRuntime.js";
|
|
|
49
51
|
*/
|
|
50
52
|
export { buildMeshBVH, createMeshSurfaceQuery, raycastMesh } from "../../physics/solverless.js";
|
|
51
53
|
import { createPhysicsRuntime } from "./PhysicsRuntime.js";
|
|
54
|
+
import { gameCameraRigs } from "./GameCameraRigs.js";
|
|
55
|
+
import { gameFeelBuilders } from "./GameFeel.js";
|
|
52
56
|
export * from "./FocusSelection.js";
|
|
53
57
|
export * from "./WorldLabelRenderer.js";
|
|
58
|
+
export * from "./GameCameraRigs.js";
|
|
59
|
+
export * from "./GameFeel.js";
|
|
60
|
+
export * from "./LabelTelemetry.js";
|
|
61
|
+
export * from "./NavigationCrowds.js";
|
|
62
|
+
export * from "./FootPlanting.js";
|
|
63
|
+
export * from "./Decals.js";
|
|
64
|
+
export * from "./Scatter.js";
|
|
65
|
+
export * from "./AssetDecoders.js";
|
|
54
66
|
export * from "./VehicleChassis.js";
|
|
55
67
|
export * from "./VehicleDriverAi.js";
|
|
56
68
|
/**
|
|
@@ -83,7 +95,16 @@ export * from "./WebCodecsFrameEncoder.js";
|
|
|
83
95
|
*/
|
|
84
96
|
export { GameInspector, createGameInspector } from "./GameInspector.js";
|
|
85
97
|
import { createGameInspector } from "./GameInspector.js";
|
|
98
|
+
import { createPhysicalMaterialSpec } from "../material-physical/PhysicalMaterialSpec.js";
|
|
99
|
+
import { createInstancedModelNode } from "../instances-model/InstancedModel.js";
|
|
100
|
+
import { resolveWrinkleMapStrength, warnOnInstancingFallback } from "../../rendering/index.js";
|
|
86
101
|
export { createGameAudio } from "../game/GameAudio.js";
|
|
102
|
+
export { PositionalEmitter, FootstepPlayer, createGameMixer, attachFocusPolicy, computeDistanceAttenuation, computeDopplerShift, resolveOcclusion } from "../../audio/index.js";
|
|
103
|
+
export { ComboDetector, createTouchLayoutPreset, probeHaptics, playHaptic } from "../../input/index.js";
|
|
104
|
+
export { attachVisualScriptingGraph, createVisualScriptingGraph, listVisualScriptingNodeCatalog } from "../../scripting/index.js";
|
|
105
|
+
export { createRootEditorSurface } from "../../editor-runtime/index.js";
|
|
106
|
+
import { attachVisualScriptingGraph as attachVisualScriptingGraphFn, createVisualScriptingGraph as createVisualScriptingGraphFn, listVisualScriptingNodeCatalog as listVisualScriptingNodeCatalogFn } from "../../scripting/index.js";
|
|
107
|
+
import { createRootEditorSurface as createRootEditorSurfaceFn } from "../../editor-runtime/index.js";
|
|
87
108
|
export * from "./RenderProgressTracker.js";
|
|
88
109
|
export * from "./AudioVisemeAnalyzer.js";
|
|
89
110
|
export * from "./ExternalPhonemeAnalyzer.js";
|
|
@@ -114,6 +135,7 @@ export * from "./EpisodeTemplates.js";
|
|
|
114
135
|
export * from "./AnimationMotionQuality.js";
|
|
115
136
|
export * from "./AnimationRouteProof.js";
|
|
116
137
|
export * from "./AnimationEpisodePackage.js";
|
|
138
|
+
import { evaluateDistancePrioritizedMipResidency } from "../../assets/browser-index.js";
|
|
117
139
|
import { createFrameLoop } from "./FrameLoop.js";
|
|
118
140
|
import { createCombatWorld, createGameCameraDirector, createGameEffects, applyGameCombatEventsToRuntime, createGameAccessibilityFocus, createGameAccessibilityLabel, createGameAccessibilityRuntimeSettings, createGameEventLog, createGameHighContrastSource, createGameHudBindings, createGameHudComboBinding, createGameHudCheckpointBinding, createGameHudDebugToggleBinding, createGameHudEventLogBinding, createGameHudHealthBinding, createGameHudLivesBinding, createGameHudMeterBinding, createGameHudObjectiveBinding, createGameHudRoundBinding, createGameHudScoreBinding, createGameHudSnapshot, createGameHudTimerBinding, createGameHudValueBinding, createGameBoxCollider, createGameCapsuleCollider, createGameColliderDebugGeometry, createGameCombatDebugGeometry, createGameCollisionWorld, createGamePlanarCollisionWorld, createGameDebugOverlayData, createGameDebugSceneNodes, createGameHitboxDebugGeometry, createGameSimulation, exportGameInputReplay, createGameFighting2DRules, createGameInput, createGameInputReplay, createGameInputReplayDriver, importGameInputReplay, createGameJumpAssist, createGameKinematicBody, createGamePauseControlsSource, createGameRectCollider, createGameReducedFlashSource, createGameReducedMotionSource, createGameSphereCollider, createGameTouchControlLayout, gameColliderAabb, gameColliders, gameEffectPresets, gameGuardboxes, gameHitboxes, gameHurtboxes, gameInputReplayEventsAt, gamePushboxes, runGameSimulation, gameTriggerVolumes } from "./GameRuntime.js";
|
|
119
141
|
import { collectGameRuntimeEvidence as collectGameRuntimeEvidenceV105 } from "./GameEvidence.js";
|
|
@@ -153,6 +175,8 @@ export * from "./GameAssetValidation.js";
|
|
|
153
175
|
export * from "./CharacterAssembly.js";
|
|
154
176
|
export * from "./AssetEvidence.js";
|
|
155
177
|
export * from "./AnimationController.js";
|
|
178
|
+
export * from "./AnimationMixerBuilders.js";
|
|
179
|
+
export * from "./AnimationDebugOverlay.js";
|
|
156
180
|
export { gameAssetValidation, quaterniusGameReadyFighterValidationContract, validateQuaterniusGameReadyFighterAsset } from "./GameAssetValidation.js";
|
|
157
181
|
export { createAnimationController } from "./AnimationController.js";
|
|
158
182
|
export * from "./PromptAnimationContract.js";
|
|
@@ -244,7 +268,8 @@ export function model(asset, options = {}) {
|
|
|
244
268
|
targetMaxDimension: options.targetMaxDimension,
|
|
245
269
|
targetLength: options.targetLength,
|
|
246
270
|
physics: options.physics,
|
|
247
|
-
hiddenNodeNames: options.hiddenNodeNames
|
|
271
|
+
hiddenNodeNames: options.hiddenNodeNames,
|
|
272
|
+
wrinkle: options.wrinkle
|
|
248
273
|
});
|
|
249
274
|
}
|
|
250
275
|
export function unsafeModelUrl(url, options = {}) {
|
|
@@ -337,15 +362,102 @@ export const instances = {
|
|
|
337
362
|
cylinder: (options) => instancedPrimitive("cylinder", options),
|
|
338
363
|
capsule: (options) => instancedPrimitive("capsule", options),
|
|
339
364
|
torus: (options) => instancedPrimitive("torus", options),
|
|
340
|
-
custom: (spec, options) => instancedPrimitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) })
|
|
365
|
+
custom: (spec, options) => instancedPrimitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) }),
|
|
366
|
+
/**
|
|
367
|
+
* P2 instanced GLB models (muse3jsparity-PRD): one draw class for repeated
|
|
368
|
+
* static models. Skinned actors fall back with a D1 warning at mount (E1
|
|
369
|
+
* owns rigs); explicitly unaware materials warn at build.
|
|
370
|
+
*/
|
|
371
|
+
model: (asset, options) => {
|
|
372
|
+
const built = createInstancedModelNode({
|
|
373
|
+
asset,
|
|
374
|
+
...(options.name !== undefined ? { name: options.name } : {}),
|
|
375
|
+
transforms: options.transforms.map((transform) => ({
|
|
376
|
+
...(transform.position !== undefined ? { position: [...transform.position] } : {}),
|
|
377
|
+
...(transform.rotation !== undefined ? { rotation: [...transform.rotation] } : {}),
|
|
378
|
+
...(transform.scale !== undefined
|
|
379
|
+
? { scale: typeof transform.scale === "number" ? transform.scale : [...transform.scale] }
|
|
380
|
+
: {})
|
|
381
|
+
})),
|
|
382
|
+
...(options.colors ? { colors: [...options.colors] } : {}),
|
|
383
|
+
...(options.lod ? { lod: options.lod } : {}),
|
|
384
|
+
materialInstancingAware: options.instancingAware ?? true,
|
|
385
|
+
...(options.maxInstancesPerDraw !== undefined ? { maxInstancesPerDraw: options.maxInstancesPerDraw } : {}),
|
|
386
|
+
...(options.material?.name !== undefined ? { materialName: options.material.name } : {})
|
|
387
|
+
});
|
|
388
|
+
const base = model(asset, options).toJSON();
|
|
389
|
+
return new AuraNodeBuilder({
|
|
390
|
+
...base,
|
|
391
|
+
instances: built.node.instances.map((transform) => ({
|
|
392
|
+
...(transform.position ? { position: [...transform.position] } : {}),
|
|
393
|
+
...(transform.rotation ? { rotation: [...transform.rotation] } : {}),
|
|
394
|
+
...(transform.scale !== undefined ? { scale: transform.scale } : {})
|
|
395
|
+
})),
|
|
396
|
+
// Safe: the builder only accepts AuraColor strings above, so no RGB tuples reach the node.
|
|
397
|
+
...(built.node.instanceColors ? { instanceColors: [...built.node.instanceColors] } : {}),
|
|
398
|
+
...(built.node.instanceLod ? { instanceLod: built.node.instanceLod } : {}),
|
|
399
|
+
instanceCulling: {
|
|
400
|
+
instanceCount: built.node.instanceCulling.instanceCount,
|
|
401
|
+
centroid: [...built.node.instanceCulling.centroid],
|
|
402
|
+
boundingRadius: built.node.instanceCulling.boundingRadius,
|
|
403
|
+
cullable: true
|
|
404
|
+
},
|
|
405
|
+
...(built.diagnostics.fallbackWarning ? { instancedModelWarning: built.diagnostics.fallbackWarning.diagnostic } : {})
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
export const visualScripting = {
|
|
410
|
+
graph: createVisualScriptingGraphFn,
|
|
411
|
+
attach: attachVisualScriptingGraphFn,
|
|
412
|
+
catalog: listVisualScriptingNodeCatalogFn
|
|
413
|
+
};
|
|
414
|
+
const rootEditorSurface = createRootEditorSurfaceFn();
|
|
415
|
+
export const editor = {
|
|
416
|
+
undo: rootEditorSurface.undo,
|
|
417
|
+
redo: rootEditorSurface.redo,
|
|
418
|
+
gizmo: rootEditorSurface.attachGizmo,
|
|
419
|
+
playMode: { enter: rootEditorSurface.enterPlayMode, exit: rootEditorSurface.exitPlayMode },
|
|
420
|
+
outliner: rootEditorSurface.describeOutliner,
|
|
421
|
+
capabilityLabel: "editor"
|
|
341
422
|
};
|
|
342
423
|
export const geometry = {
|
|
343
424
|
define: defineAuraCustomGeometry,
|
|
344
425
|
custom: (spec, options = {}) => primitive("custom", { ...options, geometry: defineAuraCustomGeometry(spec) })
|
|
345
426
|
};
|
|
427
|
+
let cachedSdfFontAtlas;
|
|
428
|
+
function rootSdfFontAtlas() {
|
|
429
|
+
// Baked once per session (~500ms default res); the caller never bakes per frame.
|
|
430
|
+
cachedSdfFontAtlas ??= createSdfFontAtlas();
|
|
431
|
+
return cachedSdfFontAtlas;
|
|
432
|
+
}
|
|
346
433
|
export function text3D(text, options = {}) {
|
|
347
434
|
const built = createAuraText3DGeometry(text, options);
|
|
348
|
-
|
|
435
|
+
if ((options.backend ?? "extruded-mesh") === "extruded-mesh") {
|
|
436
|
+
return primitive("custom", { ...options, geometry: built.geometry, text3D: { text: built.text, glyphCount: built.glyphCount, unsupportedCharacters: built.unsupportedCharacters, method: built.method } });
|
|
437
|
+
}
|
|
438
|
+
// G1 SDF backend (muse3jsparity-PRD): the layout validates fail-loud and is
|
|
439
|
+
// recorded on the descriptor with the full author intent (size, spacing,
|
|
440
|
+
// style, occlusion policy) so the production bridge can replay the sampler
|
|
441
|
+
// at mount. The extruded mesh stays as the diagnosed fallback geometry.
|
|
442
|
+
const layout = layoutSdfText(text, rootSdfFontAtlas(), { size: options.size ?? 1, letterSpacing: options.letterSpacing, style: options.sdfStyle });
|
|
443
|
+
return primitive("custom", {
|
|
444
|
+
...options,
|
|
445
|
+
geometry: built.geometry,
|
|
446
|
+
text3D: {
|
|
447
|
+
text: built.text,
|
|
448
|
+
glyphCount: built.glyphCount,
|
|
449
|
+
unsupportedCharacters: [...new Set([...built.unsupportedCharacters, ...layout.unsupportedCharacters])],
|
|
450
|
+
method: "sdf-atlas-quad",
|
|
451
|
+
backend: "sdf",
|
|
452
|
+
sdfQuadCount: layout.quads.length,
|
|
453
|
+
sdfWidthWorld: layout.widthWorld,
|
|
454
|
+
sdfHeightWorld: layout.heightWorld,
|
|
455
|
+
sdfSize: options.size ?? 1,
|
|
456
|
+
...(options.letterSpacing === undefined ? {} : { sdfLetterSpacing: options.letterSpacing }),
|
|
457
|
+
...(options.sdfStyle === undefined ? {} : { sdfStyle: options.sdfStyle }),
|
|
458
|
+
sdfOcclusion: options.sdfOcclusion ?? "dim"
|
|
459
|
+
}
|
|
460
|
+
});
|
|
349
461
|
}
|
|
350
462
|
export function distanceLod(options) {
|
|
351
463
|
if (options.levels.length === 0)
|
|
@@ -397,6 +509,13 @@ function proceduralTexture(texture, options = {}) {
|
|
|
397
509
|
colorB: options.colorB
|
|
398
510
|
};
|
|
399
511
|
}
|
|
512
|
+
const PHYSICAL_SPEC_KEYS = [
|
|
513
|
+
"color", "roughness", "metallic", "metalness", "clearcoat", "clearcoatRoughness",
|
|
514
|
+
"sheen", "sheenColor", "sheenRoughness", "iridescence", "iridescenceIOR",
|
|
515
|
+
"iridescenceThicknessRange", "anisotropy", "anisotropyRotation", "transmission",
|
|
516
|
+
"thickness", "attenuationColor", "attenuationDistance", "ior", "specularIntensity",
|
|
517
|
+
"specularColor"
|
|
518
|
+
];
|
|
400
519
|
export const material = {
|
|
401
520
|
pbr: (options = {}) => ({
|
|
402
521
|
color: "#d7dee8",
|
|
@@ -405,7 +524,29 @@ export const material = {
|
|
|
405
524
|
metalness: options.metalness ?? options.metallic ?? 0,
|
|
406
525
|
...options
|
|
407
526
|
}),
|
|
408
|
-
physical: (options = {}) =>
|
|
527
|
+
physical: (options = {}) => {
|
|
528
|
+
// P3 (muse3jsparity-PRD): the sync factory stays scalar (C1 decision —
|
|
529
|
+
// no renderer change); extension params validate through the physical
|
|
530
|
+
// matrix and every non-supported request rides the spec as a warning.
|
|
531
|
+
const defined = PHYSICAL_SPEC_KEYS.reduce((picked, key) => {
|
|
532
|
+
const value = options[key];
|
|
533
|
+
if (value !== undefined)
|
|
534
|
+
picked[key] = value;
|
|
535
|
+
return picked;
|
|
536
|
+
}, {});
|
|
537
|
+
const result = createPhysicalMaterialSpec(defined);
|
|
538
|
+
const normalized = PHYSICAL_SPEC_KEYS.reduce((picked, key) => {
|
|
539
|
+
const value = result.spec[key];
|
|
540
|
+
if (value !== undefined && options[key] !== undefined)
|
|
541
|
+
picked[key] = value;
|
|
542
|
+
return picked;
|
|
543
|
+
}, {});
|
|
544
|
+
return {
|
|
545
|
+
...material.pbr(options),
|
|
546
|
+
...normalized,
|
|
547
|
+
...(result.boundedWarnings.length > 0 ? { physicalWarnings: [...result.boundedWarnings] } : {})
|
|
548
|
+
};
|
|
549
|
+
},
|
|
409
550
|
emissive: (options = {}) => ({
|
|
410
551
|
color: options.color ?? "#111827",
|
|
411
552
|
emissive: options.emissive ?? options.color ?? "#38d6ff",
|
|
@@ -699,33 +840,41 @@ const materialCapabilityCatalog = [
|
|
|
699
840
|
{
|
|
700
841
|
id: "base-color-texture",
|
|
701
842
|
label: "Base-color texture",
|
|
702
|
-
rootSafeApi: "
|
|
843
|
+
rootSafeApi: "supported",
|
|
703
844
|
productionRuntime: "supported",
|
|
704
|
-
evidence: "
|
|
845
|
+
evidence: "Root textured-PBR upgrade resolves AuraAssetRef texture urls to sRGB baseColor textures with controlled texture on/off pixel deltas (tests/browser/root-textured-c1.spec.ts, tests/reports/root-textured-c1/c1-probe.json).",
|
|
705
846
|
claimRule: "Can describe typed texture metadata and rendered textured assets; do not claim full texture-material parity without controlled root pixels."
|
|
706
847
|
},
|
|
707
848
|
{
|
|
708
849
|
id: "metallic-roughness",
|
|
709
850
|
label: "Metallic/roughness",
|
|
710
|
-
rootSafeApi: "
|
|
851
|
+
rootSafeApi: "supported",
|
|
711
852
|
productionRuntime: "supported",
|
|
712
|
-
evidence: "Root
|
|
853
|
+
evidence: "Root textured-PBR upgrade composites roughnessMap/metalnessMap to the glTF metallic-roughness convention with scalar fallbacks and controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts).",
|
|
713
854
|
claimRule: "Do not claim full PBR parity without browser evidence."
|
|
714
855
|
},
|
|
715
856
|
{
|
|
716
857
|
id: "normal-map",
|
|
717
858
|
label: "Normal maps",
|
|
718
|
-
rootSafeApi: "
|
|
859
|
+
rootSafeApi: "supported",
|
|
719
860
|
productionRuntime: "supported",
|
|
720
|
-
evidence: "Root
|
|
861
|
+
evidence: "Root textured-PBR upgrade binds linear normal maps with tangent-space sampling over authored TBN and controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts).",
|
|
862
|
+
claimRule: "Claim only with material-specific screenshot evidence."
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
id: "occlusion-map",
|
|
866
|
+
label: "Occlusion maps",
|
|
867
|
+
rootSafeApi: "supported",
|
|
868
|
+
productionRuntime: "supported",
|
|
869
|
+
evidence: "Root textured-PBR upgrade binds linear occlusionMap textures (R channel) with occlusionStrength and controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts).",
|
|
721
870
|
claimRule: "Claim only with material-specific screenshot evidence."
|
|
722
871
|
},
|
|
723
872
|
{
|
|
724
873
|
id: "emissive",
|
|
725
874
|
label: "Emissive",
|
|
726
|
-
rootSafeApi: "
|
|
875
|
+
rootSafeApi: "supported",
|
|
727
876
|
productionRuntime: "supported",
|
|
728
|
-
evidence: "Root
|
|
877
|
+
evidence: "Root textured-PBR upgrade binds sRGB emissiveMap textures multiplied by the emissive factor with controlled browser pixel proof (tests/browser/root-textured-c1.spec.ts); pixel-backed bloom is reported separately.",
|
|
729
878
|
claimRule: "Distinguish emissive material from postprocess bloom."
|
|
730
879
|
},
|
|
731
880
|
{
|
|
@@ -779,9 +928,9 @@ const materialCapabilityCatalog = [
|
|
|
779
928
|
{
|
|
780
929
|
id: "hdr-ibl",
|
|
781
930
|
label: "HDR/IBL",
|
|
782
|
-
rootSafeApi: "
|
|
931
|
+
rootSafeApi: "supported",
|
|
783
932
|
productionRuntime: "supported",
|
|
784
|
-
evidence: "Root
|
|
933
|
+
evidence: "Root environments.hdri resolves authored Radiance HDR through the HDR-to-cubemap-to-GGX-to-BRDF-LUT chain with iblPixelBacked diagnostics and controlled browser pixel deltas (tests/browser/root-ibl-b3.spec.ts, tests/reports/root-ibl-b3/b3-probe.json).",
|
|
785
934
|
claimRule: "Root HDR/IBL claims require diagnostics and browser pixels."
|
|
786
935
|
},
|
|
787
936
|
{
|
|
@@ -805,7 +954,9 @@ function createMaterialCapabilityDiagnostics(input) {
|
|
|
805
954
|
requested.add("metallic-roughness");
|
|
806
955
|
if (spec.normal)
|
|
807
956
|
requested.add("normal-map");
|
|
808
|
-
if (spec.
|
|
957
|
+
if (spec.occlusionMap || spec.occlusionStrength !== undefined)
|
|
958
|
+
requested.add("occlusion-map");
|
|
959
|
+
if (spec.emissive || spec.emissiveIntensity !== undefined || spec.emissiveMap)
|
|
809
960
|
requested.add("emissive");
|
|
810
961
|
if (spec.opacity !== undefined && spec.opacity < 1)
|
|
811
962
|
requested.add("alpha");
|
|
@@ -829,6 +980,11 @@ function createMaterialCapabilityDiagnostics(input) {
|
|
|
829
980
|
if (partialRequestedFeatures.length > 0) {
|
|
830
981
|
warnings.push(`Partial root material features requested: ${partialRequestedFeatures.join(", ")}. Capture route pixels before claiming production material quality.`);
|
|
831
982
|
}
|
|
983
|
+
for (const spec of specs) {
|
|
984
|
+
for (const physicalWarning of spec.physicalWarnings ?? []) {
|
|
985
|
+
warnings.push(`Physical material extension bounded: ${physicalWarning}`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
832
988
|
if (unsupportedRequestedFeatures.length > 0) {
|
|
833
989
|
warnings.push(`Metadata/internal material features requested: ${unsupportedRequestedFeatures.join(", ")}. Do not claim rendered support from root createAuraApp alone.`);
|
|
834
990
|
}
|
|
@@ -1038,7 +1194,8 @@ export const lights = {
|
|
|
1038
1194
|
name: options.name,
|
|
1039
1195
|
position: options.position ?? [3, 4, 3],
|
|
1040
1196
|
intensity: options.intensity ?? 1.5,
|
|
1041
|
-
color: options.color ?? "#ffffff"
|
|
1197
|
+
color: options.color ?? "#ffffff",
|
|
1198
|
+
shadow: options.shadow
|
|
1042
1199
|
}),
|
|
1043
1200
|
point: (options = {}) => new AuraNodeBuilder({
|
|
1044
1201
|
kind: "light",
|
|
@@ -1048,6 +1205,20 @@ export const lights = {
|
|
|
1048
1205
|
intensity: options.intensity ?? 2,
|
|
1049
1206
|
color: options.color ?? "#ffffff"
|
|
1050
1207
|
}),
|
|
1208
|
+
spot: (options = {}) => new AuraNodeBuilder({
|
|
1209
|
+
kind: "light",
|
|
1210
|
+
light: "spot",
|
|
1211
|
+
name: options.name ?? "spot light",
|
|
1212
|
+
position: options.position ?? [0, 4, 0],
|
|
1213
|
+
target: options.target,
|
|
1214
|
+
angle: options.angle ?? Math.PI / 6,
|
|
1215
|
+
penumbra: options.penumbra ?? 0.4,
|
|
1216
|
+
distance: options.distance ?? 12,
|
|
1217
|
+
decay: options.decay,
|
|
1218
|
+
intensity: options.intensity ?? 8,
|
|
1219
|
+
color: options.color ?? "#ffffff",
|
|
1220
|
+
shadow: options.shadow
|
|
1221
|
+
}),
|
|
1051
1222
|
studio: (options = {}) => new AuraNodeBuilder({
|
|
1052
1223
|
kind: "light",
|
|
1053
1224
|
light: "studio",
|
|
@@ -1100,6 +1271,7 @@ export const lights = {
|
|
|
1100
1271
|
export const camera = {
|
|
1101
1272
|
perspective: (options = {}) => ({
|
|
1102
1273
|
mode: "perspective",
|
|
1274
|
+
...resolveCameraClipping(options),
|
|
1103
1275
|
position: options.position ?? [0, 1.4, 4],
|
|
1104
1276
|
target: options.target ?? [0, 0.8, 0],
|
|
1105
1277
|
fov: options.fov ?? 45
|
|
@@ -1109,6 +1281,7 @@ export const camera = {
|
|
|
1109
1281
|
const target = options.target ?? [0, 0.8, 0];
|
|
1110
1282
|
return {
|
|
1111
1283
|
mode: "orbit",
|
|
1284
|
+
...resolveCameraClipping(options),
|
|
1112
1285
|
distance,
|
|
1113
1286
|
target,
|
|
1114
1287
|
position: options.position ?? [
|
|
@@ -1121,6 +1294,7 @@ export const camera = {
|
|
|
1121
1294
|
},
|
|
1122
1295
|
dolly: (options) => ({
|
|
1123
1296
|
mode: "dolly",
|
|
1297
|
+
...resolveCameraClipping(options),
|
|
1124
1298
|
from: options.from,
|
|
1125
1299
|
to: options.to,
|
|
1126
1300
|
target: options.target ?? [0, 0.8, 0],
|
|
@@ -1130,6 +1304,7 @@ export const camera = {
|
|
|
1130
1304
|
}),
|
|
1131
1305
|
follow: (options) => ({
|
|
1132
1306
|
mode: "follow",
|
|
1307
|
+
...resolveCameraClipping(options),
|
|
1133
1308
|
targetNode: options.targetNode,
|
|
1134
1309
|
distance: options.distance ?? 5,
|
|
1135
1310
|
position: options.position,
|
|
@@ -1145,6 +1320,7 @@ export const camera = {
|
|
|
1145
1320
|
}),
|
|
1146
1321
|
path: (options) => ({
|
|
1147
1322
|
mode: "path",
|
|
1323
|
+
...resolveCameraClipping(options),
|
|
1148
1324
|
from: options.from,
|
|
1149
1325
|
to: options.to,
|
|
1150
1326
|
target: options.target ?? [0, 0.8, 0],
|
|
@@ -1155,6 +1331,7 @@ export const camera = {
|
|
|
1155
1331
|
}),
|
|
1156
1332
|
flythrough: (options = {}) => ({
|
|
1157
1333
|
mode: "flythrough",
|
|
1334
|
+
...resolveCameraClipping(options),
|
|
1158
1335
|
from: options.from ?? [0, 0.36, 1.6],
|
|
1159
1336
|
to: options.to ?? [0, 0.36, -4.4],
|
|
1160
1337
|
target: options.target ?? [0, 0.28, -5.8],
|
|
@@ -1175,6 +1352,7 @@ export const camera = {
|
|
|
1175
1352
|
*/
|
|
1176
1353
|
orthographic: (options = {}) => ({
|
|
1177
1354
|
mode: "orthographic",
|
|
1355
|
+
...resolveCameraClipping(options),
|
|
1178
1356
|
position: options.position ?? [0, 1.4, 4],
|
|
1179
1357
|
target: options.target ?? [0, 0.8, 0],
|
|
1180
1358
|
orthographicSize: options.orthographicSize ?? 1.4
|
|
@@ -1197,6 +1375,7 @@ export const camera = {
|
|
|
1197
1375
|
const horizontal = Math.cos(elevation) * distance;
|
|
1198
1376
|
return {
|
|
1199
1377
|
mode: "isometric",
|
|
1378
|
+
...resolveCameraClipping(options),
|
|
1200
1379
|
target,
|
|
1201
1380
|
distance,
|
|
1202
1381
|
orthographicSize: options.orthographicSize ?? 6,
|
|
@@ -1257,8 +1436,10 @@ export const camera = {
|
|
|
1257
1436
|
solar: () => camera.orbit({ target: [0, 0, 0], distance: 7.2, fov: 46 }),
|
|
1258
1437
|
humanoid: () => camera.perspective({ position: [1.2, 1.12, 3.45], target: [0, 0.78, -0.55], fov: 36 }),
|
|
1259
1438
|
miniGolf: () => camera.follow({ targetNode: "white physics golf ball", distance: 4.2, fov: 48 }),
|
|
1260
|
-
neon: () => camera.flythrough({ from: [0, 0.36, 1.6], to: [0, 0.36, -5.8], target: [0, 0.26, -6.8], fov: 54, captureTime: 0.16 })
|
|
1439
|
+
neon: () => camera.flythrough({ from: [0, 0.36, 1.6], to: [0, 0.36, -5.8], target: [0, 0.26, -6.8], fov: 54, captureTime: 0.16 }),
|
|
1440
|
+
...gameCameraRigs
|
|
1261
1441
|
};
|
|
1442
|
+
export const gameFeel = { create: gameFeelBuilders.create, hitStopDefaults: gameFeelBuilders.hitStopDefaults };
|
|
1262
1443
|
function resolveFrameAssetRenderScale(bounds, options) {
|
|
1263
1444
|
const size = boundsSize(bounds);
|
|
1264
1445
|
const height = Math.max(0.001, boundsHeight(bounds));
|
|
@@ -1314,7 +1495,10 @@ export const effects = {
|
|
|
1314
1495
|
radius: options.radius ?? 0.38,
|
|
1315
1496
|
threshold: options.threshold ?? 0.7,
|
|
1316
1497
|
antiBlowout,
|
|
1317
|
-
maxIntensity
|
|
1498
|
+
maxIntensity,
|
|
1499
|
+
...(options.quality !== undefined ? { quality: options.quality } : {}),
|
|
1500
|
+
...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
|
|
1501
|
+
...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
|
|
1318
1502
|
});
|
|
1319
1503
|
},
|
|
1320
1504
|
cinematicBloom: (options = {}) => effects.bloom({
|
|
@@ -1323,7 +1507,10 @@ export const effects = {
|
|
|
1323
1507
|
radius: options.radius ?? 0.42,
|
|
1324
1508
|
threshold: options.threshold ?? 0.72,
|
|
1325
1509
|
antiBlowout: options.antiBlowout ?? true,
|
|
1326
|
-
maxIntensity: options.maxIntensity ?? 0.92
|
|
1510
|
+
maxIntensity: options.maxIntensity ?? 0.92,
|
|
1511
|
+
...(options.quality !== undefined ? { quality: options.quality } : {}),
|
|
1512
|
+
...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
|
|
1513
|
+
...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
|
|
1327
1514
|
}),
|
|
1328
1515
|
neonBloom: (options = {}) => effects.bloom({
|
|
1329
1516
|
intensity: options.intensity ?? 0.72,
|
|
@@ -1331,12 +1518,27 @@ export const effects = {
|
|
|
1331
1518
|
radius: options.radius ?? 0.48,
|
|
1332
1519
|
threshold: options.threshold ?? 0.68,
|
|
1333
1520
|
antiBlowout: options.antiBlowout ?? true,
|
|
1334
|
-
maxIntensity: options.maxIntensity ?? 0.92
|
|
1521
|
+
maxIntensity: options.maxIntensity ?? 0.92,
|
|
1522
|
+
...(options.quality !== undefined ? { quality: options.quality } : {}),
|
|
1523
|
+
...(options.softKnee !== undefined ? { softKnee: options.softKnee } : {}),
|
|
1524
|
+
...(options.shoulder !== undefined ? { shoulder: options.shoulder } : {})
|
|
1335
1525
|
}),
|
|
1336
|
-
|
|
1526
|
+
/**
|
|
1527
|
+
* A5 volumetric fog (muse3jsparity-PRD): builds a DISTINCT "volumetric-fog"
|
|
1528
|
+
* node (never plain fog) that submits the depth-aware inscatter pass plus
|
|
1529
|
+
* forward GPU height-fog terms. quality "off" keeps forward exp2 fog only.
|
|
1530
|
+
*/
|
|
1531
|
+
volumetricFog: (options = {}) => new AuraNodeBuilder({
|
|
1532
|
+
kind: "effect",
|
|
1533
|
+
effect: "volumetric-fog",
|
|
1534
|
+
name: options.name ?? "volumetric fog inscatter",
|
|
1337
1535
|
density: options.density ?? 0.18,
|
|
1338
1536
|
color: options.color ?? "#6f84b9",
|
|
1339
|
-
intensity: options.intensity ?? 0.7
|
|
1537
|
+
intensity: options.intensity ?? 0.7,
|
|
1538
|
+
...(options.volumetricQuality ? { volumetricQuality: options.volumetricQuality } : {}),
|
|
1539
|
+
...(options.lightPosition ? { lightPosition: options.lightPosition } : {}),
|
|
1540
|
+
...(options.heightFalloff !== undefined ? { heightFalloff: options.heightFalloff } : {}),
|
|
1541
|
+
...(options.heightReference !== undefined ? { heightReference: options.heightReference } : {})
|
|
1340
1542
|
}),
|
|
1341
1543
|
depthFog: (options = {}) => effects.fog({
|
|
1342
1544
|
density: options.density ?? 0.14,
|
|
@@ -1361,6 +1563,82 @@ export const effects = {
|
|
|
1361
1563
|
density: options.density ?? 0.7,
|
|
1362
1564
|
color: options.color ?? "#020617"
|
|
1363
1565
|
}),
|
|
1566
|
+
/**
|
|
1567
|
+
* Root color-grade node (muse3jsparity-PRD A3). contrast/saturation execute
|
|
1568
|
+
* natively; exposure/shadows/highlights/lut are recorded on the node and
|
|
1569
|
+
* warned (no native grade target yet — never silently accepted).
|
|
1570
|
+
*/
|
|
1571
|
+
colorGrade: (options = {}) => new AuraNodeBuilder({
|
|
1572
|
+
kind: "effect",
|
|
1573
|
+
effect: "color-grade",
|
|
1574
|
+
name: options.name ?? "color grade",
|
|
1575
|
+
intensity: options.intensity ?? 1,
|
|
1576
|
+
exposure: options.exposure ?? 1,
|
|
1577
|
+
contrast: options.contrast ?? 1,
|
|
1578
|
+
saturation: options.saturation ?? 1,
|
|
1579
|
+
...(options.shadows !== undefined ? { shadows: options.shadows } : {}),
|
|
1580
|
+
...(options.highlights !== undefined ? { highlights: options.highlights } : {}),
|
|
1581
|
+
...(options.lut !== undefined ? { lut: options.lut } : {})
|
|
1582
|
+
}),
|
|
1583
|
+
/**
|
|
1584
|
+
* Root anti-alias node (muse3jsparity-PRD A3). `fxaa` executes natively;
|
|
1585
|
+
* `off` submits nothing; `taa` uses renderer-owned velocity/history for opaque
|
|
1586
|
+
* rigid triangles. Unsupported deforming/transparent geometry emits a named warning.
|
|
1587
|
+
*/
|
|
1588
|
+
antiAlias: (options = {}) => new AuraNodeBuilder({
|
|
1589
|
+
kind: "effect",
|
|
1590
|
+
effect: "anti-alias",
|
|
1591
|
+
name: options.name ?? "anti alias",
|
|
1592
|
+
mode: options.mode ?? "fxaa",
|
|
1593
|
+
intensity: options.intensity ?? 1
|
|
1594
|
+
}),
|
|
1595
|
+
/**
|
|
1596
|
+
* Root outline node (muse3jsparity-PRD A3): native outline pass, width in
|
|
1597
|
+
* pixels clamped to the device range 1-6 by the bridge.
|
|
1598
|
+
*/
|
|
1599
|
+
outline: (options = {}) => new AuraNodeBuilder({
|
|
1600
|
+
kind: "effect",
|
|
1601
|
+
effect: "outline",
|
|
1602
|
+
name: options.name ?? "outline",
|
|
1603
|
+
color: options.color ?? "#ff9822",
|
|
1604
|
+
width: options.width ?? 3,
|
|
1605
|
+
threshold: options.threshold ?? 0.12,
|
|
1606
|
+
intensity: options.intensity ?? 0.9
|
|
1607
|
+
}),
|
|
1608
|
+
/**
|
|
1609
|
+
* Root screen-space reflections node (muse3jsparity-PRD A3): submits the
|
|
1610
|
+
* native SSR pass against renderer-owned depth where available.
|
|
1611
|
+
*/
|
|
1612
|
+
screenSpaceReflections: (options = {}) => new AuraNodeBuilder({
|
|
1613
|
+
kind: "effect",
|
|
1614
|
+
effect: "screen-space-reflections",
|
|
1615
|
+
name: options.name ?? "screen space reflections",
|
|
1616
|
+
intensity: options.intensity ?? 0.9
|
|
1617
|
+
}),
|
|
1618
|
+
/**
|
|
1619
|
+
* Root depth-of-field node (muse3jsparity-PRD A3). focus is a
|
|
1620
|
+
* linear-distance fraction (0 = near plane, 1 = far plane); aperture widens
|
|
1621
|
+
* the focus transition band (game-tuned control, not a physical f-stop).
|
|
1622
|
+
*/
|
|
1623
|
+
depthOfField: (options = {}) => new AuraNodeBuilder({
|
|
1624
|
+
kind: "effect",
|
|
1625
|
+
effect: "depth-of-field",
|
|
1626
|
+
name: options.name ?? "depth of field",
|
|
1627
|
+
focus: options.focus ?? 0.02,
|
|
1628
|
+
aperture: options.aperture ?? 0.35,
|
|
1629
|
+
maxBlur: options.maxBlur ?? 4,
|
|
1630
|
+
intensity: options.intensity ?? 1
|
|
1631
|
+
}),
|
|
1632
|
+
/**
|
|
1633
|
+
* Root motion-blur node: renderer-owned GPU velocity for opaque rigid geometry.
|
|
1634
|
+
* Unsupported deforming/transparent/instanced geometry emits a named warning.
|
|
1635
|
+
*/
|
|
1636
|
+
motionBlur: (options = {}) => new AuraNodeBuilder({
|
|
1637
|
+
kind: "effect",
|
|
1638
|
+
effect: "motion-blur",
|
|
1639
|
+
name: options.name ?? "motion blur",
|
|
1640
|
+
intensity: options.intensity ?? 0.5
|
|
1641
|
+
}),
|
|
1364
1642
|
rain: (options = {}) => new AuraNodeBuilder({
|
|
1365
1643
|
kind: "effect",
|
|
1366
1644
|
effect: "rain",
|
|
@@ -1373,6 +1651,62 @@ export const effects = {
|
|
|
1373
1651
|
splashes: options.splashes ?? true,
|
|
1374
1652
|
mist: options.mist ?? true
|
|
1375
1653
|
}),
|
|
1654
|
+
snow: (options = {}) => new AuraNodeBuilder({
|
|
1655
|
+
kind: "effect",
|
|
1656
|
+
effect: "snow",
|
|
1657
|
+
intensity: options.intensity ?? 0.4,
|
|
1658
|
+
density: options.density ?? 0.68,
|
|
1659
|
+
color: options.color ?? "#e8f1ff",
|
|
1660
|
+
speed: options.speed ?? 1,
|
|
1661
|
+
wind: options.wind ?? [-0.85, -1.1, -0.22],
|
|
1662
|
+
particleCount: options.particleCount,
|
|
1663
|
+
splashes: options.splashes ?? false,
|
|
1664
|
+
mist: options.mist ?? true
|
|
1665
|
+
}),
|
|
1666
|
+
/**
|
|
1667
|
+
* Flipbook explosion/muzzle-flash sprite sheet (muse3jsparity-PRD D4):
|
|
1668
|
+
* recorded with validated sheet geometry, but withheld — root has no
|
|
1669
|
+
* native sprite-sheet sampler yet, so no pass is submitted.
|
|
1670
|
+
*/
|
|
1671
|
+
flipbook: (options = {}) => {
|
|
1672
|
+
const columns = options.spriteColumns ?? 4;
|
|
1673
|
+
const rows = options.spriteRows ?? 4;
|
|
1674
|
+
resolveFlipbookUv(0, columns, rows);
|
|
1675
|
+
return new AuraNodeBuilder({
|
|
1676
|
+
kind: "effect",
|
|
1677
|
+
effect: "flipbook-sprite",
|
|
1678
|
+
name: options.name ?? "flipbook explosion sprite",
|
|
1679
|
+
intensity: options.intensity ?? 1,
|
|
1680
|
+
color: options.color ?? "#ffb347",
|
|
1681
|
+
spriteColumns: columns,
|
|
1682
|
+
spriteRows: rows,
|
|
1683
|
+
frameRate: options.frameRate ?? 24
|
|
1684
|
+
});
|
|
1685
|
+
},
|
|
1686
|
+
/**
|
|
1687
|
+
* Additive thick light beam / fence strip (muse3jsparity-PRD D4): recorded
|
|
1688
|
+
* with a validated quad-strip descriptor, but withheld — root has no native
|
|
1689
|
+
* beam target yet, so no pass is submitted.
|
|
1690
|
+
*/
|
|
1691
|
+
beam: (options = {}) => {
|
|
1692
|
+
const descriptor = createBeamDescriptor({
|
|
1693
|
+
from: options.from ?? [0, 1, 0],
|
|
1694
|
+
to: options.to ?? [0, 1, -4],
|
|
1695
|
+
...(options.widthWorld !== undefined ? { widthWorld: options.widthWorld } : {}),
|
|
1696
|
+
...(options.segmentCount !== undefined ? { segmentCount: options.segmentCount } : {})
|
|
1697
|
+
});
|
|
1698
|
+
return new AuraNodeBuilder({
|
|
1699
|
+
kind: "effect",
|
|
1700
|
+
effect: "light-beam",
|
|
1701
|
+
name: options.name ?? "additive light beam",
|
|
1702
|
+
intensity: options.intensity ?? 0.9,
|
|
1703
|
+
color: options.color ?? "#9fd8ff",
|
|
1704
|
+
from: [...descriptor.from],
|
|
1705
|
+
to: [...descriptor.to],
|
|
1706
|
+
widthWorld: descriptor.widthWorld,
|
|
1707
|
+
segmentCount: descriptor.segmentCount
|
|
1708
|
+
});
|
|
1709
|
+
},
|
|
1376
1710
|
particles: (options = {}) => new AuraNodeBuilder({
|
|
1377
1711
|
kind: "effect",
|
|
1378
1712
|
effect: "particles",
|
|
@@ -1398,6 +1732,161 @@ export const effects = {
|
|
|
1398
1732
|
noise: options.noise ?? 0.22
|
|
1399
1733
|
})
|
|
1400
1734
|
};
|
|
1735
|
+
/**
|
|
1736
|
+
* D3 atmosphere builders (PRD D3 boxes 1-2). ADDITIVE root surface over the
|
|
1737
|
+
* pure descriptors in `@aura3d/rendering` (`DayNightSky.ts`,
|
|
1738
|
+
* `AtmosphereWetness.ts`, `Weather.ts`).
|
|
1739
|
+
*
|
|
1740
|
+
* Every builder below composes pre-existing node kinds only (primitive,
|
|
1741
|
+
* light, effect); no existing builder is modified. Precipitation pixels in
|
|
1742
|
+
* the default production path come from weather-state-driven primitive
|
|
1743
|
+
* streaks/flakes, because the production bridge does not pixel-back `rain`,
|
|
1744
|
+
* `snow`, or `particles` effect passes (they render only in the safe-basic
|
|
1745
|
+
* fallback and the Canvas2D diagnostic path). The matching effect node is
|
|
1746
|
+
* still declared so diagnostics report the request.
|
|
1747
|
+
*/
|
|
1748
|
+
export const sky = {
|
|
1749
|
+
/** Time-of-day sky: background key, sun/moon discs, stars, 2D-noise clouds, key light. */
|
|
1750
|
+
dayNight: (options = {}) => {
|
|
1751
|
+
const state = createDayNightSky(options);
|
|
1752
|
+
const nodes = [];
|
|
1753
|
+
const sunUp = state.sun.elevationRadians > -0.05;
|
|
1754
|
+
const moonUp = state.moon.elevationRadians > -0.05;
|
|
1755
|
+
if (sunUp) {
|
|
1756
|
+
nodes.push(primitives.sphere({ name: "d3 sun disc", material: material.emissive({ color: state.sun.color, emissive: state.sun.color, emissiveIntensity: 2.2, roughness: 0.8 }) })
|
|
1757
|
+
.position(-6 * Math.cos(state.sun.azimuthRadians), 1 + 5 * Math.sin(state.sun.azimuthRadians), -7).scale(0.85).toJSON());
|
|
1758
|
+
}
|
|
1759
|
+
if (moonUp) {
|
|
1760
|
+
nodes.push(primitives.sphere({ name: "d3 moon disc", material: material.emissive({ color: state.moon.color, emissive: state.moon.color, emissiveIntensity: 1.4, roughness: 0.8 }) })
|
|
1761
|
+
.position(-6 * Math.cos(state.moon.azimuthRadians), 1 + 5 * Math.sin(state.moon.azimuthRadians), -7).scale(0.6).toJSON());
|
|
1762
|
+
}
|
|
1763
|
+
const starLimit = Math.max(0, Math.min(120, options.starLimit ?? 48));
|
|
1764
|
+
const visibleStars = state.stars.filter((star) => star.brightness > 0.05 && star.y > 0).slice(0, starLimit);
|
|
1765
|
+
for (const [index, star] of visibleStars.entries()) {
|
|
1766
|
+
nodes.push(primitives.sphere({ name: `d3 night star ${index}`, material: material.emissive({ color: "#dbeafe", emissive: "#bfdbfe", emissiveIntensity: 1.8, roughness: 0.9 }) })
|
|
1767
|
+
.position(star.x * 7, star.y * 4 + 0.6, -6.5).scale(0.028 + star.size * 2).toJSON());
|
|
1768
|
+
}
|
|
1769
|
+
const cloudLimit = Math.max(0, Math.min(48, options.cloudLimit ?? 12));
|
|
1770
|
+
const cloudTint = state.dayFactor > 0.5 ? "#f1f5f9" : state.dayFactor > 0.2 ? "#b6a6a6" : "#1e293b";
|
|
1771
|
+
for (const [index, cell] of state.clouds.slice(0, cloudLimit).entries()) {
|
|
1772
|
+
nodes.push(primitives.sphere({ name: `d3 noise cloud ${index}`, material: material.pbr({ color: cloudTint, roughness: 1, metallic: 0 }) })
|
|
1773
|
+
.position(cell.x * 6, cell.y * 3 + 1.4, -5.5).scale([cell.radius * 1.7, cell.radius * 0.5, cell.radius * 0.8]).toJSON());
|
|
1774
|
+
}
|
|
1775
|
+
nodes.push(lights.directional({
|
|
1776
|
+
name: sunUp ? "d3 sun key light" : "d3 moon key light",
|
|
1777
|
+
position: sunUp ? [-4, 5, 2] : [3, 4, -1],
|
|
1778
|
+
color: sunUp ? state.sun.color : state.moon.color,
|
|
1779
|
+
intensity: sunUp ? 0.6 + state.dayFactor * 1.2 : 0.35
|
|
1780
|
+
}).toJSON());
|
|
1781
|
+
return { nodes, background: state.dayFactor > 0.5 ? state.horizonColor : state.nightFactor > 0.6 ? "#020617" : state.horizonColor, dayFactor: state.dayFactor, visibleStarCount: visibleStars.length };
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
export const weather = {
|
|
1785
|
+
/** Rain/snow declaration + weather-state-driven primitive streaks/flakes for production-path pixels. */
|
|
1786
|
+
precipitation: (options) => {
|
|
1787
|
+
const state = createWeatherState({
|
|
1788
|
+
type: options.type,
|
|
1789
|
+
seed: options.seed ?? 0xd3e7,
|
|
1790
|
+
elapsedSeconds: options.elapsedSeconds ?? 1.2,
|
|
1791
|
+
maxVisualDrops: 400
|
|
1792
|
+
});
|
|
1793
|
+
const snowing = options.type === "snow";
|
|
1794
|
+
const intensity = snowing ? Math.max(0.2, state.snowIntensity) : Math.max(0.2, state.rainIntensity);
|
|
1795
|
+
const nodes = [
|
|
1796
|
+
(snowing ? effects.snow({ intensity, color: "#e8f1ff" }) : effects.rain({ intensity, color: "#bcd7ff" })).toJSON()
|
|
1797
|
+
];
|
|
1798
|
+
const limit = Math.max(0, Math.min(160, options.streakLimit ?? (snowing ? 70 : 90)));
|
|
1799
|
+
const drops = state.visualDrops.slice(0, limit);
|
|
1800
|
+
drops.forEach((drop, index) => {
|
|
1801
|
+
const x = Math.max(-3.4, Math.min(3.4, drop.x * 2.4));
|
|
1802
|
+
const y = Math.max(0.15, Math.min(3.4, drop.y + 1.6));
|
|
1803
|
+
const z = Math.max(-4.5, Math.min(2.5, drop.z * 2.4));
|
|
1804
|
+
if (snowing) {
|
|
1805
|
+
nodes.push(primitives.sphere({ name: `d3 snow flake ${index}`, material: material.pbr({ color: "#eef4ff", roughness: 0.85, metallic: 0 }) })
|
|
1806
|
+
.position(x, y, z).scale(0.035).toJSON());
|
|
1807
|
+
}
|
|
1808
|
+
else {
|
|
1809
|
+
nodes.push(primitives.box({ name: `d3 rain streak ${index}`, material: material.pbr({ color: "#bcd7ff", roughness: 0.35, metallic: 0 }) })
|
|
1810
|
+
.position(x, y, z).scale([0.014, Math.max(0.08, drop.length * 2.4), 0.014]).toJSON());
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
return { nodes, dropCount: drops.length, wetness: state.wetness };
|
|
1814
|
+
},
|
|
1815
|
+
/** Ground slab with wetness darkening + puddle discs mapped from WeatherPuddlePatch. */
|
|
1816
|
+
wetGround: (options = {}) => {
|
|
1817
|
+
const probe = describeWetMaterial({
|
|
1818
|
+
type: options.type ?? "rain",
|
|
1819
|
+
dryColor: options.dryColor ?? "#5b6b4f",
|
|
1820
|
+
dryRoughness: options.dryRoughness ?? 0.9,
|
|
1821
|
+
seed: options.seed ?? 0xd3e7
|
|
1822
|
+
});
|
|
1823
|
+
const size = Math.max(2, Math.min(14, options.size ?? 8));
|
|
1824
|
+
const nodes = [
|
|
1825
|
+
primitives.box({ name: "d3 weather ground slab", material: material.pbr({ color: probe.response.albedoColor, roughness: probe.response.roughness, metallic: 0 }) })
|
|
1826
|
+
.position(0, -0.02, 0).scale([size, 0.04, size]).toJSON()
|
|
1827
|
+
];
|
|
1828
|
+
probe.weather.puddlePatches.forEach((patch, index) => {
|
|
1829
|
+
nodes.push(primitives.cylinder({ name: `d3 puddle disc ${index}`, material: material.pbr({ color: "#16283a", roughness: 0.05, metallic: 0.1 }) })
|
|
1830
|
+
.position(patch.x * 2.4, 0.005, patch.z * 2.4).scale([Math.max(0.06, patch.radius * 3), 0.006, Math.max(0.06, patch.radius * 3)]).toJSON());
|
|
1831
|
+
});
|
|
1832
|
+
return {
|
|
1833
|
+
nodes,
|
|
1834
|
+
wetness: probe.response.wetness,
|
|
1835
|
+
albedoColor: probe.response.albedoColor,
|
|
1836
|
+
roughness: probe.response.roughness,
|
|
1837
|
+
puddleCount: probe.weather.puddlePatches.length
|
|
1838
|
+
};
|
|
1839
|
+
},
|
|
1840
|
+
/** Lightning-flash light hook: intensity is 0 unless type is thunderstorm. */
|
|
1841
|
+
lightning: (options = { type: "thunderstorm" }) => {
|
|
1842
|
+
const probe = describeWetMaterial({ type: options.type, elapsedSeconds: options.elapsedSeconds ?? 0.4, seed: options.seed ?? 0xd3e7 });
|
|
1843
|
+
return {
|
|
1844
|
+
nodes: probe.flash.intensity > 0
|
|
1845
|
+
? [lights.directional({ name: "d3 lightning flash", color: "#dbeafe", intensity: 1 + probe.flash.intensity * 4, position: [2, 6, -3] }).toJSON()]
|
|
1846
|
+
: [],
|
|
1847
|
+
intensity: probe.flash.intensity
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
/**
|
|
1852
|
+
* D3 water surface builder (PRD D3 box 3). ADDITIVE root surface over
|
|
1853
|
+
* `createWaterSurface` in `@aura3d/rendering`.
|
|
1854
|
+
*
|
|
1855
|
+
* Rendered material = layered opaque depth-tinted bands (bounded refraction
|
|
1856
|
+
* look) + fresnel-baked sky tint + white shore-foam discs mapped from
|
|
1857
|
+
* `OceanFoamPatch` + boat + fading wake trail. Buoyancy queries stay on the
|
|
1858
|
+
* fixture (`sampleOceanFixture().buoyancy`); this builder creates no planar
|
|
1859
|
+
* reflection/refraction targets (B4 dependency, see
|
|
1860
|
+
* WATER_SURFACE_PLANAR_DEPENDENCY).
|
|
1861
|
+
*/
|
|
1862
|
+
export const water = {
|
|
1863
|
+
surface: (options = {}) => {
|
|
1864
|
+
const state = createWaterSurface({ preset: options.preset, seed: options.seed, boat: options.boat });
|
|
1865
|
+
const nodes = [];
|
|
1866
|
+
const bandSpan = 4 / state.bands.length;
|
|
1867
|
+
state.bands.forEach((band, index) => {
|
|
1868
|
+
nodes.push(primitives.box({ name: `d3 water depth band ${index}`, material: material.pbr({ color: band.color, roughness: 0.12, metallic: 0.05 }) })
|
|
1869
|
+
.position(0, 0.02 + index * 0.0012, -4.5 + bandSpan * (index + 0.5)).scale([9, 0.024, bandSpan + 0.01]).toJSON());
|
|
1870
|
+
});
|
|
1871
|
+
nodes.push(primitives.box({ name: "d3 shoreline sand", material: material.pbr({ color: "#cbb37f", roughness: 0.95, metallic: 0 }) })
|
|
1872
|
+
.position(0, 0, 0.9).scale([9, 0.03, 2.6]).toJSON());
|
|
1873
|
+
for (const [index, patch] of state.foam.entries()) {
|
|
1874
|
+
nodes.push(primitives.sphere({ name: `d3 shore foam ${index}`, material: material.pbr({ color: "#f4fafd", roughness: 0.55, metallic: 0 }) })
|
|
1875
|
+
.position(Math.max(-4.2, Math.min(4.2, patch.x * 3)), 0.045, -0.42 + (index % 3) * 0.12).scale([Math.max(0.08, patch.radius * 6), 0.02, Math.max(0.05, patch.radius * 3)]).toJSON());
|
|
1876
|
+
}
|
|
1877
|
+
if (options.withBoatHull !== false && options.boat) {
|
|
1878
|
+
nodes.push(primitives.box({ name: "d3 wake boat hull", material: material.pbr({ color: "#7c3f21", roughness: 0.7, metallic: 0 }) })
|
|
1879
|
+
.position(options.boat.x ?? 0, 0.12, options.boat.z ?? -2).scale([0.34, 0.16, 0.9]).toJSON());
|
|
1880
|
+
}
|
|
1881
|
+
state.wake.forEach((segment, index) => {
|
|
1882
|
+
nodes.push(primitives.box({ name: `d3 boat wake ${index}`, material: material.pbr({ color: "#cfeaf7", roughness: 0.3, metallic: 0 }) })
|
|
1883
|
+
.position(segment.x * 2, 0.038, segment.z - 1.2).scale([segment.width, 0.012, 0.3]).toJSON());
|
|
1884
|
+
});
|
|
1885
|
+
return { nodes, bandCount: state.bands.length, foamCount: state.foam.length, wakeActive: state.wakeActive, wakeSegmentCount: state.wake.length };
|
|
1886
|
+
},
|
|
1887
|
+
/** Fixture-side buoyancy telemetry passthrough (no physics implemented here). */
|
|
1888
|
+
buoyancy: (options = {}) => sampleOceanFixture({ preset: options.preset ?? "moderate", seed: options.seed ?? 0xaa7e5 }).buoyancy
|
|
1889
|
+
};
|
|
1401
1890
|
export const interactions = {
|
|
1402
1891
|
orbit: (options = {}) => new AuraNodeBuilder({
|
|
1403
1892
|
kind: "interaction",
|
|
@@ -1648,7 +2137,24 @@ export const environments = {
|
|
|
1648
2137
|
if (materialClass === "product")
|
|
1649
2138
|
return environments.productHero();
|
|
1650
2139
|
return environments.studio();
|
|
1651
|
-
}
|
|
2140
|
+
},
|
|
2141
|
+
/**
|
|
2142
|
+
* B3 root HDRI environment (muse3jsparity-PRD). The `.hdr` asset resolves
|
|
2143
|
+
* post-mount: first frames render the honest studio procedural fallback,
|
|
2144
|
+
* then the HDR→cubemap→GGX-prefilter→BRDF-LUT chain swaps in and
|
|
2145
|
+
* `iblPixelBacked` flips true in diagnostics. Fetch/parse failures keep the
|
|
2146
|
+
* fallback and warn — never a black scene, never a silent swap.
|
|
2147
|
+
*/
|
|
2148
|
+
hdri: (options) => new AuraNodeBuilder({
|
|
2149
|
+
kind: "environment",
|
|
2150
|
+
environment: "hdri",
|
|
2151
|
+
name: options.name ?? "hdri ibl environment",
|
|
2152
|
+
intensity: options.intensity ?? 1,
|
|
2153
|
+
texture: options.texture,
|
|
2154
|
+
...(options.reflectionTexture ? { reflectionTexture: options.reflectionTexture } : {}),
|
|
2155
|
+
...(options.color ? { color: options.color } : {}),
|
|
2156
|
+
...(options.rotation !== undefined ? { rotation: options.rotation } : {})
|
|
2157
|
+
})
|
|
1652
2158
|
};
|
|
1653
2159
|
const rendererColorManagementPreset = {
|
|
1654
2160
|
kind: "aura-renderer-color-management",
|
|
@@ -1774,6 +2280,7 @@ function normalizeCreateAppRendererOptions(options) {
|
|
|
1774
2280
|
mode,
|
|
1775
2281
|
fallback: options?.fallback ?? "safe-basic",
|
|
1776
2282
|
qualityProfile: profile.id,
|
|
2283
|
+
textureBudgetBytes: normalizeTextureBudgetBytes(options?.textureBudgetBytes),
|
|
1777
2284
|
profile
|
|
1778
2285
|
};
|
|
1779
2286
|
}
|
|
@@ -1817,17 +2324,42 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1817
2324
|
const sceneCategory = resolveRendererSceneCategory(snapshot, names);
|
|
1818
2325
|
const exposure = sceneExposurePresets[sceneCategory];
|
|
1819
2326
|
const bloom = flattened.find((node) => node.kind === "effect" && node.effect === "bloom");
|
|
1820
|
-
const fog = flattened.find((node) => node.kind === "effect" && node.effect === "fog");
|
|
2327
|
+
const fog = flattened.find((node) => node.kind === "effect" && (node.effect === "fog" || node.effect === "volumetric-fog"));
|
|
1821
2328
|
const environment = flattened.find((node) => node.kind === "environment");
|
|
1822
2329
|
const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
|
|
1823
2330
|
const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
|
|
1824
2331
|
const contactOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "contact-occlusion") || contactShadows > 0;
|
|
2332
|
+
// Authored temporal intent is separate from device-observed execution.
|
|
2333
|
+
// Missing submissions remain visible without claiming an unmounted scene rendered.
|
|
2334
|
+
const colorGradeNode = flattened.find((node) => node.kind === "effect" && node.effect === "color-grade");
|
|
2335
|
+
const antiAliasNode = flattened.find((node) => node.kind === "effect" && node.effect === "anti-alias");
|
|
2336
|
+
const outlineNode = flattened.find((node) => node.kind === "effect" && node.effect === "outline");
|
|
2337
|
+
const ssrNode = flattened.find((node) => node.kind === "effect" && node.effect === "screen-space-reflections");
|
|
2338
|
+
const dofNode = flattened.find((node) => node.kind === "effect" && node.effect === "depth-of-field");
|
|
2339
|
+
const motionBlurNode = flattened.find((node) => node.kind === "effect" && node.effect === "motion-blur");
|
|
2340
|
+
const flipbookNode = flattened.find((node) => node.kind === "effect" && node.effect === "flipbook-sprite");
|
|
2341
|
+
const beamNode = flattened.find((node) => node.kind === "effect" && node.effect === "light-beam");
|
|
2342
|
+
const sdfTextRequested = flattened.some((node) => node.kind === "primitive" && node.text3D?.backend === "sdf");
|
|
2343
|
+
const fxaaRequested = (antiAliasNode?.mode ?? "fxaa") === "fxaa" && Boolean(antiAliasNode);
|
|
2344
|
+
const taaRequested = (antiAliasNode?.mode ?? "fxaa") === "taa";
|
|
2345
|
+
const taaWithheld = taaRequested && Boolean(runtime?.mounted) && !(runtime?.postprocess?.actualPasses ?? []).includes("taa");
|
|
2346
|
+
const motionBlurWithheld = Boolean(motionBlurNode) && Boolean(runtime?.mounted) && !(runtime?.postprocess?.actualPasses ?? []).includes("motion-blur");
|
|
1825
2347
|
const runtimePostprocess = runtime?.postprocess;
|
|
1826
2348
|
const runtimePasses = runtimePostprocess?.actualPasses ?? [];
|
|
1827
|
-
const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion
|
|
2349
|
+
const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion
|
|
2350
|
+
|| Boolean(colorGradeNode) || Boolean(antiAliasNode) || Boolean(outlineNode)
|
|
2351
|
+
|| Boolean(ssrNode) || Boolean(dofNode) || Boolean(motionBlurNode) || runtimePasses.length > 0;
|
|
1828
2352
|
const requestedPasses = runtimePasses.length > 0
|
|
1829
2353
|
? runtimePasses
|
|
1830
|
-
: requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion
|
|
2354
|
+
: requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion, {
|
|
2355
|
+
colorGrade: Boolean(colorGradeNode),
|
|
2356
|
+
fxaa: fxaaRequested,
|
|
2357
|
+
outline: Boolean(outlineNode),
|
|
2358
|
+
ssr: Boolean(ssrNode),
|
|
2359
|
+
depthOfField: Boolean(dofNode),
|
|
2360
|
+
motionBlurWithheld: Boolean(motionBlurNode),
|
|
2361
|
+
taaWithheld: taaRequested
|
|
2362
|
+
});
|
|
1831
2363
|
const runtimeStatus = !postprocessRequested
|
|
1832
2364
|
? "disabled"
|
|
1833
2365
|
: runtime?.mounted
|
|
@@ -1842,6 +2374,28 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1842
2374
|
warnings.push("scene has no contact shadow or contact-occlusion grounding cue");
|
|
1843
2375
|
if (bloom && (bloom.intensity ?? 0) > 0.95 && bloom.antiBlowout !== true)
|
|
1844
2376
|
warnings.push("bloom is high without anti-blowout safeguards");
|
|
2377
|
+
if (motionBlurWithheld)
|
|
2378
|
+
warnings.push("TEMPORAL_INPUTS_UNAVAILABLE: motion-blur has no native submission");
|
|
2379
|
+
if (flipbookNode)
|
|
2380
|
+
warnings.push("flipbook-sprite is recorded but withheld: root has no native sprite-sheet sampler yet, so no flipbook pass is submitted");
|
|
2381
|
+
if (beamNode)
|
|
2382
|
+
warnings.push("light-beam is recorded but withheld: root has no native beam target yet, so no beam pass is submitted");
|
|
2383
|
+
// G1: the baked-SDF sampler mounts synchronously on the production bridge.
|
|
2384
|
+
// Warn only when the SDF request cannot be pixel-backed (non-production
|
|
2385
|
+
// mount, or the sampler fell back to the extruded mesh at runtime).
|
|
2386
|
+
if (sdfTextRequested && (!runtime?.mounted || !(runtime.text?.textPixelBacked ?? false))) {
|
|
2387
|
+
warnings.push(runtime?.mounted
|
|
2388
|
+
? `text3D sdf backend fell back to the extruded mesh (${runtime.text?.reason ?? "sampler failed"}); textPixelBacked stays false`
|
|
2389
|
+
: "text3D sdf backend is recorded but unmounted: the production bridge samples the SDF atlas at mount, so textPixelBacked stays false until render");
|
|
2390
|
+
}
|
|
2391
|
+
if (taaWithheld)
|
|
2392
|
+
warnings.push("TEMPORAL_INPUTS_UNAVAILABLE: taa has no native submission");
|
|
2393
|
+
if (colorGradeNode && (colorGradeNode.exposure ?? 1) !== 1)
|
|
2394
|
+
warnings.push("color-grade exposure is recorded but has no native grade target yet; contrast/saturation execute");
|
|
2395
|
+
if (colorGradeNode && (colorGradeNode.shadows !== undefined || colorGradeNode.highlights !== undefined))
|
|
2396
|
+
warnings.push("color-grade shadows/highlights are recorded but have no native grade target yet; contrast/saturation execute");
|
|
2397
|
+
if (colorGradeNode?.lut !== undefined)
|
|
2398
|
+
warnings.push("color-grade lut is recorded but LUT samplers are not bound yet; the lut is ignored");
|
|
1845
2399
|
if (postprocessRequested && !runtime?.mounted)
|
|
1846
2400
|
warnings.push("renderer diagnostics are a scene plan only; call createAuraApp(...).diagnostics() after the first render for pixel-backed pass status");
|
|
1847
2401
|
if (postprocessRequested && runtime?.mounted && !runtimePostprocess?.pixelBacked)
|
|
@@ -1891,7 +2445,8 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1891
2445
|
intensity: bloom?.intensity ?? 0,
|
|
1892
2446
|
threshold: bloom?.threshold ?? 0,
|
|
1893
2447
|
radius: bloom?.radius ?? 0,
|
|
1894
|
-
antiBlowout: bloom?.antiBlowout ?? true
|
|
2448
|
+
antiBlowout: bloom?.antiBlowout ?? true,
|
|
2449
|
+
...(bloom?.quality !== undefined ? { quality: bloom.quality } : {})
|
|
1895
2450
|
},
|
|
1896
2451
|
shadows: {
|
|
1897
2452
|
// Never a source-authored `true`. When the runtime is mounted this is the
|
|
@@ -1905,8 +2460,10 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1905
2460
|
label: runtime?.shadow?.label,
|
|
1906
2461
|
nativeShadowMapBindings: runtime?.shadow?.nativeShadowMapBindings ?? 0,
|
|
1907
2462
|
shadowRenderTargetsAllocated: runtime?.shadow?.shadowRenderTargetsAllocated ?? 0,
|
|
2463
|
+
observed: runtime?.mounted ? runtime.shadow?.observed ?? null : null,
|
|
1908
2464
|
contactShadows,
|
|
1909
|
-
mapType: "pcf-soft"
|
|
2465
|
+
mapType: "pcf-soft",
|
|
2466
|
+
...(runtime?.shadow?.spot === undefined ? {} : { spot: runtime.shadow.spot })
|
|
1910
2467
|
},
|
|
1911
2468
|
occlusion: {
|
|
1912
2469
|
enabled: ambientOcclusion || contactOcclusion,
|
|
@@ -1921,7 +2478,9 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1921
2478
|
fog: {
|
|
1922
2479
|
enabled: Boolean(fog),
|
|
1923
2480
|
density: fog?.density ?? 0,
|
|
1924
|
-
|
|
2481
|
+
// A5: a volumetric-fog node labels "volumetric" because it submits the
|
|
2482
|
+
// distinct inscatter pass; plain fog keeps the legacy intensity label.
|
|
2483
|
+
preset: fog ? (fog.effect === "volumetric-fog" || (fog.intensity ?? 0) > 0.65 ? "volumetric" : "depth") : "none"
|
|
1925
2484
|
},
|
|
1926
2485
|
postprocess: {
|
|
1927
2486
|
enabled: runtimePostprocess?.pixelBacked ?? false,
|
|
@@ -1937,6 +2496,7 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1937
2496
|
actualPasses: runtimePostprocess?.actualPasses ?? [],
|
|
1938
2497
|
fallbackPasses: runtimePostprocess?.fallbackPasses ?? [],
|
|
1939
2498
|
targetFormat: runtimePostprocess?.targetFormat,
|
|
2499
|
+
executionMode: runtimePostprocess?.executionMode ?? "unknown",
|
|
1940
2500
|
evidence: postprocessEvidence
|
|
1941
2501
|
},
|
|
1942
2502
|
runtime: {
|
|
@@ -1946,17 +2506,45 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1946
2506
|
passNames: runtimePostprocess?.actualPasses ?? [],
|
|
1947
2507
|
warnings: runtime?.warnings ?? [],
|
|
1948
2508
|
nativeInstancedSubmissions: runtime?.deviceDiagnostics?.nativeInstancedSubmissions ?? 0,
|
|
2509
|
+
nativeTemporalPasses: runtime?.deviceDiagnostics?.nativeTemporalPasses ?? 0,
|
|
2510
|
+
nativeTemporalBindings: runtime?.deviceDiagnostics?.nativeTemporalBindings ?? 0,
|
|
1949
2511
|
submittedObjects: runtime?.deviceDiagnostics?.submittedObjects ?? 0,
|
|
1950
2512
|
visibleObjects: runtime?.deviceDiagnostics?.visibleObjects ?? 0,
|
|
1951
2513
|
culledObjects: runtime?.deviceDiagnostics?.culledObjects ?? 0,
|
|
1952
2514
|
frustumTestedObjects: runtime?.deviceDiagnostics?.frustumTestedObjects ?? 0,
|
|
1953
|
-
|
|
2515
|
+
bloom: runtime?.deviceDiagnostics?.bloom ?? null,
|
|
2516
|
+
samplerAnisotropyUploads: runtime?.deviceDiagnostics?.samplerAnisotropyUploads ?? 0,
|
|
2517
|
+
maxTextureAnisotropy: runtime?.deviceDiagnostics?.maxTextureAnisotropy ?? 1,
|
|
2518
|
+
lodSelections: runtime?.lodSelections ?? [],
|
|
2519
|
+
texturedMaterials: runtime?.texturedMaterials ?? []
|
|
1954
2520
|
},
|
|
1955
2521
|
environment: {
|
|
1956
2522
|
enabled: environmentStatus.enabled,
|
|
1957
2523
|
preset: environmentStatus.preset,
|
|
1958
2524
|
intensity: environmentStatus.intensity,
|
|
1959
|
-
evidence: environmentStatus.evidence
|
|
2525
|
+
evidence: environmentStatus.evidence,
|
|
2526
|
+
// B3: pass through the HDRI upgrade observation; the unmounted default
|
|
2527
|
+
// reports not-pixel-backed so static diagnostics never claim HDRI proof.
|
|
2528
|
+
iblPixelBacked: environmentStatus.iblPixelBacked ?? false,
|
|
2529
|
+
hdriStatus: environmentStatus.hdriStatus ?? "none",
|
|
2530
|
+
...(environmentStatus.dualProbe === undefined ? {} : { dualProbe: environmentStatus.dualProbe }),
|
|
2531
|
+
...(environmentStatus.hdriRotation === undefined ? {} : { hdriRotation: environmentStatus.hdriRotation })
|
|
2532
|
+
},
|
|
2533
|
+
text: runtime?.text ?? {
|
|
2534
|
+
sdfTexts: flattened.filter((node) => node.kind === "primitive" && node.text3D?.backend === "sdf").length,
|
|
2535
|
+
textPixelBacked: false,
|
|
2536
|
+
quadCount: 0,
|
|
2537
|
+
lastOpacity: 1,
|
|
2538
|
+
reason: "SDF text requested in the scene graph but no runtime has mounted yet"
|
|
2539
|
+
},
|
|
2540
|
+
textures: runtime?.textures ?? {
|
|
2541
|
+
budgetBytes: normalizeTextureBudgetBytes(rendererOptions?.textureBudgetBytes),
|
|
2542
|
+
usedBytes: 0,
|
|
2543
|
+
requestedBytes: 0,
|
|
2544
|
+
overBudget: false,
|
|
2545
|
+
overBudgetBytes: 0,
|
|
2546
|
+
residentEntries: 0,
|
|
2547
|
+
evictedEntries: []
|
|
1960
2548
|
},
|
|
1961
2549
|
antialiasing: rendererQualityPresets.screenshot.antialiasing,
|
|
1962
2550
|
screenshotQuality: rendererQualityPresets.screenshot,
|
|
@@ -1964,14 +2552,31 @@ function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
|
1964
2552
|
warnings
|
|
1965
2553
|
};
|
|
1966
2554
|
}
|
|
1967
|
-
function requestedRendererPostProcessPasses(hasBloom, ambientOcclusion, contactOcclusion) {
|
|
1968
|
-
if (!hasBloom && !ambientOcclusion && !contactOcclusion
|
|
2555
|
+
function requestedRendererPostProcessPasses(hasBloom, ambientOcclusion, contactOcclusion, extra = {}) {
|
|
2556
|
+
if (!hasBloom && !ambientOcclusion && !contactOcclusion && !extra.colorGrade && !extra.fxaa
|
|
2557
|
+
&& !extra.outline && !extra.ssr && !extra.depthOfField && !extra.motionBlurWithheld && !extra.taaWithheld)
|
|
1969
2558
|
return [];
|
|
1970
2559
|
const passes = ["render"];
|
|
1971
2560
|
if (ambientOcclusion || contactOcclusion)
|
|
1972
2561
|
passes.push("ssao");
|
|
1973
2562
|
if (hasBloom)
|
|
1974
2563
|
passes.push("bloom");
|
|
2564
|
+
if (extra.colorGrade)
|
|
2565
|
+
passes.push("color-grade");
|
|
2566
|
+
if (extra.ssr)
|
|
2567
|
+
passes.push("ssr");
|
|
2568
|
+
if (extra.depthOfField)
|
|
2569
|
+
passes.push("depth-of-field");
|
|
2570
|
+
if (extra.outline)
|
|
2571
|
+
passes.push("outline");
|
|
2572
|
+
if (extra.fxaa)
|
|
2573
|
+
passes.push("fxaa");
|
|
2574
|
+
// Withheld intents stay visible in the request list with an explicit suffix
|
|
2575
|
+
// so requested-vs-actual never silently agrees on an unexecuted pass.
|
|
2576
|
+
if (extra.motionBlurWithheld)
|
|
2577
|
+
passes.push("motion-blur");
|
|
2578
|
+
if (extra.taaWithheld)
|
|
2579
|
+
passes.push("taa");
|
|
1975
2580
|
passes.push("output");
|
|
1976
2581
|
return passes;
|
|
1977
2582
|
}
|
|
@@ -2032,6 +2637,7 @@ export class AuraSceneBuilder {
|
|
|
2032
2637
|
return this;
|
|
2033
2638
|
}
|
|
2034
2639
|
camera(next) {
|
|
2640
|
+
resolveCameraClipping(next);
|
|
2035
2641
|
this.cameraSpec = next;
|
|
2036
2642
|
return this;
|
|
2037
2643
|
}
|
|
@@ -6384,6 +6990,16 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
|
|
|
6384
6990
|
renderItemCount: input.renderItemCount ?? 0,
|
|
6385
6991
|
skinnedRenderItemCount: input.skinnedRenderItemCount ?? 0,
|
|
6386
6992
|
morphRenderItemCount: input.morphRenderItemCount ?? 0,
|
|
6993
|
+
...(input.lastMaterialTracksApplied !== undefined ? { lastMaterialTracksApplied: input.lastMaterialTracksApplied } : {}),
|
|
6994
|
+
...(input.lastLightTracksApplied !== undefined ? { lastLightTracksApplied: input.lastLightTracksApplied } : {}),
|
|
6995
|
+
...(input.lastFootPlantingGroundedFeet !== undefined ? { lastFootPlantingGroundedFeet: input.lastFootPlantingGroundedFeet } : {}),
|
|
6996
|
+
...(input.lastFootPlantingTargetError !== undefined ? { lastFootPlantingTargetError: input.lastFootPlantingTargetError } : {}),
|
|
6997
|
+
...(input.lastFootPlantingHipOffset !== undefined ? { lastFootPlantingHipOffset: input.lastFootPlantingHipOffset } : {}),
|
|
6998
|
+
...(input.lastFootPlantingMissingLegs !== undefined ? { lastFootPlantingMissingLegs: [...input.lastFootPlantingMissingLegs] } : {}),
|
|
6999
|
+
...(input.lastFootPlantingDeformation !== undefined ? { lastFootPlantingDeformation: input.lastFootPlantingDeformation.map(leg => ({ ...leg })) } : {}),
|
|
7000
|
+
...(input.lastFootPlantingSurfaces !== undefined ? { lastFootPlantingSurfaces: input.lastFootPlantingSurfaces.map(point => ({ ...point, worldPosition: [...point.worldPosition] })) } : {}),
|
|
7001
|
+
...(input.lastFootPlantingFeet !== undefined ? { lastFootPlantingFeet: input.lastFootPlantingFeet.map(foot => ({ ...foot, worldPosition: [...foot.worldPosition] })) } : {}),
|
|
7002
|
+
...(input.footPlantingConfigured !== undefined ? { footPlantingConfigured: input.footPlantingConfigured } : {}),
|
|
6387
7003
|
diagnostics
|
|
6388
7004
|
};
|
|
6389
7005
|
}
|
|
@@ -6660,11 +7276,26 @@ export function createAuraApp(target, options) {
|
|
|
6660
7276
|
const canvas = resolveCanvas(target);
|
|
6661
7277
|
if (canvas) {
|
|
6662
7278
|
configureCanvas(canvas, options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
|
|
7279
|
+
initializeRootPerformanceQuality(canvas);
|
|
7280
|
+
// Apply a caller's device/capture budget before the production controller
|
|
7281
|
+
// derives its first backing size, LODs, particle pool and shadow target.
|
|
7282
|
+
// Runtime mutations remain guarded by app.setPerformanceQuality().
|
|
7283
|
+
if (options.performanceQuality)
|
|
7284
|
+
setRootPerformanceQuality(canvas, options.performanceQuality);
|
|
6663
7285
|
}
|
|
6664
7286
|
const overlay = canvas && shouldRenderOverlay(options.diagnostics, snapshot) ? createDiagnosticsOverlay(canvas, diagnosticsState) : undefined;
|
|
6665
7287
|
let disposed = false;
|
|
6666
7288
|
let animationHandle = 0;
|
|
6667
7289
|
let productionController;
|
|
7290
|
+
let pendingAsyncSteps = 0;
|
|
7291
|
+
let asyncSteps = Promise.resolve();
|
|
7292
|
+
const assertMutableFrame = () => {
|
|
7293
|
+
if (disposed)
|
|
7294
|
+
throw new Error("Aura3D app is disposed.");
|
|
7295
|
+
if (pendingAsyncSteps > 0 || productionController?.busy?.()) {
|
|
7296
|
+
throw new Error("Aura3D frame submission is pending; await stepAsync() before synchronous mutation.");
|
|
7297
|
+
}
|
|
7298
|
+
};
|
|
6668
7299
|
/** WS-2.9: true from the moment a WebGL mount starts until the controller arrives or fails. */
|
|
6669
7300
|
let productionMountPending = false;
|
|
6670
7301
|
/**
|
|
@@ -6685,6 +7316,7 @@ export function createAuraApp(target, options) {
|
|
|
6685
7316
|
* any time and on any scene.
|
|
6686
7317
|
*/
|
|
6687
7318
|
let productionMountSettled = Promise.resolve();
|
|
7319
|
+
let productionMountTask = Promise.resolve();
|
|
6688
7320
|
/** Lets `dispose()` settle an in-flight mount; see the note there. */
|
|
6689
7321
|
let settleMountForDispose = () => undefined;
|
|
6690
7322
|
/*
|
|
@@ -6749,7 +7381,9 @@ export function createAuraApp(target, options) {
|
|
|
6749
7381
|
appPhysicsWorldInstance ??= new PhysicsWorld({
|
|
6750
7382
|
gravity: options.physics?.gravity ? [...options.physics.gravity] : [0, -9.81, 0],
|
|
6751
7383
|
fixedDelta: 1 / 60,
|
|
6752
|
-
enableSleeping: true
|
|
7384
|
+
enableSleeping: true,
|
|
7385
|
+
...(options.physics?.seed === undefined ? {} : { seed: options.physics.seed }),
|
|
7386
|
+
...(options.physics?.continuousCollision ? { continuousCollision: options.physics.continuousCollision } : {})
|
|
6753
7387
|
});
|
|
6754
7388
|
return appPhysicsWorldInstance;
|
|
6755
7389
|
};
|
|
@@ -6804,6 +7438,8 @@ export function createAuraApp(target, options) {
|
|
|
6804
7438
|
let runtimeAlpha = 0;
|
|
6805
7439
|
const ownedInputControllers = new Set();
|
|
6806
7440
|
const runRuntimeFrame = (dt, source) => {
|
|
7441
|
+
if (disposed)
|
|
7442
|
+
return;
|
|
6807
7443
|
runtimeFrame += 1;
|
|
6808
7444
|
runtimeTime += dt;
|
|
6809
7445
|
runtimeAlpha = runtimeFixedDt > 0 ? Math.max(0, Math.min(1, (dt % runtimeFixedDt) / runtimeFixedDt)) : 0;
|
|
@@ -6821,7 +7457,7 @@ export function createAuraApp(target, options) {
|
|
|
6821
7457
|
for (const callback of [...frameCallbacks])
|
|
6822
7458
|
callback(frame);
|
|
6823
7459
|
};
|
|
6824
|
-
const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && renderSnapshot.nodes.some(isWebGLRenderableNode) && typeof window !== "undefined");
|
|
7460
|
+
const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && hasRootRenderableContent(canvas, renderSnapshot.nodes.some(isWebGLRenderableNode)) && typeof window !== "undefined");
|
|
6825
7461
|
const resetDiagnosticsForCurrentScene = (backend) => {
|
|
6826
7462
|
const fresh = createInitialDiagnostics(renderSnapshot, options.renderer);
|
|
6827
7463
|
diagnosticsState.backend = backend;
|
|
@@ -6886,7 +7522,7 @@ export function createAuraApp(target, options) {
|
|
|
6886
7522
|
* So the two cases are separated. A renderable scene with no usable canvas now raises a diagnosable
|
|
6887
7523
|
* error naming the cause, instead of drawing something plausible.
|
|
6888
7524
|
*/
|
|
6889
|
-
const declaresRenderableContent = renderSnapshot.nodes.some(isWebGLRenderableNode);
|
|
7525
|
+
const declaresRenderableContent = hasRootRenderableContent(canvas, renderSnapshot.nodes.some(isWebGLRenderableNode));
|
|
6890
7526
|
/*
|
|
6891
7527
|
* Scoped to the case that is actually a lie: a canvas WAS supplied, so the caller is looking at
|
|
6892
7528
|
* pixels, and the scene has renderable content — but WebGL declined it, so those pixels would be a
|
|
@@ -6925,7 +7561,7 @@ export function createAuraApp(target, options) {
|
|
|
6925
7561
|
: Promise.resolve();
|
|
6926
7562
|
settleMountForDispose = settleMount;
|
|
6927
7563
|
if (shouldUseProductionRenderer && canvas) {
|
|
6928
|
-
|
|
7564
|
+
productionMountTask = startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused, runtimeNodes,
|
|
6929
7565
|
// A route that registered a frame callback, or that has bodies to simulate, needs
|
|
6930
7566
|
// frames regardless of what the scene declares.
|
|
6931
7567
|
/*
|
|
@@ -6939,7 +7575,6 @@ export function createAuraApp(target, options) {
|
|
|
6939
7575
|
() => runtimeTime * 1000)
|
|
6940
7576
|
.then((controller) => {
|
|
6941
7577
|
if (disposed || revision !== mountRevision) {
|
|
6942
|
-
productionMountPending = false;
|
|
6943
7578
|
settleMount();
|
|
6944
7579
|
controller.dispose();
|
|
6945
7580
|
return;
|
|
@@ -6951,6 +7586,7 @@ export function createAuraApp(target, options) {
|
|
|
6951
7586
|
markRouteReady(snapshot, diagnosticsState);
|
|
6952
7587
|
})
|
|
6953
7588
|
.catch((error) => {
|
|
7589
|
+
settleMount();
|
|
6954
7590
|
if (disposed || revision !== mountRevision)
|
|
6955
7591
|
return;
|
|
6956
7592
|
productionMountPending = false;
|
|
@@ -6975,7 +7611,18 @@ export function createAuraApp(target, options) {
|
|
|
6975
7611
|
get backend() {
|
|
6976
7612
|
return diagnosticsState.backend;
|
|
6977
7613
|
},
|
|
7614
|
+
setPerformanceQuality(settings) {
|
|
7615
|
+
assertMutableFrame();
|
|
7616
|
+
const quality = validateRootPerformanceQuality(settings);
|
|
7617
|
+
if (!canvas || !productionController?.setPerformanceQuality)
|
|
7618
|
+
throw new Error("PERFORMANCE_QUALITY_UNSUPPORTED: await a production renderer mount before changing quality.");
|
|
7619
|
+
if (quality.particleScale !== 1 && !supportsRootParticleQuality(canvas))
|
|
7620
|
+
throw new Error("PERFORMANCE_PARTICLE_OWNER_UNAVAILABLE: this root workload has no adaptive native particle owner.");
|
|
7621
|
+
applyRootParticleQuality(canvas, quality.particleScale);
|
|
7622
|
+
productionController.setPerformanceQuality(quality);
|
|
7623
|
+
},
|
|
6978
7624
|
setScene(nextScene) {
|
|
7625
|
+
assertMutableFrame();
|
|
6979
7626
|
snapshot = normalizeSceneSnapshot(nextScene);
|
|
6980
7627
|
renderSnapshot = flattenSceneSnapshot(snapshot);
|
|
6981
7628
|
runtimeNodes.reset(renderSnapshot);
|
|
@@ -7022,6 +7669,10 @@ export function createAuraApp(target, options) {
|
|
|
7022
7669
|
},
|
|
7023
7670
|
pause() {
|
|
7024
7671
|
runtimePaused = true;
|
|
7672
|
+
// Stop the production requestAnimationFrame owner as well as simulation.
|
|
7673
|
+
// Holding the clock alone still submitted the same expensive frame forever.
|
|
7674
|
+
productionController?.pause?.();
|
|
7675
|
+
productionController?.resetTemporalHistory?.("pause");
|
|
7025
7676
|
},
|
|
7026
7677
|
resetRuntimeClock() {
|
|
7027
7678
|
/*
|
|
@@ -7044,6 +7695,8 @@ export function createAuraApp(target, options) {
|
|
|
7044
7695
|
},
|
|
7045
7696
|
resume() {
|
|
7046
7697
|
runtimePaused = false;
|
|
7698
|
+
productionController?.resetTemporalHistory?.("resume");
|
|
7699
|
+
productionController?.resume?.();
|
|
7047
7700
|
if (!animationHandle && !productionController && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
|
|
7048
7701
|
animationHandle = requestAnimationFrame(render);
|
|
7049
7702
|
}
|
|
@@ -7072,11 +7725,17 @@ export function createAuraApp(target, options) {
|
|
|
7072
7725
|
return productionController?.deviceLost?.() ?? false;
|
|
7073
7726
|
},
|
|
7074
7727
|
advance(dt = 1 / 60) {
|
|
7728
|
+
assertMutableFrame();
|
|
7075
7729
|
const seconds = Math.max(0, dt);
|
|
7076
7730
|
runRuntimeFrame(seconds, "manual");
|
|
7077
7731
|
canvasRuntimePhysics?.step(seconds);
|
|
7732
|
+
// Imported GLB animation, consumed root motion and foot planting live in the
|
|
7733
|
+
// production actor bridge. They are simulation state, so `advance()` must
|
|
7734
|
+
// update them even when no GPU frame is presented.
|
|
7735
|
+
productionController?.update?.(runtimeTime * 1000);
|
|
7078
7736
|
},
|
|
7079
7737
|
step(dt = 1 / 60) {
|
|
7738
|
+
assertMutableFrame();
|
|
7080
7739
|
const seconds = Math.max(0, dt);
|
|
7081
7740
|
app.advance(seconds);
|
|
7082
7741
|
const previousPaused = runtimePaused;
|
|
@@ -7136,7 +7795,38 @@ export function createAuraApp(target, options) {
|
|
|
7136
7795
|
}
|
|
7137
7796
|
runtimePaused = previousPaused;
|
|
7138
7797
|
},
|
|
7798
|
+
stepAsync(dt = 1 / 60) {
|
|
7799
|
+
if (!Number.isFinite(dt) || dt < 0)
|
|
7800
|
+
return Promise.reject(new Error("Aura3D stepAsync requires a finite non-negative delta."));
|
|
7801
|
+
if (disposed)
|
|
7802
|
+
return Promise.reject(new Error("Aura3D app is disposed."));
|
|
7803
|
+
pendingAsyncSteps += 1;
|
|
7804
|
+
const next = asyncSteps.then(async () => {
|
|
7805
|
+
await productionMountSettled;
|
|
7806
|
+
if (disposed)
|
|
7807
|
+
throw new Error("Aura3D app is disposed.");
|
|
7808
|
+
const controller = productionController;
|
|
7809
|
+
if (productionMountFailed || !controller?.renderAsync) {
|
|
7810
|
+
throw new Error(`Aura3D asynchronous submission unavailable: ${diagnosticsState.errors.join("; ") || "a production renderer is required"}`);
|
|
7811
|
+
}
|
|
7812
|
+
await controller.renderAsync(() => {
|
|
7813
|
+
runRuntimeFrame(dt, "manual");
|
|
7814
|
+
canvasRuntimePhysics?.step(dt);
|
|
7815
|
+
return runtimeTime * 1000;
|
|
7816
|
+
});
|
|
7817
|
+
}).finally(() => { pendingAsyncSteps -= 1; });
|
|
7818
|
+
asyncSteps = next.catch(() => undefined);
|
|
7819
|
+
return next;
|
|
7820
|
+
},
|
|
7821
|
+
async disposeAsync() {
|
|
7822
|
+
const controller = productionController;
|
|
7823
|
+
app.dispose();
|
|
7824
|
+
await asyncSteps;
|
|
7825
|
+
await productionMountTask;
|
|
7826
|
+
await controller?.whenIdle?.();
|
|
7827
|
+
},
|
|
7139
7828
|
diagnostics() {
|
|
7829
|
+
diagnosticsState.renderer = readRootDiagnosticSnapshot(diagnosticsState.renderer, productionController?.diagnostics, disposed, pendingAsyncSteps > 0 || Boolean(productionController?.busy?.()));
|
|
7140
7830
|
return snapshotDiagnostics(diagnosticsState);
|
|
7141
7831
|
},
|
|
7142
7832
|
evidence(evidenceOptions = {}) {
|
|
@@ -7476,6 +8166,7 @@ function worldLabelsFromSnapshot(snapshot, runtimeNodes) {
|
|
|
7476
8166
|
return {
|
|
7477
8167
|
id: node.name ?? `${node.label}-${index + 1}`,
|
|
7478
8168
|
text: node.text,
|
|
8169
|
+
role: labelTelemetryRoleFor(node.label),
|
|
7479
8170
|
anchor: [0, 0, 0],
|
|
7480
8171
|
offscreenPolicy: "hide"
|
|
7481
8172
|
};
|
|
@@ -7489,6 +8180,7 @@ function worldLabelsFromSnapshot(snapshot, runtimeNodes) {
|
|
|
7489
8180
|
return {
|
|
7490
8181
|
id: node.name ?? `${node.label}-${index + 1}`,
|
|
7491
8182
|
text: node.text,
|
|
8183
|
+
role: labelTelemetryRoleFor(node.label),
|
|
7492
8184
|
anchor: [position[0], position[1], position[2]],
|
|
7493
8185
|
leaderAnchor: [anchor[0], anchor[1], anchor[2]],
|
|
7494
8186
|
screenOffset: separated ? [0, 0] : [0, -26],
|
|
@@ -7642,8 +8334,10 @@ requiresFrames = () => false,
|
|
|
7642
8334
|
* wall-clock time. Without it, `pause()` froze the simulation but not time-driven rendering.
|
|
7643
8335
|
*/
|
|
7644
8336
|
pausedRenderTime = () => 0) {
|
|
7645
|
-
|
|
7646
|
-
|
|
8337
|
+
// Use the same ownership rule as initial mount and setScene. A compatibility
|
|
8338
|
+
// source continues supplying arena/fighter geometry when public nodes are
|
|
8339
|
+
// temporarily absent (for example the crowd-hidden negative control).
|
|
8340
|
+
if (!hasRootRenderableContent(canvas, snapshot.nodes.some(isWebGLRenderableNode))) {
|
|
7647
8341
|
throw new AuraRuntimeError("missing-asset", "Aura3D production rendering requires at least one typed model asset or primitive. Suggested fix: add model(assets.product), primitives.box(), primitives.sphere(), primitives.cylinder(), or primitives.plane().");
|
|
7648
8342
|
}
|
|
7649
8343
|
const renderer = await createProductionSceneRenderer(canvas, snapshot, options.renderer, runtimeNodes);
|
|
@@ -7652,14 +8346,34 @@ pausedRenderTime = () => 0) {
|
|
|
7652
8346
|
const labelLayer = createSceneLabelLayer(canvas, snapshot);
|
|
7653
8347
|
const rendererSelection = normalizeCreateAppRendererOptions(options.renderer);
|
|
7654
8348
|
const pixelRatio = options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe();
|
|
8349
|
+
let disposed = false;
|
|
8350
|
+
let animationHandle = 0;
|
|
8351
|
+
let lastTime = 0;
|
|
8352
|
+
let pendingFrames = 0;
|
|
8353
|
+
let frameTail = Promise.resolve();
|
|
8354
|
+
const baseBackingSize = getRootPerformanceBaseSize(canvas);
|
|
8355
|
+
let resourcesReleased = false;
|
|
8356
|
+
const releaseResources = () => {
|
|
8357
|
+
if (resourcesReleased)
|
|
8358
|
+
return;
|
|
8359
|
+
resourcesReleased = true;
|
|
8360
|
+
labelLayer?.dispose();
|
|
8361
|
+
renderer.dispose();
|
|
8362
|
+
};
|
|
7655
8363
|
const resizeRenderer = () => {
|
|
8364
|
+
if (disposed)
|
|
8365
|
+
return;
|
|
8366
|
+
if (pendingFrames > 0) {
|
|
8367
|
+
resourceOwner.requestResize();
|
|
8368
|
+
return;
|
|
8369
|
+
}
|
|
7656
8370
|
if (options.resize === false)
|
|
7657
8371
|
return;
|
|
7658
8372
|
const parentRect = canvas.parentElement?.getBoundingClientRect();
|
|
7659
8373
|
const cssWidth = parentRect?.width || canvas.clientWidth || (typeof window !== "undefined" ? window.innerWidth : 960) || 960;
|
|
7660
8374
|
const cssHeight = parentRect?.height || canvas.clientHeight || (typeof window !== "undefined" ? window.innerHeight : 540) || 540;
|
|
7661
|
-
const width = Math.max(
|
|
7662
|
-
const height = Math.max(
|
|
8375
|
+
const width = Math.max(1, Math.round(Math.max(320, cssWidth * pixelRatio) * (getRootPerformanceQuality(canvas)?.resolutionScale ?? 1)));
|
|
8376
|
+
const height = Math.max(1, Math.round(Math.max(220, cssHeight * pixelRatio) * (getRootPerformanceQuality(canvas)?.resolutionScale ?? 1)));
|
|
7663
8377
|
if (canvas.width === width && canvas.height === height)
|
|
7664
8378
|
return;
|
|
7665
8379
|
canvas.style.width = `${cssWidth}px`;
|
|
@@ -7668,21 +8382,19 @@ pausedRenderTime = () => 0) {
|
|
|
7668
8382
|
canvas.height = height;
|
|
7669
8383
|
renderer.resize?.(width, height);
|
|
7670
8384
|
};
|
|
8385
|
+
const resourceOwner = new DeferredFrameResources(releaseResources, resizeRenderer);
|
|
7671
8386
|
const resizeObserver = options.resize !== false && typeof ResizeObserver !== "undefined" && canvas.parentElement
|
|
7672
8387
|
? new ResizeObserver(resizeRenderer)
|
|
7673
8388
|
: undefined;
|
|
7674
8389
|
resizeObserver?.observe(canvas.parentElement);
|
|
7675
8390
|
if (options.resize !== false && typeof window !== "undefined")
|
|
7676
8391
|
window.addEventListener("resize", resizeRenderer);
|
|
7677
|
-
|
|
7678
|
-
let animationHandle = 0;
|
|
7679
|
-
let lastTime = 0;
|
|
7680
|
-
const renderFrame = (time = performanceNow()) => {
|
|
8392
|
+
const renderFrame = (time = performanceNow(), submittedDrawCalls, explicit = false) => {
|
|
7681
8393
|
if (disposed)
|
|
7682
8394
|
return;
|
|
7683
8395
|
const delta = lastTime > 0 ? Math.max(1, time - lastTime) : 16.67;
|
|
7684
8396
|
lastTime = time;
|
|
7685
|
-
if (!isPaused())
|
|
8397
|
+
if (!explicit && !isPaused())
|
|
7686
8398
|
beforeRender?.(delta / 1000, "raf");
|
|
7687
8399
|
/*
|
|
7688
8400
|
* A paused app must not keep advancing time-driven rendering.
|
|
@@ -7697,8 +8409,8 @@ pausedRenderTime = () => 0) {
|
|
|
7697
8409
|
* `runtimeTime` and renders explicitly, so stepping still produces new frames — what stops is
|
|
7698
8410
|
* time passing on its own.
|
|
7699
8411
|
*/
|
|
7700
|
-
const renderTime =
|
|
7701
|
-
const drawCalls = renderer.render(renderTime);
|
|
8412
|
+
const renderTime = resolveRootRenderTime(time, explicit, isPaused, pausedRenderTime);
|
|
8413
|
+
const drawCalls = submittedDrawCalls ?? renderer.render(renderTime);
|
|
7702
8414
|
diagnosticsState.backend = renderer.backend;
|
|
7703
8415
|
diagnosticsState.fps = diagnosticsState.fps || 60;
|
|
7704
8416
|
diagnosticsState.drawCalls = drawCalls;
|
|
@@ -7717,14 +8429,100 @@ pausedRenderTime = () => 0) {
|
|
|
7717
8429
|
labelLayer.setOcclusionTest(createSceneLabelOcclusionTest(snapshot, resolveCameraFrame(snapshot, snapshot.camera, renderTime, runtimeNodes).eye, runtimeNodes));
|
|
7718
8430
|
labelLayer.update(renderer.viewProjection(renderTime));
|
|
7719
8431
|
diagnosticsState.labels = labelLayer.snapshot();
|
|
8432
|
+
const labelNodes = groups.flatten(snapshot.nodes).filter((node) => node.kind === "label");
|
|
8433
|
+
diagnosticsState.labelTelemetry = collectLabelTelemetry(labelNodes, diagnosticsState.labels);
|
|
8434
|
+
diagnosticsState.textBuckets = summarizeTextBuckets({
|
|
8435
|
+
accessibleDom: labelNodes.filter((node) => node.label === "hud").length,
|
|
8436
|
+
worldAnchoredPlaced: diagnosticsState.labels.filter((label) => label.visible).length,
|
|
8437
|
+
sdfTexts: groups.flatten(snapshot.nodes).filter((node) => node.kind === "primitive" && node.text3D?.backend === "sdf").length
|
|
8438
|
+
});
|
|
7720
8439
|
}
|
|
7721
8440
|
overlay?.update();
|
|
7722
|
-
if ((sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
|
|
7723
|
-
animationHandle = requestAnimationFrame(
|
|
8441
|
+
if (!explicit && !isPaused() && (sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
|
|
8442
|
+
animationHandle = requestAnimationFrame(scheduleFrame);
|
|
7724
8443
|
}
|
|
7725
8444
|
};
|
|
7726
|
-
|
|
8445
|
+
const submitAsync = (clock, automatic = false) => {
|
|
8446
|
+
if (disposed)
|
|
8447
|
+
return Promise.reject(new Error("Aura3D renderer is disposed."));
|
|
8448
|
+
if (!renderer.renderAsync)
|
|
8449
|
+
return Promise.reject(new Error("Aura3D asynchronous submission requires the production renderer."));
|
|
8450
|
+
pendingFrames += 1;
|
|
8451
|
+
const submitted = resourceOwner.run(() => frameTail.then(async () => {
|
|
8452
|
+
if (disposed)
|
|
8453
|
+
throw new Error("Aura3D renderer is disposed.");
|
|
8454
|
+
const time = clock();
|
|
8455
|
+
if (automatic) {
|
|
8456
|
+
const delta = lastTime > 0 ? Math.max(1, time - lastTime) : 16.67;
|
|
8457
|
+
lastTime = time;
|
|
8458
|
+
if (!isPaused())
|
|
8459
|
+
beforeRender?.(delta / 1000, "raf");
|
|
8460
|
+
}
|
|
8461
|
+
// User frame callbacks can dispose the app. Do not submit a new frame after
|
|
8462
|
+
// that callback; pending ownership still defers cleanup until this settles.
|
|
8463
|
+
if (disposed)
|
|
8464
|
+
throw new Error("Aura3D renderer is disposed.");
|
|
8465
|
+
const renderTime = resolveRootRenderTime(time, !automatic, isPaused, pausedRenderTime);
|
|
8466
|
+
const drawCalls = await renderer.renderAsync(renderTime);
|
|
8467
|
+
if (!disposed) {
|
|
8468
|
+
const previousLastTime = lastTime;
|
|
8469
|
+
renderFrame(renderTime, drawCalls, true);
|
|
8470
|
+
lastTime = previousLastTime;
|
|
8471
|
+
}
|
|
8472
|
+
}).finally(() => { pendingFrames -= 1; }));
|
|
8473
|
+
frameTail = submitted.catch(() => undefined);
|
|
8474
|
+
return submitted;
|
|
8475
|
+
};
|
|
8476
|
+
const scheduleFrame = (time) => {
|
|
8477
|
+
animationHandle = 0;
|
|
8478
|
+
if (disposed)
|
|
8479
|
+
return;
|
|
8480
|
+
if (options.frameMode === "async") {
|
|
8481
|
+
void submitAsync(() => time, true).then(() => {
|
|
8482
|
+
if (!disposed && !isPaused() && (sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
|
|
8483
|
+
animationHandle = requestAnimationFrame(scheduleFrame);
|
|
8484
|
+
}
|
|
8485
|
+
}).catch((error) => {
|
|
8486
|
+
diagnosticsState.errors.push(productionRenderErrorMessage(error));
|
|
8487
|
+
overlay?.update();
|
|
8488
|
+
});
|
|
8489
|
+
}
|
|
8490
|
+
else if (pendingFrames > 0) {
|
|
8491
|
+
// A manual async frame owns the resources. Retry without advancing simulation.
|
|
8492
|
+
// A pause cancels automatic retries; the explicit owner completes independently.
|
|
8493
|
+
if (!isPaused())
|
|
8494
|
+
animationHandle = requestAnimationFrame(scheduleFrame);
|
|
8495
|
+
}
|
|
8496
|
+
else
|
|
8497
|
+
renderFrame(time);
|
|
8498
|
+
};
|
|
8499
|
+
try {
|
|
8500
|
+
if (options.frameMode === "async") {
|
|
8501
|
+
await submitAsync(() => performanceNow(), true);
|
|
8502
|
+
if (!isPaused() && (sceneWantsFrames || requiresFrames()) && options.autoStart !== false && typeof requestAnimationFrame !== "undefined") {
|
|
8503
|
+
animationHandle = requestAnimationFrame(scheduleFrame);
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
8506
|
+
else
|
|
8507
|
+
renderFrame();
|
|
8508
|
+
}
|
|
8509
|
+
catch (error) {
|
|
8510
|
+
// A failed first submission has no returned controller to dispose its resources.
|
|
8511
|
+
disposed = true;
|
|
8512
|
+
resizeObserver?.disconnect();
|
|
8513
|
+
if (typeof window !== "undefined")
|
|
8514
|
+
window.removeEventListener("resize", resizeRenderer);
|
|
8515
|
+
releaseResources();
|
|
8516
|
+
throw error;
|
|
8517
|
+
}
|
|
7727
8518
|
return {
|
|
8519
|
+
update(time) {
|
|
8520
|
+
if (disposed)
|
|
8521
|
+
throw new Error("Aura3D renderer is disposed.");
|
|
8522
|
+
if (pendingFrames > 0)
|
|
8523
|
+
throw new Error("Aura3D async frame is pending; await it before advancing renderer state.");
|
|
8524
|
+
renderer.update?.(time);
|
|
8525
|
+
},
|
|
7728
8526
|
render(time = performanceNow()) {
|
|
7729
8527
|
/*
|
|
7730
8528
|
* Preserve `lastTime` across an explicit render.
|
|
@@ -7735,10 +8533,50 @@ pausedRenderTime = () => 0) {
|
|
|
7735
8533
|
* different clock — a visible jump. Same defect the canvas2d path had; fixed identically so
|
|
7736
8534
|
* the two render paths cannot disagree about what `step` does.
|
|
7737
8535
|
*/
|
|
8536
|
+
if (pendingFrames > 0)
|
|
8537
|
+
throw new Error("Aura3D async frame is pending; await it before synchronous rendering.");
|
|
7738
8538
|
const previousLastTime = lastTime;
|
|
7739
|
-
renderFrame(time);
|
|
8539
|
+
renderFrame(time, undefined, true);
|
|
7740
8540
|
lastTime = previousLastTime;
|
|
7741
8541
|
},
|
|
8542
|
+
setPerformanceQuality(settings) {
|
|
8543
|
+
if (disposed || pendingFrames > 0)
|
|
8544
|
+
throw new Error("PERFORMANCE_QUALITY_BUSY: await the active frame before changing quality.");
|
|
8545
|
+
setRootPerformanceQuality(canvas, settings);
|
|
8546
|
+
if (options.resize !== false)
|
|
8547
|
+
resizeRenderer();
|
|
8548
|
+
else {
|
|
8549
|
+
const width = Math.max(1, Math.round(baseBackingSize.width * settings.resolutionScale));
|
|
8550
|
+
const height = Math.max(1, Math.round(baseBackingSize.height * settings.resolutionScale));
|
|
8551
|
+
canvas.width = width;
|
|
8552
|
+
canvas.height = height;
|
|
8553
|
+
renderer.resize?.(width, height);
|
|
8554
|
+
}
|
|
8555
|
+
renderer.resetTemporalHistory?.("performance-quality");
|
|
8556
|
+
},
|
|
8557
|
+
diagnostics: () => disposed || pendingFrames > 0 ? undefined : renderer.diagnostics,
|
|
8558
|
+
renderAsync: (clock) => submitAsync(clock),
|
|
8559
|
+
busy: () => pendingFrames > 0,
|
|
8560
|
+
whenIdle: () => frameTail,
|
|
8561
|
+
resetTemporalHistory(reason) {
|
|
8562
|
+
// Reset after the active submission, before the next one can consume history.
|
|
8563
|
+
if (pendingFrames > 0)
|
|
8564
|
+
frameTail = frameTail.then(() => { if (!disposed)
|
|
8565
|
+
renderer.resetTemporalHistory?.(reason); });
|
|
8566
|
+
else
|
|
8567
|
+
renderer.resetTemporalHistory?.(reason);
|
|
8568
|
+
},
|
|
8569
|
+
pause() {
|
|
8570
|
+
if (animationHandle && typeof cancelAnimationFrame !== "undefined")
|
|
8571
|
+
cancelAnimationFrame(animationHandle);
|
|
8572
|
+
animationHandle = 0;
|
|
8573
|
+
},
|
|
8574
|
+
resume() {
|
|
8575
|
+
if (disposed || animationHandle || options.autoStart === false || typeof requestAnimationFrame === "undefined")
|
|
8576
|
+
return;
|
|
8577
|
+
if (sceneWantsFrames || requiresFrames())
|
|
8578
|
+
animationHandle = requestAnimationFrame(scheduleFrame);
|
|
8579
|
+
},
|
|
7742
8580
|
// WS-2.6 — forward device-loss subscription from whichever renderer backs this controller.
|
|
7743
8581
|
onDeviceLost: renderer.onDeviceLost ? (listener) => renderer.onDeviceLost(listener) : undefined,
|
|
7744
8582
|
onDeviceRestored: renderer.onDeviceRestored ? (listener) => renderer.onDeviceRestored(listener) : undefined,
|
|
@@ -7750,8 +8588,7 @@ pausedRenderTime = () => 0) {
|
|
|
7750
8588
|
resizeObserver?.disconnect();
|
|
7751
8589
|
if (typeof window !== "undefined")
|
|
7752
8590
|
window.removeEventListener("resize", resizeRenderer);
|
|
7753
|
-
|
|
7754
|
-
renderer.dispose();
|
|
8591
|
+
resourceOwner.dispose();
|
|
7755
8592
|
}
|
|
7756
8593
|
};
|
|
7757
8594
|
}
|
|
@@ -7814,16 +8651,37 @@ function createProductionRuntimeEnvironment(snapshot) {
|
|
|
7814
8651
|
"product-hero": "softbox",
|
|
7815
8652
|
"night-cinematic": "evening",
|
|
7816
8653
|
"metal-studio": "exhibit",
|
|
7817
|
-
"glass-studio": "softbox"
|
|
8654
|
+
"glass-studio": "softbox",
|
|
8655
|
+
// B3: HDRI first frames render the honest studio procedural fallback;
|
|
8656
|
+
// the post-mount upgrade swaps in the HDR chain (hdriUrl below).
|
|
8657
|
+
"hdri": "studio"
|
|
7818
8658
|
};
|
|
7819
8659
|
const preset = presetByEnvironment[authoredEnvironment.environment];
|
|
7820
8660
|
const bundle = createExternalParityEnvironmentLighting(preset);
|
|
7821
8661
|
const intensity = nonNegativeFinite(authoredEnvironment.intensity);
|
|
7822
8662
|
const proceduralMap = bundle.lighting.proceduralMap;
|
|
8663
|
+
const hdriUrl = authoredEnvironment.environment === "hdri"
|
|
8664
|
+
&& typeof authoredEnvironment.texture === "object"
|
|
8665
|
+
&& authoredEnvironment.texture.kind === "aura-asset-ref"
|
|
8666
|
+
? authoredEnvironment.texture.url ?? undefined
|
|
8667
|
+
: undefined;
|
|
8668
|
+
const hdriReflectionUrl = authoredEnvironment.environment === "hdri"
|
|
8669
|
+
&& typeof authoredEnvironment.reflectionTexture === "object"
|
|
8670
|
+
&& authoredEnvironment.reflectionTexture.kind === "aura-asset-ref"
|
|
8671
|
+
? authoredEnvironment.reflectionTexture.url ?? undefined
|
|
8672
|
+
: undefined;
|
|
8673
|
+
const hdriRotation = authoredEnvironment.environment === "hdri" && authoredEnvironment.rotation !== undefined
|
|
8674
|
+
? authoredEnvironment.rotation
|
|
8675
|
+
: undefined;
|
|
7823
8676
|
return {
|
|
7824
8677
|
preset: authoredEnvironment.environment,
|
|
7825
8678
|
intensity,
|
|
7826
|
-
evidence:
|
|
8679
|
+
evidence: hdriUrl
|
|
8680
|
+
? `production bridge renders the studio procedural fallback until the authored HDRI ${hdriUrl}${hdriReflectionUrl ? ` (reflection ${hdriReflectionUrl})` : ""} resolves through the HDR chain`
|
|
8681
|
+
: `production bridge submitted the authored ${authoredEnvironment.environment} environment through generated HDR ${preset} lighting`,
|
|
8682
|
+
...(hdriUrl ? { hdriUrl } : {}),
|
|
8683
|
+
...(hdriReflectionUrl ? { hdriReflectionUrl } : {}),
|
|
8684
|
+
...(hdriRotation !== undefined ? { hdriRotation } : {}),
|
|
7827
8685
|
lighting: {
|
|
7828
8686
|
...bundle.lighting,
|
|
7829
8687
|
color: authoredEnvironment.color ? colorToLinearRgb(authoredEnvironment.color) : bundle.lighting.color,
|
|
@@ -7878,14 +8736,16 @@ function createProductionRuntimeEnvironment(snapshot) {
|
|
|
7878
8736
|
* changed exactly zero pixels through `createAuraApp`. Returning `false` when no
|
|
7879
8737
|
* fog node is authored keeps the unfogged path unchanged.
|
|
7880
8738
|
*/
|
|
7881
|
-
function createProductionRuntimeEnvironmentFog(snapshot) {
|
|
7882
|
-
const
|
|
8739
|
+
function createProductionRuntimeEnvironmentFog(snapshot, lights = [], renderWidth = 1280, renderHeight = 720) {
|
|
8740
|
+
const nodes = groups.flatten(snapshot.nodes);
|
|
8741
|
+
const volumetric = nodes.find((node) => node.kind === "effect" && node.effect === "volumetric-fog");
|
|
8742
|
+
const fog = volumetric ?? nodes.find((node) => node.kind === "effect" && node.effect === "fog");
|
|
7883
8743
|
if (!fog)
|
|
7884
8744
|
return false;
|
|
7885
8745
|
const density = clampNumber(fog.density ?? 0.12, 0, 1);
|
|
7886
8746
|
const intensity = clampNumber(fog.intensity ?? 0.5, 0, 1);
|
|
7887
8747
|
const color = colorToLinearRgb(fog.color ?? "#9fb7d9");
|
|
7888
|
-
|
|
8748
|
+
const base = {
|
|
7889
8749
|
// Exponential-squared reads as depth haze rather than a hard linear band, which
|
|
7890
8750
|
// matches what the public helper documents.
|
|
7891
8751
|
mode: "exponential-squared",
|
|
@@ -7895,10 +8755,53 @@ function createProductionRuntimeEnvironmentFog(snapshot) {
|
|
|
7895
8755
|
density,
|
|
7896
8756
|
maxOpacity: clampNumber(0.25 + intensity * 0.55, 0, 0.92)
|
|
7897
8757
|
};
|
|
7898
|
-
|
|
7899
|
-
|
|
8758
|
+
if (!volumetric)
|
|
8759
|
+
return base;
|
|
8760
|
+
// A5: the volumetric-fog node drives the GPU forward inscatter terms from
|
|
8761
|
+
// the dominant collected light; quality "off" zeroes the forward term and
|
|
8762
|
+
// keeps this exp2 base exactly.
|
|
8763
|
+
const resolved = resolveVolumetricFog({
|
|
8764
|
+
density: volumetric.density,
|
|
8765
|
+
intensity: volumetric.intensity,
|
|
8766
|
+
...(volumetric.lightPosition ? { lightPosition: volumetric.lightPosition } : {}),
|
|
8767
|
+
...(volumetric.heightFalloff !== undefined ? { heightFalloff: volumetric.heightFalloff } : {}),
|
|
8768
|
+
...(volumetric.heightReference !== undefined ? { heightReference: volumetric.heightReference } : {}),
|
|
8769
|
+
...(volumetric.volumetricQuality ? { quality: volumetric.volumetricQuality } : {})
|
|
8770
|
+
}, lights, renderWidth, renderHeight);
|
|
8771
|
+
return {
|
|
8772
|
+
...base,
|
|
8773
|
+
density: clampNumber(volumetric.density ?? 0.18, 0, 1),
|
|
8774
|
+
volumetricIntensity: resolved.forward.volumetricIntensity,
|
|
8775
|
+
...(resolved.forward.volumetricLightDirection ? { volumetricLightDirection: resolved.forward.volumetricLightDirection } : {}),
|
|
8776
|
+
volumetricLightColor: resolved.forward.volumetricLightColor,
|
|
8777
|
+
...(resolved.forward.heightFalloff !== undefined ? { heightFalloff: resolved.forward.heightFalloff } : {}),
|
|
8778
|
+
...(resolved.forward.heightReference !== undefined ? { heightReference: resolved.forward.heightReference } : {})
|
|
8779
|
+
};
|
|
8780
|
+
}
|
|
8781
|
+
const productionTemporalSceneKeys = new WeakMap();
|
|
8782
|
+
let productionTemporalSceneSequence = 0;
|
|
8783
|
+
export function resolveNativeBloomRadius(authoredRadius) {
|
|
8784
|
+
const radius = authoredRadius ?? 0.38;
|
|
8785
|
+
const normalizedKernel = radius <= 1 ? Math.round(radius * 8) : Math.round(radius);
|
|
8786
|
+
return Math.max(1, Math.min(4, normalizedKernel));
|
|
8787
|
+
}
|
|
8788
|
+
function createProductionRuntimePostprocess(snapshot, lights = [], renderWidth = 1280, renderHeight = 720, temporalSupported = true) {
|
|
7900
8789
|
const nodes = groups.flatten(snapshot.nodes);
|
|
7901
8790
|
const authoredBloom = nodes.find((node) => node.kind === "effect" && node.effect === "bloom");
|
|
8791
|
+
// A5: the DISTINCT volumetric-fog node submits the depth-aware inscatter
|
|
8792
|
+
// pass (renderer-owned depth attaches automatically); quality "off" (or no
|
|
8793
|
+
// node) submits nothing and the scene keeps forward exp2 fog only.
|
|
8794
|
+
const authoredVolumetricFog = nodes.find((node) => node.kind === "effect" && node.effect === "volumetric-fog");
|
|
8795
|
+
const volumetricPass = authoredVolumetricFog
|
|
8796
|
+
? resolveVolumetricFog({
|
|
8797
|
+
density: authoredVolumetricFog.density,
|
|
8798
|
+
intensity: authoredVolumetricFog.intensity,
|
|
8799
|
+
...(authoredVolumetricFog.lightPosition ? { lightPosition: authoredVolumetricFog.lightPosition } : {}),
|
|
8800
|
+
...(authoredVolumetricFog.heightFalloff !== undefined ? { heightFalloff: authoredVolumetricFog.heightFalloff } : {}),
|
|
8801
|
+
...(authoredVolumetricFog.heightReference !== undefined ? { heightReference: authoredVolumetricFog.heightReference } : {}),
|
|
8802
|
+
...(authoredVolumetricFog.volumetricQuality ? { quality: authoredVolumetricFog.volumetricQuality } : {})
|
|
8803
|
+
}, lights, renderWidth, renderHeight).pass
|
|
8804
|
+
: null;
|
|
7902
8805
|
const bloomRequested = Boolean(authoredBloom);
|
|
7903
8806
|
// Root previously advertised `ssao` in `requestedPasses` whenever a scene added
|
|
7904
8807
|
// effects.ambientOcclusion() or effects.contactOcclusion(), but never submitted
|
|
@@ -7908,15 +8811,45 @@ function createProductionRuntimePostprocess(snapshot) {
|
|
|
7908
8811
|
const authoredAmbientOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
|
|
7909
8812
|
const authoredContactOcclusion = nodes.find((node) => node.kind === "effect" && node.effect === "contact-occlusion");
|
|
7910
8813
|
const authoredOcclusion = authoredAmbientOcclusion ?? authoredContactOcclusion;
|
|
8814
|
+
// The renderer owns temporal GPU inputs. Root checks actual drawable
|
|
8815
|
+
// geometry before requesting them; unsupported scenes retain ordinary output.
|
|
8816
|
+
const authoredColorGrade = nodes.find((node) => node.kind === "effect" && node.effect === "color-grade");
|
|
8817
|
+
const authoredAntiAlias = nodes.find((node) => node.kind === "effect" && node.effect === "anti-alias");
|
|
8818
|
+
const authoredOutline = nodes.find((node) => node.kind === "effect" && node.effect === "outline");
|
|
8819
|
+
const authoredSsr = nodes.find((node) => node.kind === "effect" && node.effect === "screen-space-reflections");
|
|
8820
|
+
const authoredDof = nodes.find((node) => node.kind === "effect" && node.effect === "depth-of-field");
|
|
8821
|
+
const authoredMotionBlur = nodes.find((node) => node.kind === "effect" && node.effect === "motion-blur");
|
|
8822
|
+
const temporalRequested = temporalSupported && (Boolean(authoredMotionBlur) || authoredAntiAlias?.mode === "taa");
|
|
8823
|
+
let sceneKey = productionTemporalSceneKeys.get(snapshot);
|
|
8824
|
+
if (!sceneKey) {
|
|
8825
|
+
sceneKey = `root-scene-${++productionTemporalSceneSequence}`;
|
|
8826
|
+
productionTemporalSceneKeys.set(snapshot, sceneKey);
|
|
8827
|
+
}
|
|
8828
|
+
const fxaaRequested = (authoredAntiAlias?.mode ?? "fxaa") === "fxaa";
|
|
8829
|
+
const outlineChannels = colorToRgba(authoredOutline?.color ?? "#ff9822");
|
|
7911
8830
|
return {
|
|
7912
8831
|
// Tone mapping requires unclamped linear input. RGBA8 quantized dark clear
|
|
7913
8832
|
// colors and clipped highlights before ACES, which produced washed-out output.
|
|
7914
8833
|
targetFormat: "rgba16f",
|
|
8834
|
+
...(temporalRequested ? {
|
|
8835
|
+
temporal: { sceneKey },
|
|
8836
|
+
...(authoredMotionBlur ? { motionBlur: { samples: 8, scale: clampNumber(authoredMotionBlur.intensity ?? .5, 0, 2) } } : {}),
|
|
8837
|
+
...(authoredAntiAlias?.mode === "taa" ? { taa: { blend: .9 } } : {})
|
|
8838
|
+
} : {}),
|
|
7915
8839
|
...(bloomRequested ? {
|
|
7916
8840
|
bloom: {
|
|
7917
8841
|
threshold: clampNumber(authoredBloom?.threshold ?? 0.78, 0, 1),
|
|
7918
8842
|
intensity: clampNumber(authoredBloom?.intensity ?? 0.3, 0, 2),
|
|
7919
|
-
|
|
8843
|
+
// Public bloom radius follows the normalized UnrealBloomPass-style
|
|
8844
|
+
// control used by routes and prefabs (normally 0..1), while the native
|
|
8845
|
+
// separable blur consumes an integer pixel kernel. Preserve legacy
|
|
8846
|
+
// explicit pixel kernels above 1 and spread normalized values across
|
|
8847
|
+
// 1..4 so common authored values such as 0.22/0.38 do not all collapse
|
|
8848
|
+
// to a one-pixel blur after rounding.
|
|
8849
|
+
radius: resolveNativeBloomRadius(authoredBloom?.radius),
|
|
8850
|
+
...(authoredBloom?.quality !== undefined ? { quality: authoredBloom.quality } : {}),
|
|
8851
|
+
...(authoredBloom?.softKnee !== undefined ? { softKnee: authoredBloom.softKnee } : {}),
|
|
8852
|
+
...(authoredBloom?.shoulder !== undefined ? { shoulder: authoredBloom.shoulder } : {})
|
|
7920
8853
|
}
|
|
7921
8854
|
} : {}),
|
|
7922
8855
|
...(authoredOcclusion ? {
|
|
@@ -7937,7 +8870,41 @@ function createProductionRuntimePostprocess(snapshot) {
|
|
|
7937
8870
|
operator: "aces",
|
|
7938
8871
|
inputColorSpace: "linear",
|
|
7939
8872
|
outputColorSpace: "srgb"
|
|
7940
|
-
}
|
|
8873
|
+
},
|
|
8874
|
+
...(authoredColorGrade ? {
|
|
8875
|
+
colorGrade: {
|
|
8876
|
+
contrast: clampNumber(authoredColorGrade.contrast ?? 1, 0, 3),
|
|
8877
|
+
saturation: clampNumber(authoredColorGrade.saturation ?? 1, 0, 3)
|
|
8878
|
+
}
|
|
8879
|
+
} : {}),
|
|
8880
|
+
...(authoredOutline ? {
|
|
8881
|
+
outline: {
|
|
8882
|
+
color: [
|
|
8883
|
+
Math.round(outlineChannels[0] * 255),
|
|
8884
|
+
Math.round(outlineChannels[1] * 255),
|
|
8885
|
+
Math.round(outlineChannels[2] * 255),
|
|
8886
|
+
255
|
|
8887
|
+
],
|
|
8888
|
+
width: Math.max(1, Math.min(6, Math.round(authoredOutline.width ?? 3))),
|
|
8889
|
+
threshold: clampNumber(authoredOutline.threshold ?? 0.12, 0, 4),
|
|
8890
|
+
opacity: clampNumber(authoredOutline.intensity ?? 0.9, 0, 1)
|
|
8891
|
+
}
|
|
8892
|
+
} : {}),
|
|
8893
|
+
...(fxaaRequested && authoredAntiAlias ? { fxaa: {} } : {}),
|
|
8894
|
+
...(authoredSsr ? {
|
|
8895
|
+
ssr: {
|
|
8896
|
+
intensity: clampNumber(authoredSsr.intensity ?? 0.9, 0, 2),
|
|
8897
|
+
maxDistance: 18
|
|
8898
|
+
}
|
|
8899
|
+
} : {}),
|
|
8900
|
+
...(authoredDof ? {
|
|
8901
|
+
depthOfField: {
|
|
8902
|
+
focusDepth: clampNumber(authoredDof.focus ?? 0.02, 0, 1),
|
|
8903
|
+
focusRange: clampNumber(0.02 + clampNumber(authoredDof.aperture ?? 0.35, 0, 1) * 0.3, 0.001, 1),
|
|
8904
|
+
maxRadius: Math.max(0, Math.min(8, Math.round(authoredDof.maxBlur ?? 4)))
|
|
8905
|
+
}
|
|
8906
|
+
} : {}),
|
|
8907
|
+
...(volumetricPass ? { volumetricLight: volumetricPass } : {})
|
|
7941
8908
|
};
|
|
7942
8909
|
}
|
|
7943
8910
|
function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
|
|
@@ -7956,11 +8923,11 @@ function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
|
|
|
7956
8923
|
}, 1);
|
|
7957
8924
|
const shadowCaster = collectedLights.find((light) => light.castsShadow);
|
|
7958
8925
|
const size = sceneRadius > 30 ? 4096 : sceneRadius > 10 ? 2048 : 1024;
|
|
7959
|
-
const
|
|
8926
|
+
const tuning = resolveProductionRuntimeShadowTuning(shadowCaster?.kind, size, sceneRadius);
|
|
7960
8927
|
return {
|
|
7961
8928
|
enabled: Boolean(shadowCaster),
|
|
7962
8929
|
size,
|
|
7963
|
-
|
|
8930
|
+
...tuning,
|
|
7964
8931
|
strength: category === "city-day" ? 0.38
|
|
7965
8932
|
: category === "material" || category === "product" ? 0.24
|
|
7966
8933
|
: 0.32,
|
|
@@ -7970,6 +8937,30 @@ function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
|
|
|
7970
8937
|
label: `aura3d-root-production-${category}-${size}px-shadow-map`
|
|
7971
8938
|
};
|
|
7972
8939
|
}
|
|
8940
|
+
/**
|
|
8941
|
+
* Shadow bias is compared in normalized light-depth space. Directional maps use
|
|
8942
|
+
* an orthographic projection, so their existing scene-radius/texel heuristic is
|
|
8943
|
+
* world-linear. A spot map is perspective: applying that world-space value at
|
|
8944
|
+
* the far end of its cone erased legitimate caster/receiver separation (Aura
|
|
8945
|
+
* Clash measured 0.00147 normalized depth versus the old 0.004 constant bias),
|
|
8946
|
+
* and the default slope term could erase the remainder on a vertical receiver.
|
|
8947
|
+
* Keep the established directional/point policy intact and use a bounded
|
|
8948
|
+
* fraction of one normalized spot-map texel for both perspective terms.
|
|
8949
|
+
*/
|
|
8950
|
+
export function resolveProductionRuntimeShadowTuning(kind, size, sceneRadius) {
|
|
8951
|
+
if (!Number.isInteger(size) || size <= 0)
|
|
8952
|
+
throw new RangeError("Shadow-map size must be a positive integer.");
|
|
8953
|
+
if (!Number.isFinite(sceneRadius) || sceneRadius <= 0)
|
|
8954
|
+
throw new RangeError("Shadow scene radius must be finite and positive.");
|
|
8955
|
+
if (kind === "spot") {
|
|
8956
|
+
return {
|
|
8957
|
+
bias: clampNumber(0.15 / size, 0.00005, 0.00035),
|
|
8958
|
+
slopeBias: 0.12
|
|
8959
|
+
};
|
|
8960
|
+
}
|
|
8961
|
+
const texelWorldSize = (sceneRadius * 2) / size;
|
|
8962
|
+
return { bias: clampNumber(texelWorldSize * 0.55, 0.00035, 0.004) };
|
|
8963
|
+
}
|
|
7973
8964
|
/**
|
|
7974
8965
|
* Device-observed shadow state. `requested` comes from the submitted shadow
|
|
7975
8966
|
* options, but `mapRendered`/`mapSampled` come only from what the device
|
|
@@ -7977,11 +8968,12 @@ function createProductionRuntimeShadowOptions(snapshot, collectedLights) {
|
|
|
7977
8968
|
* sample it. This is what stops the root report from publishing an unconditional
|
|
7978
8969
|
* `shadows.enabled: true`.
|
|
7979
8970
|
*/
|
|
7980
|
-
function createProductionRuntimeShadowObservation(shadowOptions, diagnostics) {
|
|
8971
|
+
function createProductionRuntimeShadowObservation(shadowOptions, diagnostics, observed) {
|
|
7981
8972
|
const nativeShadowMapBindings = diagnostics.nativeShadowMapBindings ?? 0;
|
|
7982
|
-
const mapRendered = Boolean(shadowOptions.enabled) && (diagnostics.shadowRenderTargetsAllocated ?? 0) > 0;
|
|
8973
|
+
const mapRendered = Boolean(shadowOptions.enabled) && observed !== null && (diagnostics.shadowRenderTargetsAllocated ?? 0) > 0;
|
|
7983
8974
|
return {
|
|
7984
8975
|
requested: Boolean(shadowOptions.enabled),
|
|
8976
|
+
observed,
|
|
7985
8977
|
mapRendered,
|
|
7986
8978
|
mapSampled: mapRendered && nativeShadowMapBindings > 0,
|
|
7987
8979
|
mapSize: shadowOptions.size,
|
|
@@ -7990,6 +8982,79 @@ function createProductionRuntimeShadowObservation(shadowOptions, diagnostics) {
|
|
|
7990
8982
|
shadowRenderTargetsAllocated: diagnostics.shadowRenderTargetsAllocated ?? 0
|
|
7991
8983
|
};
|
|
7992
8984
|
}
|
|
8985
|
+
/**
|
|
8986
|
+
* G1 SDF text observation (muse3jsparity-PRD): per-resource fail-closed gate
|
|
8987
|
+
* (`describeSdfTextPixelBacking`) ANDed across mounted SDF resources. Layout
|
|
8988
|
+
* alone never backs pixels; lastOpacity is the live LOD x occlusion proof.
|
|
8989
|
+
*/
|
|
8990
|
+
function createProductionTextObservation(entries) {
|
|
8991
|
+
const sdf = entries.flatMap((entry) => entry.resources.filter((resource) => resource.sdfText !== null));
|
|
8992
|
+
if (sdf.length === 0) {
|
|
8993
|
+
return {
|
|
8994
|
+
sdfTexts: 0,
|
|
8995
|
+
textPixelBacked: false,
|
|
8996
|
+
quadCount: 0,
|
|
8997
|
+
lastOpacity: 1,
|
|
8998
|
+
reason: "no SDF text nodes mounted"
|
|
8999
|
+
};
|
|
9000
|
+
}
|
|
9001
|
+
const gates = sdf.map((resource) => describeSdfTextPixelBacking({
|
|
9002
|
+
atlasUploaded: resource.textureStatus === "textured" && resource.texturedMaterial !== null,
|
|
9003
|
+
quadsSubmitted: resource.sdfText.lastSubmitted,
|
|
9004
|
+
quadCount: resource.sdfText.quadCount
|
|
9005
|
+
}));
|
|
9006
|
+
const backed = gates.filter((gate) => gate.textPixelBacked).length;
|
|
9007
|
+
const quadCount = sdf.reduce((total, resource) => total + (resource.sdfText?.quadCount ?? 0), 0);
|
|
9008
|
+
const lastOpacity = Math.min(...sdf.map((resource) => resource.sdfText?.lastOpacity ?? 1));
|
|
9009
|
+
return {
|
|
9010
|
+
sdfTexts: sdf.length,
|
|
9011
|
+
textPixelBacked: backed === sdf.length,
|
|
9012
|
+
quadCount,
|
|
9013
|
+
lastOpacity,
|
|
9014
|
+
reason: backed === sdf.length
|
|
9015
|
+
? `${backed}/${sdf.length} SDF quad sets sampled from uploaded label textures (${quadCount} quads)`
|
|
9016
|
+
: `${backed}/${sdf.length} SDF quad sets backed: ${gates.filter((gate) => !gate.textPixelBacked).map((gate) => gate.reason).join("; ")}`
|
|
9017
|
+
};
|
|
9018
|
+
}
|
|
9019
|
+
/**
|
|
9020
|
+
* M2 texture streaming observation (muse3jsparity-PRD): funds the
|
|
9021
|
+
* post-upgrade texture table (SDF label images + C1 upgrades) against the
|
|
9022
|
+
* budget with distance-prioritized residency. Empty table = nothing to fund.
|
|
9023
|
+
*/
|
|
9024
|
+
function createProductionTexturesObservation(entries, cameraEye, budgetBytes) {
|
|
9025
|
+
const budget = normalizeTextureBudgetBytes(budgetBytes);
|
|
9026
|
+
const table = entries.flatMap((entry, entryIndex) => entry.resources
|
|
9027
|
+
.filter((resource) => resource.textureMipBytes.length > 0)
|
|
9028
|
+
.map((resource, resourceIndex) => {
|
|
9029
|
+
const position = entry.node.position ?? [0, 0, 0];
|
|
9030
|
+
return {
|
|
9031
|
+
id: `${entry.node.name ?? `primitive-${entryIndex}`}#${resourceIndex}`,
|
|
9032
|
+
mipBytesCoarseToFine: resource.textureMipBytes,
|
|
9033
|
+
distanceMeters: Math.hypot(cameraEye[0] - position[0], cameraEye[1] - position[1], cameraEye[2] - position[2])
|
|
9034
|
+
};
|
|
9035
|
+
}));
|
|
9036
|
+
if (table.length === 0) {
|
|
9037
|
+
return {
|
|
9038
|
+
budgetBytes: budget,
|
|
9039
|
+
usedBytes: 0,
|
|
9040
|
+
requestedBytes: 0,
|
|
9041
|
+
overBudget: false,
|
|
9042
|
+
overBudgetBytes: 0,
|
|
9043
|
+
residentEntries: 0,
|
|
9044
|
+
evictedEntries: []
|
|
9045
|
+
};
|
|
9046
|
+
}
|
|
9047
|
+
const residency = describeTextureStreamingResidency(table, budget);
|
|
9048
|
+
return {
|
|
9049
|
+
budgetBytes: budget,
|
|
9050
|
+
usedBytes: residency.usedBytes,
|
|
9051
|
+
requestedBytes: residency.requestedBytes,
|
|
9052
|
+
overBudget: residency.overBudget,
|
|
9053
|
+
overBudgetBytes: residency.overBudgetBytes,
|
|
9054
|
+
residentEntries: residency.residents.filter((resident) => resident.residentLevels > 0).length,
|
|
9055
|
+
evictedEntries: [...residency.evicted]
|
|
9056
|
+
};
|
|
9057
|
+
}
|
|
7993
9058
|
function createProductionRuntimePostprocessObservation(diagnostics) {
|
|
7994
9059
|
const actualPasses = diagnostics.postprocessPassNames ?? [];
|
|
7995
9060
|
const pixelBacked = actualPasses.length > 0 && diagnostics.lastError === null;
|
|
@@ -8002,7 +9067,8 @@ function createProductionRuntimePostprocessObservation(diagnostics) {
|
|
|
8002
9067
|
pixelBacked,
|
|
8003
9068
|
actualPasses,
|
|
8004
9069
|
fallbackPasses: pixelBacked ? [] : ["direct-render"],
|
|
8005
|
-
targetFormat: diagnostics.postprocessTargetFormat
|
|
9070
|
+
targetFormat: diagnostics.postprocessTargetFormat,
|
|
9071
|
+
executionMode: diagnostics.postprocessPlan?.executionMode ?? "unknown"
|
|
8006
9072
|
};
|
|
8007
9073
|
}
|
|
8008
9074
|
function createProductionRuntimeCollectedLights(snapshot) {
|
|
@@ -8010,17 +9076,36 @@ function createProductionRuntimeCollectedLights(snapshot) {
|
|
|
8010
9076
|
const descriptors = authoredLights.flatMap((node, index) => createProductionRuntimeLightDescriptors(node, index));
|
|
8011
9077
|
if (descriptors.length === 0)
|
|
8012
9078
|
return createProductionRuntimeFallbackLights();
|
|
8013
|
-
const shadowCasterIndex = descriptors
|
|
8014
|
-
if (selected < 0)
|
|
8015
|
-
return index;
|
|
8016
|
-
const current = descriptors[selected];
|
|
8017
|
-
if (descriptor.shadowPriority !== current.shadowPriority) {
|
|
8018
|
-
return descriptor.shadowPriority > current.shadowPriority ? index : selected;
|
|
8019
|
-
}
|
|
8020
|
-
return descriptor.intensity > current.intensity ? index : selected;
|
|
8021
|
-
}, -1);
|
|
9079
|
+
const shadowCasterIndex = resolveProductionShadowCasterIndex(descriptors);
|
|
8022
9080
|
return descriptors.map((descriptor, index) => createProductionRuntimeCollectedLight(descriptor, index === shadowCasterIndex));
|
|
8023
9081
|
}
|
|
9082
|
+
/**
|
|
9083
|
+
* N1 caster selection (pure, unit-tested): an explicit `shadow: true`
|
|
9084
|
+
* request wins the single caster slot over unrequested lights; ties and
|
|
9085
|
+
* all-unrequested scenes keep the legacy priority-then-intensity order.
|
|
9086
|
+
*/
|
|
9087
|
+
export function resolveProductionShadowCasterIndex(descriptors) {
|
|
9088
|
+
const explicitlyRequested = descriptors
|
|
9089
|
+
.map((descriptor, index) => ({ descriptor, index }))
|
|
9090
|
+
.filter(({ descriptor }) => descriptor.shadowRequested);
|
|
9091
|
+
// An authored false is scene-level opt-out when no light is explicitly
|
|
9092
|
+
// requested. Otherwise another unspecified light would silently take the
|
|
9093
|
+
// legacy caster slot and make `shadow: false` visually ineffective.
|
|
9094
|
+
if (explicitlyRequested.length === 0 && descriptors.some((descriptor) => descriptor.shadowDisabled === true)) {
|
|
9095
|
+
return -1;
|
|
9096
|
+
}
|
|
9097
|
+
const candidates = explicitlyRequested.length > 0
|
|
9098
|
+
? explicitlyRequested
|
|
9099
|
+
: descriptors.map((descriptor, index) => ({ descriptor, index }));
|
|
9100
|
+
return candidates.reduce((selected, candidate) => {
|
|
9101
|
+
if (selected === undefined)
|
|
9102
|
+
return candidate;
|
|
9103
|
+
if (candidate.descriptor.shadowPriority !== selected.descriptor.shadowPriority) {
|
|
9104
|
+
return candidate.descriptor.shadowPriority > selected.descriptor.shadowPriority ? candidate : selected;
|
|
9105
|
+
}
|
|
9106
|
+
return candidate.descriptor.intensity > selected.descriptor.intensity ? candidate : selected;
|
|
9107
|
+
}, undefined)?.index ?? -1;
|
|
9108
|
+
}
|
|
8024
9109
|
function createProductionRuntimeLightDescriptors(node, authoredIndex) {
|
|
8025
9110
|
const name = node.name?.trim() || `aura-authored-${node.light}-${authoredIndex + 1}`;
|
|
8026
9111
|
const color = colorToLinearRgb(node.color ?? "#ffffff");
|
|
@@ -8044,6 +9129,8 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
|
|
|
8044
9129
|
spotAngle: 0,
|
|
8045
9130
|
penumbra: 0,
|
|
8046
9131
|
shadowPriority: 3,
|
|
9132
|
+
shadowRequested: node.shadow === true,
|
|
9133
|
+
shadowDisabled: node.shadow === false,
|
|
8047
9134
|
authoredLight: node.light
|
|
8048
9135
|
}];
|
|
8049
9136
|
}
|
|
@@ -8059,12 +9146,34 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
|
|
|
8059
9146
|
spotAngle: 0,
|
|
8060
9147
|
penumbra: 0,
|
|
8061
9148
|
shadowPriority: 1,
|
|
9149
|
+
shadowRequested: false,
|
|
8062
9150
|
authoredLight: node.light
|
|
8063
9151
|
}];
|
|
8064
9152
|
}
|
|
8065
9153
|
if (node.light === "studio") {
|
|
8066
9154
|
return createProductionRuntimeStudioLightDescriptors(node, name, color, intensity, position);
|
|
8067
9155
|
}
|
|
9156
|
+
if (node.light === "spot") {
|
|
9157
|
+
// N1 (muse3jsparity-PRD): authored spot with an explicit cone. The aim
|
|
9158
|
+
// target wins over lookAt; the direction feeds the B1 spot shadow path
|
|
9159
|
+
// and the A5 volumetric dominant-light selection.
|
|
9160
|
+
const spotTarget = node.target ?? node.lookAt ?? [0, 0.75, 0];
|
|
9161
|
+
return [{
|
|
9162
|
+
kind: "spot",
|
|
9163
|
+
name,
|
|
9164
|
+
color,
|
|
9165
|
+
intensity,
|
|
9166
|
+
position,
|
|
9167
|
+
direction: normalize3([spotTarget[0] - position[0], spotTarget[1] - position[1], spotTarget[2] - position[2]]),
|
|
9168
|
+
range: Math.max(1, node.distance ?? 12),
|
|
9169
|
+
spotAngle: clampNumber(node.angle ?? Math.PI / 6, 0.08, Math.PI / 2 - 0.01),
|
|
9170
|
+
penumbra: clampNumber(node.penumbra ?? 0.4, 0, 1),
|
|
9171
|
+
shadowPriority: 2,
|
|
9172
|
+
shadowRequested: node.shadow === true,
|
|
9173
|
+
shadowDisabled: node.shadow === false,
|
|
9174
|
+
authoredLight: node.light
|
|
9175
|
+
}];
|
|
9176
|
+
}
|
|
8068
9177
|
const width = positiveFinite(node.width, node.light === "softbox" ? 2.4 : 2.2)
|
|
8069
9178
|
* productionRuntimeLightAxisScale(node.scale, 0);
|
|
8070
9179
|
const height = positiveFinite(node.height, node.light === "softbox" ? 1.6 : 1.2)
|
|
@@ -8083,6 +9192,7 @@ function createProductionRuntimeLightDescriptors(node, authoredIndex) {
|
|
|
8083
9192
|
spotAngle: clampNumber(Math.atan2(halfDiagonal, targetDistance), 0.08, Math.PI / 2 - 0.01),
|
|
8084
9193
|
penumbra: node.light === "softbox" ? 0.78 : 0.42,
|
|
8085
9194
|
shadowPriority: 2,
|
|
9195
|
+
shadowRequested: false,
|
|
8086
9196
|
authoredLight: node.light,
|
|
8087
9197
|
authoredWidth: width,
|
|
8088
9198
|
authoredHeight: height
|
|
@@ -8112,6 +9222,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
|
|
|
8112
9222
|
spotAngle: 0,
|
|
8113
9223
|
penumbra: 0,
|
|
8114
9224
|
shadowPriority: 3,
|
|
9225
|
+
shadowRequested: false,
|
|
8115
9226
|
authoredLight: node.light
|
|
8116
9227
|
},
|
|
8117
9228
|
{
|
|
@@ -8125,6 +9236,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
|
|
|
8125
9236
|
spotAngle: 0,
|
|
8126
9237
|
penumbra: 0,
|
|
8127
9238
|
shadowPriority: 0,
|
|
9239
|
+
shadowRequested: false,
|
|
8128
9240
|
authoredLight: node.light
|
|
8129
9241
|
},
|
|
8130
9242
|
{
|
|
@@ -8138,6 +9250,7 @@ function createProductionRuntimeStudioLightDescriptors(node, name, color, intens
|
|
|
8138
9250
|
spotAngle: 0,
|
|
8139
9251
|
penumbra: 0,
|
|
8140
9252
|
shadowPriority: 0,
|
|
9253
|
+
shadowRequested: false,
|
|
8141
9254
|
authoredLight: node.light
|
|
8142
9255
|
}
|
|
8143
9256
|
];
|
|
@@ -8157,6 +9270,7 @@ function createProductionRuntimeFallbackLights() {
|
|
|
8157
9270
|
spotAngle: 0,
|
|
8158
9271
|
penumbra: 0,
|
|
8159
9272
|
shadowPriority: 3,
|
|
9273
|
+
shadowRequested: false,
|
|
8160
9274
|
authoredLight: "fallback"
|
|
8161
9275
|
},
|
|
8162
9276
|
{
|
|
@@ -8170,6 +9284,7 @@ function createProductionRuntimeFallbackLights() {
|
|
|
8170
9284
|
spotAngle: 0,
|
|
8171
9285
|
penumbra: 0,
|
|
8172
9286
|
shadowPriority: 0,
|
|
9287
|
+
shadowRequested: false,
|
|
8173
9288
|
authoredLight: "fallback"
|
|
8174
9289
|
},
|
|
8175
9290
|
{
|
|
@@ -8183,6 +9298,7 @@ function createProductionRuntimeFallbackLights() {
|
|
|
8183
9298
|
spotAngle: 0,
|
|
8184
9299
|
penumbra: 0,
|
|
8185
9300
|
shadowPriority: 0,
|
|
9301
|
+
shadowRequested: false,
|
|
8186
9302
|
authoredLight: "fallback"
|
|
8187
9303
|
}
|
|
8188
9304
|
];
|
|
@@ -8342,6 +9458,7 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
|
|
|
8342
9458
|
height: canvas.height,
|
|
8343
9459
|
backend: "webgl2",
|
|
8344
9460
|
antialias: true,
|
|
9461
|
+
...(getRootRenderSource(canvas) ? { errorCheckMode: "frame" } : {}),
|
|
8345
9462
|
preserveDrawingBuffer: true,
|
|
8346
9463
|
// Background colors are display intent. Pre-invert the renderer's coupled
|
|
8347
9464
|
// matrix-fitted ACES transform so presentation preserves that authored color.
|
|
@@ -8349,53 +9466,153 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
|
|
|
8349
9466
|
});
|
|
8350
9467
|
let latestDeviceDiagnostics = productionRenderer.getDiagnostics();
|
|
8351
9468
|
let latestFeatures = productionRenderer.getFeatures();
|
|
9469
|
+
// M2 streaming distances measure against the live camera eye; refreshed
|
|
9470
|
+
// every render so residency follows the camera instead of mount intent.
|
|
9471
|
+
let latestCameraEye = resolveCameraFrame(snapshot, snapshot.camera, 0, runtimeNodes).eye;
|
|
8352
9472
|
const runtimeWarnings = new Set();
|
|
9473
|
+
// C1 textured upgrade: fire-and-forget after mount. Scalar first frames stay
|
|
9474
|
+
// fast; outcomes land in textureUpgradeWarnings, which render() never clears
|
|
9475
|
+
// (per-frame runtimeWarnings are rebuilt every frame).
|
|
9476
|
+
const textureUpgradeWarnings = new Set();
|
|
9477
|
+
void upgradeProductionPrimitiveTextures(primitiveEntries, (message) => {
|
|
9478
|
+
textureUpgradeWarnings.add(message);
|
|
9479
|
+
}, Number(canvas.getContext("webgl2")?.getParameter(WebGL2RenderingContext.MAX_TEXTURE_SIZE) ?? 4096)).catch((error) => {
|
|
9480
|
+
textureUpgradeWarnings.add(`textured upgrade pass failed (${error instanceof Error ? error.message : String(error)}); scalar materials retained`);
|
|
9481
|
+
});
|
|
8353
9482
|
const productionEnvironment = createProductionRuntimeEnvironment(snapshot);
|
|
9483
|
+
// B3 HDRI upgrade: fire-and-forget after mount. First frames render the
|
|
9484
|
+
// studio procedural fallback; the HDR chain swaps the lighting object the
|
|
9485
|
+
// render closure reads every frame. Outcomes land in hdriWarnings, which
|
|
9486
|
+
// render() never clears (per-frame runtimeWarnings are rebuilt every frame).
|
|
9487
|
+
let currentEnvironmentLighting = productionEnvironment.lighting;
|
|
9488
|
+
const hdriWarnings = new Set();
|
|
9489
|
+
const hdriState = { status: productionEnvironment.hdriUrl ? "pending" : "none" };
|
|
9490
|
+
let disposeHdriEnvironment = null;
|
|
9491
|
+
if (flattened.some((node) => node.kind === "environment" && node.environment === "hdri" && node.texture?.kind !== "aura-asset-ref")) {
|
|
9492
|
+
hdriWarnings.add("hdri environment has no texture asset ref; studio procedural fallback retained");
|
|
9493
|
+
}
|
|
9494
|
+
if (productionEnvironment.hdriUrl) {
|
|
9495
|
+
const hdriUrl = productionEnvironment.hdriUrl;
|
|
9496
|
+
const hdriReflectionUrl = productionEnvironment.hdriReflectionUrl;
|
|
9497
|
+
const hdriIntensity = productionEnvironment.intensity;
|
|
9498
|
+
const hdriRotation = productionEnvironment.hdriRotation;
|
|
9499
|
+
void upgradeProductionEnvironmentHdri(hdriUrl, hdriIntensity, hdriReflectionUrl, hdriRotation)
|
|
9500
|
+
.then((result) => {
|
|
9501
|
+
disposeHdriEnvironment?.();
|
|
9502
|
+
disposeHdriEnvironment = result.dispose;
|
|
9503
|
+
currentEnvironmentLighting = result.lighting;
|
|
9504
|
+
hdriState.status = "ready";
|
|
9505
|
+
hdriState.maxLinearValue = result.maxLinearValue;
|
|
9506
|
+
hdriState.specularMipCount = result.specularMipCount;
|
|
9507
|
+
hdriState.dualProbe = result.dualProbe;
|
|
9508
|
+
})
|
|
9509
|
+
.catch((error) => {
|
|
9510
|
+
hdriState.status = "fallback";
|
|
9511
|
+
hdriWarnings.add(`HDRI upgrade failed for ${hdriUrl} (${error instanceof Error ? error.message : String(error)}); studio procedural fallback retained`);
|
|
9512
|
+
});
|
|
9513
|
+
}
|
|
8354
9514
|
const productionRuntimeLights = createProductionRuntimeCollectedLights(snapshot);
|
|
8355
9515
|
const authoredLightNodes = flattened.filter((node) => node.kind === "light");
|
|
8356
9516
|
const authoredDirectLightNodes = authoredLightNodes.filter((node) => node.light !== "ambient");
|
|
8357
9517
|
const authoredAmbientLightCount = authoredLightNodes.length - authoredDirectLightNodes.length;
|
|
8358
9518
|
const authoredAreaProxyCount = authoredDirectLightNodes.filter((node) => node.light === "rect" || node.light === "softbox").length;
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
9519
|
+
// N1 + G1 + M2 observations refresh on every diagnostics read from the
|
|
9520
|
+
// live entries/device state — never cached intent.
|
|
9521
|
+
const buildDiagnostics = () => {
|
|
9522
|
+
const shadowObservation = createProductionRuntimeShadowObservation({ ...createProductionRuntimeShadowOptions(snapshot, productionRuntimeLights), ...(getRootPerformanceQuality(canvas) ? { size: getRootPerformanceQuality(canvas).shadowSize } : {}) }, latestDeviceDiagnostics, productionRenderer.getShadowEvidence());
|
|
9523
|
+
const spotCaster = productionRuntimeLights.find((light) => light.castsShadow);
|
|
9524
|
+
const spotAngle = spotCaster?.kind === "spot" ? spotCaster.spotAngle : undefined;
|
|
9525
|
+
const texturesObservation = createProductionTexturesObservation(primitiveEntries, latestCameraEye, rendererOptions?.textureBudgetBytes ?? normalizeTextureBudgetBytes(undefined));
|
|
9526
|
+
const streamingWarnings = texturesObservation.overBudget
|
|
9527
|
+
? [`texture streaming over budget by ${texturesObservation.overBudgetBytes} bytes `
|
|
9528
|
+
+ `(${texturesObservation.residentEntries} resident, `
|
|
9529
|
+
+ `${texturesObservation.evictedEntries.length} evicted: ${texturesObservation.evictedEntries.join(", ") || "none"}); `
|
|
9530
|
+
+ `raise renderer.textureBudgetBytes above ${texturesObservation.requestedBytes} bytes or move textures closer`]
|
|
9531
|
+
: [];
|
|
9532
|
+
return createRendererDiagnosticReport(snapshot, {
|
|
9533
|
+
mounted: true,
|
|
9534
|
+
backend: "production-runtime",
|
|
9535
|
+
postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
|
|
9536
|
+
shadow: {
|
|
9537
|
+
...shadowObservation,
|
|
9538
|
+
spot: describeProductionSpotShadow({
|
|
9539
|
+
requested: flattened.some((node) => node.kind === "light" && node.light === "spot" && node.shadow === true),
|
|
9540
|
+
casterIsSpot: spotCaster?.kind === "spot",
|
|
9541
|
+
...(spotCaster === undefined ? {} : { casterName: spotCaster.source.name }),
|
|
9542
|
+
...(spotAngle === undefined ? {} : { angle: spotAngle }),
|
|
9543
|
+
...(spotCaster?.kind === "spot" ? { penumbra: spotCaster.penumbra, range: spotCaster.range } : {}),
|
|
9544
|
+
mapRendered: shadowObservation.mapRendered,
|
|
9545
|
+
mapSampled: shadowObservation.mapSampled
|
|
9546
|
+
})
|
|
9547
|
+
},
|
|
9548
|
+
text: createProductionTextObservation(primitiveEntries),
|
|
9549
|
+
textures: texturesObservation,
|
|
9550
|
+
environment: {
|
|
9551
|
+
enabled: true,
|
|
9552
|
+
preset: productionEnvironment.preset,
|
|
9553
|
+
intensity: productionEnvironment.intensity,
|
|
9554
|
+
evidence: hdriState.status === "ready"
|
|
9555
|
+
? `${productionEnvironment.evidence} — HDRI chain ready${hdriState.dualProbe ? " (dual-probe: illumination diffuse + reflection specular)" : " (single probe)"} (maxLinear ${hdriState.maxLinearValue}, ${hdriState.specularMipCount} specular mips)`
|
|
9556
|
+
: productionEnvironment.evidence,
|
|
9557
|
+
// B3 iblPixelBacked: true only after the HDR chain swaps the live
|
|
9558
|
+
// lighting object. Procedural first frames and fallbacks never claim it.
|
|
9559
|
+
iblPixelBacked: hdriState.status === "ready",
|
|
9560
|
+
hdriStatus: hdriState.status,
|
|
9561
|
+
...(hdriState.dualProbe === undefined ? {} : { dualProbe: hdriState.dualProbe }),
|
|
9562
|
+
...(productionEnvironment.hdriRotation === undefined ? {} : { hdriRotation: productionEnvironment.hdriRotation })
|
|
9563
|
+
},
|
|
9564
|
+
warnings: [
|
|
9565
|
+
`Production runtime bridge active with ${actorEntries.length} typed GLB actor${actorEntries.length === 1 ? "" : "s"} and ${primitiveEntries.length} Aura primitive${primitiveEntries.length === 1 ? "" : "s"} on ${productionRenderer.backend}.`,
|
|
9566
|
+
...(authoredDirectLightNodes.length === 0
|
|
9567
|
+
? ["Production runtime direct-light fallback active: the scene has no authored directional, point, studio, rect, or softbox light."]
|
|
9568
|
+
: [`Production runtime derived ${productionRuntimeLights.length} collected direct light${productionRuntimeLights.length === 1 ? "" : "s"} from ${authoredDirectLightNodes.length} authored scene light${authoredDirectLightNodes.length === 1 ? "" : "s"}.`]),
|
|
9569
|
+
...(authoredAmbientLightCount > 0
|
|
9570
|
+
? [`${authoredAmbientLightCount} authored ambient light${authoredAmbientLightCount === 1 ? "" : "s"} remain environment-lighting intent and are not mislabeled as direct CollectedLight entries.`]
|
|
9571
|
+
: []),
|
|
9572
|
+
...(authoredAreaProxyCount > 0
|
|
9573
|
+
? [`${authoredAreaProxyCount} authored rect/softbox light${authoredAreaProxyCount === 1 ? "" : "s"} use bounded spot-light proxies whose cone and range derive from authored width and height; this does not claim physical area-light shading.`]
|
|
9574
|
+
: []),
|
|
9575
|
+
...(flattened.some((node) => node.kind === "effect")
|
|
9576
|
+
? ["Effect nodes are requested in the scene graph; production bridge diagnostics report them, but unsupported postprocess/effect passes remain non-pixel-backed until the runtime feature reports support."]
|
|
9577
|
+
: []),
|
|
9578
|
+
...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
|
|
9579
|
+
...latestFeatures
|
|
9580
|
+
.filter((feature) => feature.state !== "supported")
|
|
9581
|
+
.map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
|
|
9582
|
+
...runtimeWarnings,
|
|
9583
|
+
...textureUpgradeWarnings,
|
|
9584
|
+
...streamingWarnings,
|
|
9585
|
+
...hdriWarnings
|
|
9586
|
+
],
|
|
9587
|
+
deviceDiagnostics: latestDeviceDiagnostics,
|
|
9588
|
+
lodSelections: primitiveEntries
|
|
9589
|
+
.filter((entry) => Boolean(entry.node.lod?.levels.length))
|
|
9590
|
+
.map((entry) => ({
|
|
9591
|
+
nodeName: entry.node.name ?? "unnamed distance LOD",
|
|
9592
|
+
levelIndex: entry.currentLodIndex,
|
|
9593
|
+
levelName: entry.resources[entry.currentLodIndex]?.name ?? `level-${entry.currentLodIndex}`
|
|
9594
|
+
})),
|
|
9595
|
+
texturedMaterials: primitiveEntries.flatMap((entry) => entry.resources.map((resource) => ({
|
|
9596
|
+
nodeName: entry.node.name ?? `aura-primitive-${entry.node.primitive}`,
|
|
9597
|
+
levelName: resource.name,
|
|
9598
|
+
status: resource.textureStatus,
|
|
9599
|
+
slots: resource.textureSlots,
|
|
9600
|
+
pixelBacked: resource.textureStatus === "textured" && resource.texturedMaterial !== null,
|
|
9601
|
+
warnings: resource.textureWarnings
|
|
9602
|
+
})))
|
|
9603
|
+
}, rendererOptions);
|
|
9604
|
+
};
|
|
9605
|
+
let preparedFrame;
|
|
9606
|
+
const buildFrame = (time) => {
|
|
9607
|
+
runtimeWarnings.clear();
|
|
9608
|
+
latestCameraEye = resolveCameraFrame(snapshot, snapshot.camera, time, runtimeNodes).eye;
|
|
9609
|
+
return createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, currentEnvironmentLighting, productionRuntimeLights);
|
|
9610
|
+
};
|
|
9611
|
+
const takePreparedFrame = (time) => {
|
|
9612
|
+
const prepared = preparedFrame?.time === time ? preparedFrame.input : undefined;
|
|
9613
|
+
preparedFrame = undefined;
|
|
9614
|
+
return prepared ?? buildFrame(time);
|
|
9615
|
+
};
|
|
8399
9616
|
return {
|
|
8400
9617
|
get backend() {
|
|
8401
9618
|
return productionRenderer.backend;
|
|
@@ -8403,17 +9620,34 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
|
|
|
8403
9620
|
get diagnostics() {
|
|
8404
9621
|
return buildDiagnostics();
|
|
8405
9622
|
},
|
|
9623
|
+
update(time) {
|
|
9624
|
+
// Building the frame applies controller-bound actor clips, root-motion
|
|
9625
|
+
// consumption, foot IK, morphs and imported-asset evidence. Discarding the
|
|
9626
|
+
// returned input deliberately avoids a GPU/device submission.
|
|
9627
|
+
preparedFrame = { time, input: buildFrame(time) };
|
|
9628
|
+
},
|
|
8406
9629
|
render(time) {
|
|
8407
|
-
|
|
8408
|
-
const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, productionEnvironment.lighting, productionRuntimeLights);
|
|
9630
|
+
const input = takePreparedFrame(time);
|
|
8409
9631
|
const result = productionRenderer.renderInteractiveFrame(input);
|
|
8410
9632
|
latestDeviceDiagnostics = result.diagnostics;
|
|
9633
|
+
getRootRenderSource(canvas)?.onFrame?.(latestDeviceDiagnostics, [...(input.source.collectRenderItems?.() ?? [])]);
|
|
9634
|
+
latestFeatures = result.features;
|
|
9635
|
+
return latestDeviceDiagnostics.drawCalls;
|
|
9636
|
+
},
|
|
9637
|
+
async renderAsync(time) {
|
|
9638
|
+
const input = takePreparedFrame(time);
|
|
9639
|
+
const result = await productionRenderer.renderInteractiveFrameAsync(input);
|
|
9640
|
+
latestDeviceDiagnostics = result.diagnostics;
|
|
9641
|
+
getRootRenderSource(canvas)?.onFrame?.(latestDeviceDiagnostics, [...(input.source.collectRenderItems?.() ?? [])]);
|
|
8411
9642
|
latestFeatures = result.features;
|
|
8412
9643
|
return latestDeviceDiagnostics.drawCalls;
|
|
8413
9644
|
},
|
|
8414
9645
|
viewProjection(time) {
|
|
8415
9646
|
return createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
|
|
8416
9647
|
},
|
|
9648
|
+
resetTemporalHistory(reason) {
|
|
9649
|
+
productionRenderer.resetTemporalHistory(reason);
|
|
9650
|
+
},
|
|
8417
9651
|
resize(width, height) {
|
|
8418
9652
|
productionRenderer.resize(width, height);
|
|
8419
9653
|
},
|
|
@@ -8427,69 +9661,185 @@ async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOp
|
|
|
8427
9661
|
return productionRenderer.deviceLost();
|
|
8428
9662
|
},
|
|
8429
9663
|
dispose() {
|
|
9664
|
+
disposeHdriEnvironment?.();
|
|
8430
9665
|
productionRenderer.dispose();
|
|
8431
9666
|
for (const { actor } of actorEntries)
|
|
8432
9667
|
actor.dispose();
|
|
8433
9668
|
for (const { resources } of primitiveEntries) {
|
|
8434
|
-
for (const { geometry, material } of resources) {
|
|
9669
|
+
for (const { geometry, material, texturedMaterial, textureDisposer } of resources) {
|
|
9670
|
+
textureDisposer?.();
|
|
8435
9671
|
geometry.dispose();
|
|
8436
9672
|
material.dispose();
|
|
9673
|
+
texturedMaterial?.dispose();
|
|
8437
9674
|
}
|
|
8438
9675
|
}
|
|
8439
9676
|
}
|
|
8440
9677
|
};
|
|
8441
9678
|
}
|
|
8442
9679
|
function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, environmentLighting, collectedLights) {
|
|
8443
|
-
const
|
|
9680
|
+
const compatibility = getRootRenderSource(canvas);
|
|
9681
|
+
const attachedItems = compatibility ? [...(compatibility.source.collectRenderItems?.() ?? compatibility.source.renderItems ?? [])] : [];
|
|
9682
|
+
const items = [...attachedItems];
|
|
8444
9683
|
const viewProjectionMatrix = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
|
|
8445
9684
|
const cameraPosition = resolveCameraFrame(snapshot, snapshot.camera, time, runtimeNodes).eye;
|
|
8446
|
-
for (const entry of actorEntries) {
|
|
8447
|
-
|
|
8448
|
-
|
|
9685
|
+
for (const [actorIndex, entry] of actorEntries.entries()) {
|
|
9686
|
+
let currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
|
|
9687
|
+
let currentNode = currentState.node;
|
|
8449
9688
|
if (currentNode.visible === false)
|
|
8450
9689
|
continue;
|
|
8451
|
-
|
|
9690
|
+
let modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
|
|
9691
|
+
// The foot-planting post-pass solves in the same world space this matrix draws into;
|
|
9692
|
+
// refresh its matrix before the clip plays so the solve uses this frame, not the last.
|
|
9693
|
+
applyProductionActorFootPlanting(entry, currentState.animationBinding, modelMatrix, runtimeWarnings);
|
|
9694
|
+
if (currentState.animationPose) {
|
|
9695
|
+
try {
|
|
9696
|
+
entry.actor.applyRetargetedPose(currentState.animationPose, currentState.animationPoseTime ?? time);
|
|
9697
|
+
entry.rootMotionCursors = undefined;
|
|
9698
|
+
}
|
|
9699
|
+
catch (error) {
|
|
9700
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply bound pose: ${productionRenderErrorMessage(error)}`);
|
|
9701
|
+
}
|
|
9702
|
+
}
|
|
9703
|
+
else
|
|
9704
|
+
applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings, modelMatrix, () => {
|
|
9705
|
+
currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
|
|
9706
|
+
currentNode = currentState.node;
|
|
9707
|
+
modelMatrix = [...createModelMatrix(currentNode, productionActorModelBounds(currentNode.asset, entry.actor), shouldNormalizeModelNode(currentNode), time)];
|
|
9708
|
+
applyProductionActorFootPlanting(entry, currentState.animationBinding, modelMatrix, runtimeWarnings);
|
|
9709
|
+
});
|
|
8452
9710
|
applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
9711
|
+
// Wrinkle detail (E1 face-rig demo): resolve morph weights through the model's hook.
|
|
9712
|
+
// Absent hook (or empty weights) resolves to 0 = today's rendering exactly.
|
|
9713
|
+
const wrinkleStrength = currentNode.wrinkle
|
|
9714
|
+
? resolveWrinkleMapStrength(currentState.morphTargets ?? {}, currentNode.wrinkle)
|
|
9715
|
+
: undefined;
|
|
9716
|
+
const actorItems = entry.actor.collectRenderItems({
|
|
9717
|
+
modelMatrix,
|
|
9718
|
+
...(wrinkleStrength === undefined ? {} : { wrinkleStrength })
|
|
9719
|
+
});
|
|
9720
|
+
// P2 (muse3jsparity-PRD): model instances attach exactly like primitive
|
|
9721
|
+
// nodes. Skinned actors cannot instance (D1 matrix) — warn once and draw
|
|
9722
|
+
// single instead of silently dropping copies.
|
|
9723
|
+
const modelInstances = currentNode.instances;
|
|
9724
|
+
let modelInstanceAttach = {};
|
|
9725
|
+
if (modelInstances && modelInstances.length > 0) {
|
|
9726
|
+
if (actorItems.some((item) => item.skinning)) {
|
|
9727
|
+
warnOnInstancingFallback({
|
|
9728
|
+
material: currentNode.name ?? currentNode.asset.id,
|
|
9729
|
+
requestedInstances: modelInstances.length,
|
|
9730
|
+
drawnBatches: Math.max(1, actorItems.length),
|
|
9731
|
+
reason: "skinned-palette-overflow-cpu-fallback",
|
|
9732
|
+
onWarning: (message) => runtimeWarnings.add(message)
|
|
9733
|
+
});
|
|
9734
|
+
}
|
|
9735
|
+
else {
|
|
9736
|
+
modelInstanceAttach = {
|
|
9737
|
+
instanceTransforms: createProductionModelInstanceTransforms(modelInstances, currentNode, productionActorModelBounds(currentNode.asset, entry.actor), time),
|
|
9738
|
+
...(currentNode.instanceColors
|
|
9739
|
+
? { instanceColors: createProductionInstanceColors(currentNode.instanceColors, modelInstances.length) }
|
|
9740
|
+
: {})
|
|
9741
|
+
};
|
|
9742
|
+
}
|
|
9743
|
+
}
|
|
9744
|
+
if (currentNode.instancedModelWarning)
|
|
9745
|
+
runtimeWarnings.add(currentNode.instancedModelWarning);
|
|
9746
|
+
// A GLB mesh has its own authored local/world transform. Native instance
|
|
9747
|
+
// transforms must apply the complete normalized node placement BEFORE that
|
|
9748
|
+
// mesh transform, exactly like an individually mounted actor. The previous
|
|
9749
|
+
// base(actorRoot * mesh) * instance(actorRoot) order normalized twice and
|
|
9750
|
+
// rotated/scaled the instance translations in the mesh's coordinate system.
|
|
9751
|
+
const localActorItems = modelInstanceAttach.instanceTransforms
|
|
9752
|
+
? entry.actor.collectRenderItems({ modelMatrix: identityMat4(), ...(wrinkleStrength === undefined ? {} : { wrinkleStrength }) })
|
|
9753
|
+
: undefined;
|
|
9754
|
+
items.push(...actorItems.map((item, itemIndex) => {
|
|
9755
|
+
const nativeMatrices = modelInstanceAttach.instanceTransforms;
|
|
9756
|
+
const localItem = localActorItems?.[itemIndex];
|
|
9757
|
+
if (nativeMatrices && !localItem)
|
|
9758
|
+
throw new Error("Instanced GLB mesh collection changed within one frame");
|
|
9759
|
+
return { ...item, label: `actor-${actorIndex}:mesh-${itemIndex}:${item.label ?? "mesh"}`, castShadow: currentNode.castShadow, ...modelInstanceAttach,
|
|
9760
|
+
...(nativeMatrices && localItem ? {
|
|
9761
|
+
modelMatrix: identityMat4(),
|
|
9762
|
+
instanceTransforms: composeModelInstanceMatrices(nativeMatrices, localItem.modelMatrix ?? identityMat4())
|
|
9763
|
+
} : {}) };
|
|
9764
|
+
}));
|
|
8456
9765
|
attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
|
|
8457
9766
|
}
|
|
8458
|
-
|
|
9767
|
+
// G1 SDF occlusion test, built once per frame only when SDF quads exist
|
|
9768
|
+
// (same cost argument as WS-2.7: one bbox per node, negligible beside render).
|
|
9769
|
+
const sdfEntries = primitiveEntries.filter((entry) => entry.resources.some((resource) => resource.sdfText !== null));
|
|
9770
|
+
const sdfOcclusionTest = sdfEntries.length > 0
|
|
9771
|
+
? createSceneLabelOcclusionTest(snapshot, cameraPosition, runtimeNodes)
|
|
9772
|
+
: undefined;
|
|
9773
|
+
for (const [primitiveIndex, entry] of primitiveEntries.entries()) {
|
|
8459
9774
|
const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
|
|
8460
9775
|
if (!currentState.visible)
|
|
8461
9776
|
continue;
|
|
8462
|
-
const resource = selectProductionPrimitiveResource(entry, currentState.node, cameraPosition);
|
|
9777
|
+
const resource = selectProductionPrimitiveResource(entry, currentState.node, cameraPosition, getRootPerformanceQuality(canvas)?.lodBias ?? 1);
|
|
9778
|
+
if (resource.sdfText && resource.texturedMaterial) {
|
|
9779
|
+
// G1 per-frame SDF opacity: LOD fade from the live camera distance
|
|
9780
|
+
// times the scene occlusion policy, written to the quad material.
|
|
9781
|
+
// Occlusion "hide" skips submission (no pixels, no backing claim).
|
|
9782
|
+
const position = currentState.node.position ?? [0, 0, 0];
|
|
9783
|
+
const distance = Math.hypot(cameraPosition[0] - position[0], cameraPosition[1] - position[1], cameraPosition[2] - position[2]);
|
|
9784
|
+
const frame = resolveSdfTextFrameOpacity({
|
|
9785
|
+
distance,
|
|
9786
|
+
...(resource.sdfText.lodFadeNear === undefined ? {} : { lodFadeNear: resource.sdfText.lodFadeNear }),
|
|
9787
|
+
...(resource.sdfText.lodFadeFar === undefined ? {} : { lodFadeFar: resource.sdfText.lodFadeFar }),
|
|
9788
|
+
occluded: sdfOcclusionTest?.(position) ?? false,
|
|
9789
|
+
occlusionPolicy: resource.sdfText.occlusionPolicy
|
|
9790
|
+
});
|
|
9791
|
+
resource.sdfText.lastOpacity = frame.opacity;
|
|
9792
|
+
resource.sdfText.lastVisible = frame.visible;
|
|
9793
|
+
resource.sdfText.lastSubmitted = frame.visible;
|
|
9794
|
+
if (!frame.visible)
|
|
9795
|
+
continue;
|
|
9796
|
+
const base = resource.texturedMaterial.getParameter("baseColor");
|
|
9797
|
+
const rgb = Array.isArray(base) && base.length >= 3
|
|
9798
|
+
? [base[0] ?? 1, base[1] ?? 1, base[2] ?? 1]
|
|
9799
|
+
: [1, 1, 1];
|
|
9800
|
+
resource.texturedMaterial.setParameter("baseColor", [rgb[0], rgb[1], rgb[2], frame.opacity]);
|
|
9801
|
+
}
|
|
8463
9802
|
items.push({
|
|
8464
9803
|
geometry: resource.geometry,
|
|
8465
|
-
material: resource.material,
|
|
9804
|
+
material: resource.texturedMaterial ?? resource.material,
|
|
8466
9805
|
modelMatrix: createModelMatrix(currentState.node, resource.bounds, false, time),
|
|
8467
|
-
label: currentState.node.name ??
|
|
9806
|
+
label: `primitive-${primitiveIndex}:${resource.name}:${currentState.node.name ?? currentState.node.primitive}`,
|
|
8468
9807
|
castShadow: currentState.node.castShadow,
|
|
8469
9808
|
includeInAutoFrame: false,
|
|
8470
9809
|
...(currentState.node.instances ? { instanceTransforms: createProductionInstanceTransforms(currentState.node.instances, currentState.node), instanceColors: createProductionInstanceColors(currentState.node.instanceColors, currentState.node.instances.length) } : {})
|
|
8471
9810
|
});
|
|
8472
9811
|
}
|
|
9812
|
+
const unsupportedTemporal = items.find(item => {
|
|
9813
|
+
const base = item.material && "baseMaterial" in item.material ? item.material.baseMaterial : item.material;
|
|
9814
|
+
return Boolean(item.skinning || item.morphTargets?.length || item.instanceTransforms?.length || item.geometry.topology !== "triangles" || base?.renderState.blend);
|
|
9815
|
+
});
|
|
9816
|
+
const temporalRequested = groups.flatten(snapshot.nodes).some(node => node.kind === "effect" && (node.effect === "motion-blur" || (node.effect === "anti-alias" && node.mode === "taa")));
|
|
9817
|
+
for (const warning of runtimeWarnings)
|
|
9818
|
+
if (warning.startsWith("TEMPORAL_UNSUPPORTED_GEOMETRY:"))
|
|
9819
|
+
runtimeWarnings.delete(warning);
|
|
9820
|
+
if (temporalRequested && unsupportedTemporal)
|
|
9821
|
+
runtimeWarnings.add(`TEMPORAL_UNSUPPORTED_GEOMETRY: ${unsupportedTemporal.label}; requires opaque rigid noninstanced triangles`);
|
|
8473
9822
|
const source = {
|
|
9823
|
+
...(compatibility?.source ?? {}),
|
|
8474
9824
|
collectRenderItems: () => items,
|
|
8475
|
-
cameraPolicy: "require",
|
|
8476
|
-
staticBatching:
|
|
9825
|
+
cameraPolicy: compatibility?.source.cameraPolicy ?? "require",
|
|
9826
|
+
staticBatching: !temporalRequested,
|
|
8477
9827
|
frustumCulling: true,
|
|
8478
|
-
collectedLights,
|
|
8479
|
-
environmentLighting,
|
|
9828
|
+
collectedLights: [...collectedLights, ...(compatibility?.source.collectedLights ?? [])],
|
|
9829
|
+
environmentLighting: compatibility?.source.environmentLighting ?? environmentLighting,
|
|
8480
9830
|
// The production runtime owns the pixel-backed HDR target and pass chain for
|
|
8481
9831
|
// routes that request effects. The diagnostics are device-observed, so a
|
|
8482
9832
|
// compositor failure is reported as fallback rather than claimed as a pass.
|
|
8483
|
-
postprocess: createProductionRuntimePostprocess(snapshot),
|
|
8484
|
-
shadow: createProductionRuntimeShadowOptions(snapshot, collectedLights),
|
|
8485
|
-
environmentFog: createProductionRuntimeEnvironmentFog(snapshot),
|
|
8486
|
-
cameraPosition
|
|
9833
|
+
postprocess: compatibility?.source.postprocess ?? createProductionRuntimePostprocess(snapshot, collectedLights, canvas.width, canvas.height, !unsupportedTemporal),
|
|
9834
|
+
shadow: { ...createProductionRuntimeShadowOptions(snapshot, collectedLights), ...(getRootPerformanceQuality(canvas) ? { size: getRootPerformanceQuality(canvas).shadowSize } : {}) },
|
|
9835
|
+
environmentFog: compatibility?.source.environmentFog ?? createProductionRuntimeEnvironmentFog(snapshot, collectedLights, canvas.width, canvas.height),
|
|
9836
|
+
...(compatibility?.source.cameraPolicy === "auto-frame" ? {} : { cameraPosition })
|
|
8487
9837
|
};
|
|
8488
9838
|
const cameraLike = { viewProjectionMatrix };
|
|
8489
9839
|
return {
|
|
8490
9840
|
source,
|
|
8491
|
-
camera: cameraLike,
|
|
8492
|
-
metadata: createProductionRuntimeMetadata(actorEntries, primitiveEntries)
|
|
9841
|
+
...(compatibility?.source.cameraPolicy === "auto-frame" ? {} : { camera: cameraLike }),
|
|
9842
|
+
metadata: includeRootSourceMetadata(createProductionRuntimeMetadata(actorEntries, primitiveEntries), attachedItems)
|
|
8493
9843
|
};
|
|
8494
9844
|
}
|
|
8495
9845
|
function createProductionRuntimePrimitiveEntries(nodes) {
|
|
@@ -8497,10 +9847,610 @@ function createProductionRuntimePrimitiveEntries(nodes) {
|
|
|
8497
9847
|
.filter((node) => node.kind === "primitive")
|
|
8498
9848
|
.map((node) => ({ node, resources: createProductionPrimitiveResources(node), currentLodIndex: 0 }));
|
|
8499
9849
|
}
|
|
9850
|
+
function blankProductionPrimitiveTextureState() {
|
|
9851
|
+
return { texturedMaterial: null, textureStatus: "none", textureSlots: [], textureWarnings: [], sdfText: null, textureBytes: 0, textureMipBytes: [] };
|
|
9852
|
+
}
|
|
9853
|
+
/**
|
|
9854
|
+
* C1 texture intent classification (pure, unit-tested). Asset refs resolve to
|
|
9855
|
+
* fetchable urls; procedural inputs have no rasterizer and are reported so
|
|
9856
|
+
* the caller can warn instead of silently dropping them.
|
|
9857
|
+
*/
|
|
9858
|
+
const ROOT_EXTENSION_TEXTURE_SLOTS = ["clearcoat", "clearcoatRoughness", "clearcoatNormal", "sheenColor", "sheenRoughness", "iridescence", "iridescenceThickness", "anisotropy"];
|
|
9859
|
+
export function createProductionPrimitiveTextureIntent(materialSpec) {
|
|
9860
|
+
const refUrl = (input) => input && typeof input === "object" && input.kind === "aura-asset-ref"
|
|
9861
|
+
? input.url ?? undefined
|
|
9862
|
+
: undefined;
|
|
9863
|
+
const procedurals = [];
|
|
9864
|
+
const noteProcedural = (slot, input) => {
|
|
9865
|
+
if (input && typeof input === "object" && input.kind === "aura-procedural-texture") {
|
|
9866
|
+
procedurals.push(`${slot}:${input.texture}`);
|
|
9867
|
+
}
|
|
9868
|
+
};
|
|
9869
|
+
noteProcedural("normal", materialSpec?.normal);
|
|
9870
|
+
noteProcedural("roughnessMap", materialSpec?.roughnessMap);
|
|
9871
|
+
noteProcedural("metalnessMap", materialSpec?.metalnessMap);
|
|
9872
|
+
noteProcedural("occlusionMap", materialSpec?.occlusionMap);
|
|
9873
|
+
noteProcedural("emissiveMap", materialSpec?.emissiveMap);
|
|
9874
|
+
const baseColorUrl = refUrl(materialSpec?.texture);
|
|
9875
|
+
const normalUrl = refUrl(materialSpec?.normal);
|
|
9876
|
+
const roughnessUrl = refUrl(materialSpec?.roughnessMap);
|
|
9877
|
+
const metalnessUrl = refUrl(materialSpec?.metalnessMap);
|
|
9878
|
+
const occlusionUrl = refUrl(materialSpec?.occlusionMap);
|
|
9879
|
+
const emissiveUrl = refUrl(materialSpec?.emissiveMap);
|
|
9880
|
+
const extensionMaps = Object.fromEntries(ROOT_EXTENSION_TEXTURE_SLOTS.flatMap((slot) => {
|
|
9881
|
+
const input = materialSpec?.[`${slot}Map`];
|
|
9882
|
+
if (input === undefined)
|
|
9883
|
+
return [];
|
|
9884
|
+
if (!input || typeof input !== "object" || input.kind !== "aura-asset-ref" || input.type !== "texture") {
|
|
9885
|
+
throw new AuraRuntimeError("unsupported-texture", `${slot}Map requires a typed texture asset reference`);
|
|
9886
|
+
}
|
|
9887
|
+
if (!["png", "jpg", "jpeg", "webp"].includes(input.format) || !input.url) {
|
|
9888
|
+
throw new AuraRuntimeError("unsupported-texture", `${slot}Map requires a fetchable png, jpg, jpeg or webp texture`);
|
|
9889
|
+
}
|
|
9890
|
+
const texCoord = materialSpec?.texCoords?.[slot] ?? 0;
|
|
9891
|
+
if (texCoord !== 0 && texCoord !== 1)
|
|
9892
|
+
throw new RangeError(`${slot} texCoord must be 0 or 1`);
|
|
9893
|
+
const transform = materialSpec?.texTransforms?.[slot];
|
|
9894
|
+
if (transform && [...(transform.offset ?? [0, 0]), ...(transform.scale ?? [1, 1]), transform.rotation ?? 0].some((value) => !Number.isFinite(value))) {
|
|
9895
|
+
throw new RangeError(`${slot} texture transform must contain finite values`);
|
|
9896
|
+
}
|
|
9897
|
+
return [[slot, { url: input.url, colorSpace: slot === "sheenColor" ? "srgb" : "linear", texCoord, ...(transform ? { transform } : {}) }]];
|
|
9898
|
+
}));
|
|
9899
|
+
return {
|
|
9900
|
+
...(baseColorUrl ? { baseColorUrl } : {}),
|
|
9901
|
+
...(normalUrl ? { normalUrl } : {}),
|
|
9902
|
+
...(roughnessUrl ? { roughnessUrl } : {}),
|
|
9903
|
+
...(metalnessUrl ? { metalnessUrl } : {}),
|
|
9904
|
+
...(occlusionUrl ? { occlusionUrl } : {}),
|
|
9905
|
+
...(emissiveUrl ? { emissiveUrl } : {}),
|
|
9906
|
+
...(Object.keys(extensionMaps).length ? { extensionMaps } : {}),
|
|
9907
|
+
proceduralInputs: procedurals
|
|
9908
|
+
};
|
|
9909
|
+
}
|
|
9910
|
+
/**
|
|
9911
|
+
* C1 metallic-roughness compositing (pure, unit-tested). glTF convention:
|
|
9912
|
+
* R = occlusion (unused here, forced to 255), G = roughness, B = metallic.
|
|
9913
|
+
* Missing channels fall back to the scalar spec values.
|
|
9914
|
+
*/
|
|
9915
|
+
const productionPrimitiveBitmapPixels = new WeakMap();
|
|
9916
|
+
async function loadProductionPrimitiveBitmap(url) {
|
|
9917
|
+
if (typeof fetch !== "function" || typeof createImageBitmap !== "function") {
|
|
9918
|
+
throw new Error("texture fetch requires fetch + createImageBitmap (browser production mount)");
|
|
9919
|
+
}
|
|
9920
|
+
const response = await fetch(url);
|
|
9921
|
+
if (!response.ok)
|
|
9922
|
+
throw new Error(`texture fetch failed with status ${response.status} for ${url}`);
|
|
9923
|
+
const blob = await response.blob();
|
|
9924
|
+
const bitmap = await createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" });
|
|
9925
|
+
try {
|
|
9926
|
+
const { decodePngTexturePixels } = await import("./PngTexturePixels.js");
|
|
9927
|
+
const pixels = await decodePngTexturePixels(new Uint8Array(await blob.arrayBuffer()));
|
|
9928
|
+
if (pixels)
|
|
9929
|
+
productionPrimitiveBitmapPixels.set(bitmap, pixels);
|
|
9930
|
+
return bitmap;
|
|
9931
|
+
}
|
|
9932
|
+
catch (error) {
|
|
9933
|
+
bitmap.close();
|
|
9934
|
+
throw error;
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
function bitmapRgbaPixels(bitmap) {
|
|
9938
|
+
const decoded = productionPrimitiveBitmapPixels.get(bitmap);
|
|
9939
|
+
if (decoded)
|
|
9940
|
+
return decoded;
|
|
9941
|
+
if (typeof document === "undefined")
|
|
9942
|
+
throw new Error("texture compositing requires a DOM canvas");
|
|
9943
|
+
const canvas = document.createElement("canvas");
|
|
9944
|
+
canvas.width = bitmap.width;
|
|
9945
|
+
canvas.height = bitmap.height;
|
|
9946
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
9947
|
+
if (!context)
|
|
9948
|
+
throw new Error("2d canvas unavailable for texture compositing");
|
|
9949
|
+
context.drawImage(bitmap, 0, 0);
|
|
9950
|
+
const image = context.getImageData(0, 0, bitmap.width, bitmap.height);
|
|
9951
|
+
return { width: bitmap.width, height: bitmap.height, data: new Uint8Array(image.data) };
|
|
9952
|
+
}
|
|
9953
|
+
/**
|
|
9954
|
+
* C1 post-mount textured upgrade (muse3jsparity-PRD). Runs fire-and-forget
|
|
9955
|
+
* after mount: scalar first frames stay fast and honest, and every outcome —
|
|
9956
|
+
* textured, fallback, or skipped — is recorded on the resource with warnings.
|
|
9957
|
+
* Procedural inputs have no rasterizer: recorded + warned, never faked.
|
|
9958
|
+
*/
|
|
9959
|
+
export async function upgradeProductionPrimitiveTextures(entries, warn, maxTextureSize = 4096) {
|
|
9960
|
+
for (const entry of entries) {
|
|
9961
|
+
for (const resource of entry.resources) {
|
|
9962
|
+
const spec = resource.materialSpec;
|
|
9963
|
+
const intent = createProductionPrimitiveTextureIntent(spec);
|
|
9964
|
+
for (const procedural of intent.proceduralInputs) {
|
|
9965
|
+
// Static channel (collectGeneratedCodeWarnings) already warns; record
|
|
9966
|
+
// per-resource without duplicating into runtime warnings.
|
|
9967
|
+
resource.textureWarnings.push(`procedural texture ${procedural} on "${resource.name}" has no rasterizer; recorded only, scalar material retained`);
|
|
9968
|
+
}
|
|
9969
|
+
const urls = [intent.baseColorUrl, intent.normalUrl, intent.roughnessUrl, intent.metalnessUrl, intent.occlusionUrl, intent.emissiveUrl, ...Object.values(intent.extensionMaps ?? {}).map((map) => map.url)].filter((url) => typeof url === "string" && url.length > 0);
|
|
9970
|
+
if (urls.length === 0)
|
|
9971
|
+
continue;
|
|
9972
|
+
if (resource.textureStatus !== "none")
|
|
9973
|
+
continue;
|
|
9974
|
+
resource.textureStatus = "pending";
|
|
9975
|
+
try {
|
|
9976
|
+
await upgradeProductionPrimitiveResource(resource, resource.sourceNode, spec, intent, maxTextureSize);
|
|
9977
|
+
}
|
|
9978
|
+
catch (error) {
|
|
9979
|
+
resource.textureStatus = "fallback";
|
|
9980
|
+
const message = `textured upgrade failed for "${resource.name}" (${error instanceof Error ? error.message : String(error)}); scalar material retained`;
|
|
9981
|
+
resource.textureWarnings.push(message);
|
|
9982
|
+
warn(message);
|
|
9983
|
+
}
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
}
|
|
9987
|
+
/**
|
|
9988
|
+
* B3 post-mount HDRI upgrade (muse3jsparity-PRD). Fetches a Radiance `.hdr`
|
|
9989
|
+
* asset, runs the HDR→cubemap→GGX-prefilter→BRDF-LUT chain, and returns the
|
|
9990
|
+
* live lighting object plus its disposal. Throws on fetch/parse failure so
|
|
9991
|
+
* the caller keeps the honest procedural fallback and warns.
|
|
9992
|
+
*/
|
|
9993
|
+
export async function upgradeProductionEnvironmentHdri(url, intensity, reflectionUrl, rotation) {
|
|
9994
|
+
if (typeof fetch !== "function") {
|
|
9995
|
+
throw new Error("HDRI upgrade requires fetch (browser production mount)");
|
|
9996
|
+
}
|
|
9997
|
+
const fetchRadiance = async (assetUrl) => {
|
|
9998
|
+
const response = await fetch(assetUrl);
|
|
9999
|
+
if (!response.ok)
|
|
10000
|
+
throw new Error(`HDRI fetch failed with status ${response.status} for ${assetUrl}`);
|
|
10001
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
10002
|
+
if (bytes.length === 0)
|
|
10003
|
+
throw new Error(`HDRI asset is empty for ${assetUrl}`);
|
|
10004
|
+
return bytes;
|
|
10005
|
+
};
|
|
10006
|
+
const clampIntensity = Math.max(0, intensity);
|
|
10007
|
+
const illumination = createProductionPbrHdrPipelineFromRadiance(await fetchRadiance(url), {
|
|
10008
|
+
id: "root-hdri-environment",
|
|
10009
|
+
label: "Root HDRI environment",
|
|
10010
|
+
intensity: clampIntensity,
|
|
10011
|
+
...(rotation !== undefined ? { rotation } : {})
|
|
10012
|
+
});
|
|
10013
|
+
if (!illumination.diagnostics.realRadianceHdr)
|
|
10014
|
+
throw new Error(`HDRI asset did not parse as Radiance HDR for ${url}`);
|
|
10015
|
+
if (!reflectionUrl) {
|
|
10016
|
+
const resources = createProductionEnvironmentLightingResources(illumination);
|
|
10017
|
+
return {
|
|
10018
|
+
lighting: resources.lighting,
|
|
10019
|
+
dispose: resources.dispose,
|
|
10020
|
+
maxLinearValue: illumination.diagnostics.maxLinearValue,
|
|
10021
|
+
specularMipCount: illumination.diagnostics.specularMipCount,
|
|
10022
|
+
dualProbe: false
|
|
10023
|
+
};
|
|
10024
|
+
}
|
|
10025
|
+
const reflection = createProductionPbrHdrPipelineFromRadiance(await fetchRadiance(reflectionUrl), {
|
|
10026
|
+
id: "root-hdri-reflection-environment",
|
|
10027
|
+
label: "Root HDRI reflection environment",
|
|
10028
|
+
intensity: clampIntensity,
|
|
10029
|
+
...(rotation !== undefined ? { rotation } : {})
|
|
10030
|
+
});
|
|
10031
|
+
if (!reflection.diagnostics.realRadianceHdr)
|
|
10032
|
+
throw new Error(`HDRI reflection asset did not parse as Radiance HDR for ${reflectionUrl}`);
|
|
10033
|
+
const dual = createDualProbeEnvironmentLightingResources({ illumination, reflection });
|
|
10034
|
+
return {
|
|
10035
|
+
lighting: dual.lighting,
|
|
10036
|
+
dispose: dual.dispose,
|
|
10037
|
+
maxLinearValue: Math.max(illumination.diagnostics.maxLinearValue, reflection.diagnostics.maxLinearValue),
|
|
10038
|
+
specularMipCount: reflection.diagnostics.specularMipCount,
|
|
10039
|
+
dualProbe: true
|
|
10040
|
+
};
|
|
10041
|
+
}
|
|
10042
|
+
async function upgradeProductionPrimitiveResource(resource, node, spec, intent, maxTextureSize) {
|
|
10043
|
+
const fail = (message) => new Error(message);
|
|
10044
|
+
if (!resource.geometry.vertexBuffer.format.hasAttribute("uv")) {
|
|
10045
|
+
throw fail(`"${resource.name}" geometry carries no uv set; textured upgrade needs generated uvs`);
|
|
10046
|
+
}
|
|
10047
|
+
const scalars = resolveProductionPrimitiveScalars(node);
|
|
10048
|
+
const extensionEntries = Object.entries(intent.extensionMaps ?? {});
|
|
10049
|
+
const needsScalarAtlas = extensionEntries.some(([slot]) => slot.startsWith("clearcoat"))
|
|
10050
|
+
&& extensionEntries.some(([slot]) => slot.startsWith("sheen") || slot === "anisotropy")
|
|
10051
|
+
&& extensionEntries.some(([slot]) => slot.startsWith("iridescence"));
|
|
10052
|
+
if (extensionEntries.some(([, map]) => map.texCoord === 1) && !resource.geometry.vertexBuffer.format.hasAttribute("uv1")) {
|
|
10053
|
+
throw fail(`"${resource.name}" geometry carries no uv1 set requested by extension texture`);
|
|
10054
|
+
}
|
|
10055
|
+
const ownedBitmaps = [];
|
|
10056
|
+
const ownedTextures = [];
|
|
10057
|
+
const bitmapLoads = new Map();
|
|
10058
|
+
const load = (url) => {
|
|
10059
|
+
let pending = bitmapLoads.get(url);
|
|
10060
|
+
if (!pending) {
|
|
10061
|
+
pending = loadProductionPrimitiveBitmap(url).then((bitmap) => {
|
|
10062
|
+
ownedBitmaps.push(bitmap);
|
|
10063
|
+
return bitmap;
|
|
10064
|
+
});
|
|
10065
|
+
bitmapLoads.set(url, pending);
|
|
10066
|
+
}
|
|
10067
|
+
return pending;
|
|
10068
|
+
};
|
|
10069
|
+
const own = (texture) => { ownedTextures.push(texture); return texture; };
|
|
10070
|
+
const disposeTextures = () => {
|
|
10071
|
+
for (const texture of ownedTextures)
|
|
10072
|
+
texture.dispose();
|
|
10073
|
+
for (const bitmap of ownedBitmaps)
|
|
10074
|
+
bitmap.close();
|
|
10075
|
+
ownedTextures.length = 0;
|
|
10076
|
+
ownedBitmaps.length = 0;
|
|
10077
|
+
};
|
|
10078
|
+
try {
|
|
10079
|
+
const baseResults = await Promise.allSettled([
|
|
10080
|
+
intent.baseColorUrl ? load(intent.baseColorUrl) : Promise.resolve(undefined),
|
|
10081
|
+
intent.normalUrl ? load(intent.normalUrl) : Promise.resolve(undefined),
|
|
10082
|
+
intent.roughnessUrl ? load(intent.roughnessUrl) : Promise.resolve(undefined),
|
|
10083
|
+
intent.metalnessUrl ? load(intent.metalnessUrl) : Promise.resolve(undefined),
|
|
10084
|
+
intent.occlusionUrl ? load(intent.occlusionUrl) : Promise.resolve(undefined),
|
|
10085
|
+
intent.emissiveUrl ? load(intent.emissiveUrl) : Promise.resolve(undefined)
|
|
10086
|
+
]);
|
|
10087
|
+
const failure = baseResults.find((result) => result.status === "rejected");
|
|
10088
|
+
if (failure?.status === "rejected")
|
|
10089
|
+
throw failure.reason;
|
|
10090
|
+
const [baseColorSource, normalSource, roughnessSource, metalnessSource, occlusionSource, emissiveSource] = baseResults.map((result) => result.status === "fulfilled" ? result.value : undefined);
|
|
10091
|
+
const slots = [];
|
|
10092
|
+
const extensionOptions = {};
|
|
10093
|
+
const extensionSources = [];
|
|
10094
|
+
const atlasPixels = {};
|
|
10095
|
+
for (const [slot, map] of extensionEntries) {
|
|
10096
|
+
const source = await load(map.url);
|
|
10097
|
+
extensionSources.push(source);
|
|
10098
|
+
if (needsScalarAtlas && ["clearcoat", "clearcoatRoughness", "sheenRoughness", "iridescence", "iridescenceThickness"].includes(slot)) {
|
|
10099
|
+
Object.assign(atlasPixels, { [slot]: bitmapRgbaPixels(source) });
|
|
10100
|
+
}
|
|
10101
|
+
extensionOptions[`${slot}Texture`] = own(new Texture({ width: source.width, height: source.height, source, colorSpace: map.colorSpace, label: `${resource.name}-${slot}` }));
|
|
10102
|
+
if (map.transform)
|
|
10103
|
+
extensionOptions[`${slot}TextureTransform`] = { ...map.transform };
|
|
10104
|
+
slots.push(slot);
|
|
10105
|
+
}
|
|
10106
|
+
if (needsScalarAtlas) {
|
|
10107
|
+
const { createExtensionScalarAtlas } = await import("../../rendering/extension-scalar-atlas.js");
|
|
10108
|
+
const atlas = createExtensionScalarAtlas(atlasPixels, maxTextureSize);
|
|
10109
|
+
own(atlas.texture);
|
|
10110
|
+
extensionOptions.extensionScalarAtlas = atlas;
|
|
10111
|
+
}
|
|
10112
|
+
const baseColorTexture = baseColorSource
|
|
10113
|
+
? own(new Texture({ width: baseColorSource.width, height: baseColorSource.height, source: baseColorSource, colorSpace: "srgb", label: `${resource.name}-basecolor` }))
|
|
10114
|
+
: undefined;
|
|
10115
|
+
if (baseColorTexture)
|
|
10116
|
+
slots.push("baseColor");
|
|
10117
|
+
const normalTexture = normalSource
|
|
10118
|
+
? own(new Texture({ width: normalSource.width, height: normalSource.height, source: normalSource, colorSpace: "linear", label: `${resource.name}-normal` }))
|
|
10119
|
+
: undefined;
|
|
10120
|
+
if (normalTexture)
|
|
10121
|
+
slots.push("normal");
|
|
10122
|
+
let metallicRoughnessTexture;
|
|
10123
|
+
if (roughnessSource ?? metalnessSource) {
|
|
10124
|
+
const base = roughnessSource ?? metalnessSource;
|
|
10125
|
+
const size = { width: base.width, height: base.height };
|
|
10126
|
+
const readPixels = (bitmap) => {
|
|
10127
|
+
if (!bitmap)
|
|
10128
|
+
return undefined;
|
|
10129
|
+
const decoded = bitmapRgbaPixels(bitmap);
|
|
10130
|
+
if (decoded.width !== size.width || decoded.height !== size.height) {
|
|
10131
|
+
const canvas = document.createElement("canvas");
|
|
10132
|
+
canvas.width = size.width;
|
|
10133
|
+
canvas.height = size.height;
|
|
10134
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
10135
|
+
if (!context)
|
|
10136
|
+
throw fail("2d canvas unavailable for texture compositing");
|
|
10137
|
+
context.drawImage(bitmap, 0, 0, size.width, size.height);
|
|
10138
|
+
return new Uint8Array(context.getImageData(0, 0, size.width, size.height).data);
|
|
10139
|
+
}
|
|
10140
|
+
return decoded.data;
|
|
10141
|
+
};
|
|
10142
|
+
const composited = compositeMetallicRoughnessPixels(readPixels(roughnessSource), readPixels(metalnessSource), size.width * size.height, spec?.roughness ?? 0.58, spec?.metallic ?? spec?.metalness ?? 0);
|
|
10143
|
+
metallicRoughnessTexture = own(new Texture({ width: size.width, height: size.height, data: composited, colorSpace: "linear", label: `${resource.name}-metallicroughness` }));
|
|
10144
|
+
slots.push("metallicRoughness");
|
|
10145
|
+
}
|
|
10146
|
+
const occlusionTexture = occlusionSource
|
|
10147
|
+
? own(new Texture({ width: occlusionSource.width, height: occlusionSource.height, source: occlusionSource, colorSpace: "linear", label: `${resource.name}-occlusion` }))
|
|
10148
|
+
: undefined;
|
|
10149
|
+
if (occlusionTexture)
|
|
10150
|
+
slots.push("occlusion");
|
|
10151
|
+
const emissiveTexture = emissiveSource
|
|
10152
|
+
? own(new Texture({ width: emissiveSource.width, height: emissiveSource.height, source: emissiveSource, colorSpace: "srgb", label: `${resource.name}-emissive` }))
|
|
10153
|
+
: undefined;
|
|
10154
|
+
if (emissiveTexture)
|
|
10155
|
+
slots.push("emissive");
|
|
10156
|
+
if (slots.length === 0)
|
|
10157
|
+
throw fail(`no texture resolved for "${resource.name}"`);
|
|
10158
|
+
// M2 streaming table: resident bytes from the decoded sources (base +
|
|
10159
|
+
// full mip-chain estimate) with the chain keyed off the largest source.
|
|
10160
|
+
const loadedDims = [baseColorSource, normalSource, roughnessSource, metalnessSource, occlusionSource, emissiveSource, ...extensionSources]
|
|
10161
|
+
.filter((source) => source !== undefined)
|
|
10162
|
+
.map((source) => ({ width: source.width, height: source.height }));
|
|
10163
|
+
const loadedBaseBytes = loadedDims.reduce((total, dims) => total + dims.width * dims.height * 4, 0);
|
|
10164
|
+
const largestDims = loadedDims.reduce((largest, dims) => (dims.width * dims.height > largest.width * largest.height ? dims : largest), { width: 1, height: 1 });
|
|
10165
|
+
resource.textureBytes = Math.round(loadedBaseBytes * (4 / 3));
|
|
10166
|
+
resource.textureMipBytes = mipChainBytesCoarseToFine(largestDims.width, largestDims.height);
|
|
10167
|
+
const texCoords = spec?.texCoords;
|
|
10168
|
+
// C3: every root textured slot shares one capability-gated sampler request
|
|
10169
|
+
// (default 8x where supported; the renderer clamps to the device maximum).
|
|
10170
|
+
const textureSampler = new Sampler({
|
|
10171
|
+
maxAnisotropy: resolveSamplerAnisotropy({ desired: spec?.textureAnisotropy }).applied
|
|
10172
|
+
});
|
|
10173
|
+
for (const [slot] of extensionEntries)
|
|
10174
|
+
extensionOptions[`${slot}Sampler`] = textureSampler;
|
|
10175
|
+
resource.texturedMaterial = new TexturedPBRMaterial({
|
|
10176
|
+
name: `a3d-production-textured-primitive-${resource.name}`,
|
|
10177
|
+
baseColor: [scalars.baseColor[0], scalars.baseColor[1], scalars.baseColor[2], scalars.opacity],
|
|
10178
|
+
metallic: clamp01(spec?.metallic ?? spec?.metalness ?? 0),
|
|
10179
|
+
roughness: clamp01(spec?.roughness ?? 0.58),
|
|
10180
|
+
emissiveColor: spec?.emissive
|
|
10181
|
+
? [scalars.emissiveColor[0], scalars.emissiveColor[1], scalars.emissiveColor[2]]
|
|
10182
|
+
: emissiveTexture ? [1, 1, 1] : [scalars.emissiveColor[0], scalars.emissiveColor[1], scalars.emissiveColor[2]],
|
|
10183
|
+
emissiveStrength: Math.max(0, spec?.emissiveIntensity ?? (spec?.emissive || emissiveTexture ? 1.35 : 0)),
|
|
10184
|
+
occlusionStrength: clamp01(spec?.occlusionStrength ?? 1),
|
|
10185
|
+
clearcoatFactor: scalars.clearcoat,
|
|
10186
|
+
clearcoatRoughnessFactor: clamp01(spec?.clearcoatRoughness ?? 0.34),
|
|
10187
|
+
clearcoatNormalScale: Math.max(0, spec?.clearcoatNormalScale ?? 1),
|
|
10188
|
+
...extensionOptions,
|
|
10189
|
+
sheenColorFactor: [scalars.sheenColor[0], scalars.sheenColor[1], scalars.sheenColor[2]],
|
|
10190
|
+
sheenRoughnessFactor: clamp01(spec?.sheenRoughness ?? 0.3),
|
|
10191
|
+
anisotropyStrength: scalars.anisotropy,
|
|
10192
|
+
anisotropyRotation: spec?.anisotropyRotation ?? 0,
|
|
10193
|
+
iridescenceFactor: scalars.iridescence,
|
|
10194
|
+
iridescenceIor: Math.max(1, spec?.iridescenceIOR ?? 1.3),
|
|
10195
|
+
...(scalars.thicknessRange ? { iridescenceThicknessMinimum: scalars.thicknessRange[0], iridescenceThicknessMaximum: scalars.thicknessRange[1] } : {}),
|
|
10196
|
+
transmissionFactor: scalars.transmission,
|
|
10197
|
+
...(spec?.thickness === undefined ? {} : { volumeThicknessFactor: Math.max(0, spec.thickness) }),
|
|
10198
|
+
...(spec?.ior === undefined ? {} : { ior: Math.max(1, spec.ior) }),
|
|
10199
|
+
...(spec?.attenuationColor ? { volumeAttenuationColor: colorToLinearRgb(spec.attenuationColor) } : {}),
|
|
10200
|
+
...(spec?.attenuationDistance === undefined ? {} : { volumeAttenuationDistance: Math.max(0, spec.attenuationDistance) }),
|
|
10201
|
+
environmentIntensity: scalars.environmentIntensity,
|
|
10202
|
+
envMapIntensity: scalars.envMapIntensity,
|
|
10203
|
+
renderState: {
|
|
10204
|
+
blend: scalars.opacity < 0.999,
|
|
10205
|
+
depthWrite: scalars.opacity >= 0.999,
|
|
10206
|
+
cullMode: node.primitive === "plane" || scalars.opacity < 0.999 ? "none" : "back"
|
|
10207
|
+
},
|
|
10208
|
+
...(baseColorTexture ? { baseColorTexture, baseColorSampler: textureSampler } : {}),
|
|
10209
|
+
...(normalTexture ? { normalTexture, normalSampler: textureSampler, normalScale: spec?.normalScale ?? 1 } : {}),
|
|
10210
|
+
...(metallicRoughnessTexture ? { metallicRoughnessTexture, metallicRoughnessSampler: textureSampler } : {}),
|
|
10211
|
+
...(occlusionTexture ? { occlusionTexture, occlusionSampler: textureSampler } : {}),
|
|
10212
|
+
...(emissiveTexture ? { emissiveTexture, emissiveSampler: textureSampler } : {}),
|
|
10213
|
+
...(spec?.texTransforms?.baseColor ? { baseColorTextureTransform: { ...spec.texTransforms.baseColor } } : {}),
|
|
10214
|
+
...(spec?.texTransforms?.normal ? { normalTextureTransform: { ...spec.texTransforms.normal } } : {}),
|
|
10215
|
+
...(spec?.texTransforms?.metallicRoughness ? { metallicRoughnessTextureTransform: { ...spec.texTransforms.metallicRoughness } } : {}),
|
|
10216
|
+
...(spec?.texTransforms?.occlusion ? { occlusionTextureTransform: { ...spec.texTransforms.occlusion } } : {}),
|
|
10217
|
+
...(spec?.texTransforms?.emissive ? { emissiveTextureTransform: { ...spec.texTransforms.emissive } } : {}),
|
|
10218
|
+
...((texCoords?.baseColor ?? 0) > 0 || (texCoords?.normal ?? 0) > 0 || (texCoords?.metallicRoughness ?? 0) > 0 || (texCoords?.occlusion ?? 0) > 0 || (texCoords?.emissive ?? 0) > 0 || extensionEntries.length > 0
|
|
10219
|
+
? {
|
|
10220
|
+
textureTexCoords: {
|
|
10221
|
+
...Object.fromEntries(extensionEntries.map(([slot, map]) => [slot, map.texCoord])),
|
|
10222
|
+
...(baseColorTexture && (texCoords?.baseColor ?? 0) > 0 ? { baseColor: 1 } : {}),
|
|
10223
|
+
...(normalTexture && (texCoords?.normal ?? 0) > 0 ? { normal: 1 } : {}),
|
|
10224
|
+
...(metallicRoughnessTexture && (texCoords?.metallicRoughness ?? 0) > 0 ? { metallicRoughness: 1 } : {}),
|
|
10225
|
+
...(occlusionTexture && (texCoords?.occlusion ?? 0) > 0 ? { occlusion: 1 } : {}),
|
|
10226
|
+
...(emissiveTexture && (texCoords?.emissive ?? 0) > 0 ? { emissive: 1 } : {})
|
|
10227
|
+
}
|
|
10228
|
+
}
|
|
10229
|
+
: {})
|
|
10230
|
+
});
|
|
10231
|
+
if (resource.material.disposed) {
|
|
10232
|
+
resource.texturedMaterial.dispose();
|
|
10233
|
+
resource.texturedMaterial = null;
|
|
10234
|
+
throw fail("primitive disposed while texture upgrade was pending");
|
|
10235
|
+
}
|
|
10236
|
+
resource.textureDisposer = disposeTextures;
|
|
10237
|
+
resource.textureStatus = "textured";
|
|
10238
|
+
resource.textureSlots = slots;
|
|
10239
|
+
}
|
|
10240
|
+
catch (error) {
|
|
10241
|
+
disposeTextures();
|
|
10242
|
+
throw error;
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
export function compositeMetallicRoughnessPixels(rough, metal, pixelCount, roughnessScalar, metalnessScalar) {
|
|
10246
|
+
const out = new Uint8Array(pixelCount * 4);
|
|
10247
|
+
const fallbackG = Math.round(clamp01(roughnessScalar) * 255);
|
|
10248
|
+
const fallbackB = Math.round(clamp01(metalnessScalar) * 255);
|
|
10249
|
+
for (let pixel = 0; pixel < pixelCount; pixel += 1) {
|
|
10250
|
+
const base = pixel * 4;
|
|
10251
|
+
out[base] = 255;
|
|
10252
|
+
out[base + 1] = rough ? rough[base + 1] ?? fallbackG : fallbackG;
|
|
10253
|
+
out[base + 2] = metal ? metal[base + 2] ?? fallbackB : fallbackB;
|
|
10254
|
+
out[base + 3] = 255;
|
|
10255
|
+
}
|
|
10256
|
+
return out;
|
|
10257
|
+
}
|
|
10258
|
+
/**
|
|
10259
|
+
* G1 SDF text resource (muse3jsparity-PRD): replays the recorded descriptor
|
|
10260
|
+
* through the atlas sampler at mount, uploads the label image as a native
|
|
10261
|
+
* texture, and submits atlas-derived quads. Returns null (extruded fallback)
|
|
10262
|
+
* with a warning when the sampler cannot run — never a silent mesh swap.
|
|
10263
|
+
*/
|
|
10264
|
+
function createSdfTextPrimitiveResource(node, warn) {
|
|
10265
|
+
const text = node.text3D;
|
|
10266
|
+
if (!text || text.backend !== "sdf")
|
|
10267
|
+
return null;
|
|
10268
|
+
try {
|
|
10269
|
+
const layout = layoutSdfText(text.text, rootSdfFontAtlas(), {
|
|
10270
|
+
size: text.sdfSize ?? 1,
|
|
10271
|
+
...(text.sdfLetterSpacing === undefined ? {} : { letterSpacing: text.sdfLetterSpacing }),
|
|
10272
|
+
...(text.sdfStyle === undefined ? {} : { style: text.sdfStyle })
|
|
10273
|
+
});
|
|
10274
|
+
const style = layout.style;
|
|
10275
|
+
const styled = text.sdfStyle !== undefined && (style.outlineWidthEm > 0 || style.glowRadiusEm > 0 || style.shadowOffsetEm !== undefined);
|
|
10276
|
+
const image = rasterizeSdfTextLabelImage(layout, rootSdfFontAtlas(), {
|
|
10277
|
+
texelsPerWorldUnit: 64,
|
|
10278
|
+
...(styled
|
|
10279
|
+
? {
|
|
10280
|
+
outline: [1, 0.85, 0.6, 1],
|
|
10281
|
+
glow: [0.5, 0.8, 1, 1],
|
|
10282
|
+
shadow: [0, 0, 0, 1]
|
|
10283
|
+
}
|
|
10284
|
+
: {})
|
|
10285
|
+
});
|
|
10286
|
+
const mesh = createSdfTextQuadMesh(layout, image);
|
|
10287
|
+
const vertexCount = mesh.vertexCount;
|
|
10288
|
+
const vertices = new VertexBuffer(VertexFormat.P3N3T4T2T2, vertexCount);
|
|
10289
|
+
for (let index = 0; index < vertexCount; index += 1) {
|
|
10290
|
+
vertices.setAttribute(index, "position", [mesh.positions[index * 3] ?? 0, mesh.positions[index * 3 + 1] ?? 0, 0]);
|
|
10291
|
+
vertices.setAttribute(index, "normal", [0, 0, 1]);
|
|
10292
|
+
vertices.setAttribute(index, "tangent", [1, 0, 0, 1]);
|
|
10293
|
+
vertices.setAttribute(index, "uv", [mesh.uvs[index * 2] ?? 0, mesh.uvs[index * 2 + 1] ?? 0]);
|
|
10294
|
+
vertices.setAttribute(index, "uv1", [mesh.uvs[index * 2] ?? 0, mesh.uvs[index * 2 + 1] ?? 0]);
|
|
10295
|
+
}
|
|
10296
|
+
const geometry = new Geometry(vertices, new IndexBuffer(mesh.indices, vertexCount), "triangles", { min: [mesh.min[0], mesh.min[1], mesh.min[2]], max: [mesh.max[0], mesh.max[1], mesh.max[2]] });
|
|
10297
|
+
const opacity = clamp01(node.material?.opacity ?? 1);
|
|
10298
|
+
const labelTexture = new Texture({
|
|
10299
|
+
width: image.width,
|
|
10300
|
+
height: image.height,
|
|
10301
|
+
data: image.data,
|
|
10302
|
+
colorSpace: "srgb",
|
|
10303
|
+
label: `${node.name ?? "sdf-text"}-sdf-label`
|
|
10304
|
+
});
|
|
10305
|
+
const sampler = new Sampler({ maxAnisotropy: resolveSamplerAnisotropy({ desired: node.material?.textureAnisotropy }).applied });
|
|
10306
|
+
const texturedMaterial = new TexturedPBRMaterial({
|
|
10307
|
+
name: `a3d-production-sdf-text-${node.name ?? "label"}`,
|
|
10308
|
+
baseColor: [1, 1, 1, opacity],
|
|
10309
|
+
metallic: 0,
|
|
10310
|
+
roughness: 0.9,
|
|
10311
|
+
emissiveColor: [0.92, 0.92, 0.92],
|
|
10312
|
+
emissiveStrength: 0.9,
|
|
10313
|
+
occlusionStrength: 0,
|
|
10314
|
+
environmentIntensity: 0,
|
|
10315
|
+
renderState: { blend: true, depthWrite: false, cullMode: "none" },
|
|
10316
|
+
baseColorTexture: labelTexture,
|
|
10317
|
+
baseColorSampler: sampler,
|
|
10318
|
+
emissiveTexture: labelTexture,
|
|
10319
|
+
emissiveSampler: sampler
|
|
10320
|
+
});
|
|
10321
|
+
const mipBytes = mipChainBytesCoarseToFine(image.width, image.height);
|
|
10322
|
+
return {
|
|
10323
|
+
geometry,
|
|
10324
|
+
material: createProductionPrimitiveMaterial(node),
|
|
10325
|
+
bounds: { min: [mesh.min[0], mesh.min[1], mesh.min[2]], max: [mesh.max[0], mesh.max[1], mesh.max[2]] },
|
|
10326
|
+
name: node.name ?? "sdf-text",
|
|
10327
|
+
materialSpec: node.material,
|
|
10328
|
+
sourceNode: node,
|
|
10329
|
+
...blankProductionPrimitiveTextureState(),
|
|
10330
|
+
texturedMaterial,
|
|
10331
|
+
textureStatus: "textured",
|
|
10332
|
+
textureSlots: ["baseColor", "emissive"],
|
|
10333
|
+
textureWarnings: [],
|
|
10334
|
+
sdfText: {
|
|
10335
|
+
quadCount: mesh.quadCount,
|
|
10336
|
+
imageBytes: image.width * image.height * 4,
|
|
10337
|
+
...(style.lodFadeNear === undefined ? {} : { lodFadeNear: style.lodFadeNear }),
|
|
10338
|
+
...(style.lodFadeFar === undefined ? {} : { lodFadeFar: style.lodFadeFar }),
|
|
10339
|
+
occlusionPolicy: (text.sdfOcclusion ?? "dim"),
|
|
10340
|
+
lastOpacity: opacity,
|
|
10341
|
+
lastVisible: true,
|
|
10342
|
+
lastSubmitted: false
|
|
10343
|
+
},
|
|
10344
|
+
textureBytes: Math.round(image.width * image.height * 4 * (4 / 3)),
|
|
10345
|
+
textureMipBytes: mipBytes
|
|
10346
|
+
};
|
|
10347
|
+
}
|
|
10348
|
+
catch (error) {
|
|
10349
|
+
warn(`SDF text sampler failed for "${node.name ?? "sdf-text"}" (${error instanceof Error ? error.message : String(error)}); extruded mesh fallback retained`);
|
|
10350
|
+
return null;
|
|
10351
|
+
}
|
|
10352
|
+
}
|
|
10353
|
+
/**
|
|
10354
|
+
* M2 mip-chain byte estimate (pure, unit-tested): full chain from the base
|
|
10355
|
+
* level, coarse-to-fine, RGBA8. Matches the GPU residency the bridge funds.
|
|
10356
|
+
*/
|
|
10357
|
+
export function mipChainBytesCoarseToFine(width, height) {
|
|
10358
|
+
if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(height) || height <= 0) {
|
|
10359
|
+
throw new Error("Mip-chain bytes require positive integer dimensions.");
|
|
10360
|
+
}
|
|
10361
|
+
const levels = [];
|
|
10362
|
+
let w = width;
|
|
10363
|
+
let h = height;
|
|
10364
|
+
while (true) {
|
|
10365
|
+
levels.unshift(w * h * 4);
|
|
10366
|
+
if (w === 1 && h === 1)
|
|
10367
|
+
break;
|
|
10368
|
+
w = Math.max(1, Math.floor(w / 2));
|
|
10369
|
+
h = Math.max(1, Math.floor(h / 2));
|
|
10370
|
+
}
|
|
10371
|
+
return levels;
|
|
10372
|
+
}
|
|
10373
|
+
/** M2 streaming budget normalization (pure, unit-tested): default 256 MiB, fail-closed. */
|
|
10374
|
+
export function normalizeTextureBudgetBytes(value) {
|
|
10375
|
+
if (value === undefined)
|
|
10376
|
+
return 256 * 1024 * 1024;
|
|
10377
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
10378
|
+
throw new Error("Aura3D textureBudgetBytes must be a positive byte count.");
|
|
10379
|
+
}
|
|
10380
|
+
return Math.floor(value);
|
|
10381
|
+
}
|
|
10382
|
+
/**
|
|
10383
|
+
* M2 streaming residency from the post-upgrade texture table (pure,
|
|
10384
|
+
* unit-tested): distance-prioritized mip funding against the budget with
|
|
10385
|
+
* over-budget telemetry for the unfunded tail.
|
|
10386
|
+
*/
|
|
10387
|
+
export function describeTextureStreamingResidency(table, budgetBytes) {
|
|
10388
|
+
const candidates = table.map((entry) => ({
|
|
10389
|
+
id: entry.id,
|
|
10390
|
+
mipBytesCoarseToFine: entry.mipBytesCoarseToFine,
|
|
10391
|
+
distanceMeters: entry.distanceMeters
|
|
10392
|
+
}));
|
|
10393
|
+
return evaluateDistancePrioritizedMipResidency(candidates, normalizeTextureBudgetBytes(budgetBytes));
|
|
10394
|
+
}
|
|
10395
|
+
/**
|
|
10396
|
+
* N1 spot shadow observation (pure, unit-tested): cone + atlas tier from the
|
|
10397
|
+
* authored spot, pixel-backing gated on the device-observed map signals with
|
|
10398
|
+
* the spot as caster. No signal, no claim.
|
|
10399
|
+
*/
|
|
10400
|
+
export function describeProductionSpotShadow(input) {
|
|
10401
|
+
if (!input.requested || !input.casterIsSpot || input.angle === undefined) {
|
|
10402
|
+
return {
|
|
10403
|
+
requested: input.requested,
|
|
10404
|
+
casterIsSpot: input.casterIsSpot,
|
|
10405
|
+
...(input.casterName === undefined ? {} : { casterName: input.casterName }),
|
|
10406
|
+
spotPixelBacked: false,
|
|
10407
|
+
reason: !input.requested
|
|
10408
|
+
? "no authored spot requested a shadow map"
|
|
10409
|
+
: !input.casterIsSpot
|
|
10410
|
+
? "shadow requested but the caster slot went to a non-spot light"
|
|
10411
|
+
: "spot caster has no cone description"
|
|
10412
|
+
};
|
|
10413
|
+
}
|
|
10414
|
+
// Tier selection validates the cone fail-closed (RangeError on bad angle);
|
|
10415
|
+
// the full projection matrix composes in-device from this cone + range.
|
|
10416
|
+
const tier = selectSpotShadowAtlasTier(input.angle);
|
|
10417
|
+
createSpotShadowProjection(input.angle, Math.max(1, input.range ?? 12));
|
|
10418
|
+
const backed = input.mapRendered && input.mapSampled;
|
|
10419
|
+
return {
|
|
10420
|
+
requested: true,
|
|
10421
|
+
casterIsSpot: true,
|
|
10422
|
+
...(input.casterName === undefined ? {} : { casterName: input.casterName }),
|
|
10423
|
+
angle: input.angle,
|
|
10424
|
+
...(input.penumbra === undefined ? {} : { penumbra: input.penumbra }),
|
|
10425
|
+
range: Math.max(1, input.range ?? 12),
|
|
10426
|
+
atlasResolution: tier.resolution,
|
|
10427
|
+
atlasReason: tier.reason,
|
|
10428
|
+
spotPixelBacked: backed,
|
|
10429
|
+
reason: backed
|
|
10430
|
+
? `spot shadow map rendered and sampled (${tier.resolution}px ${tier.reason})`
|
|
10431
|
+
: "spot caster selected but the device shows no rendered+sampled shadow map yet"
|
|
10432
|
+
};
|
|
10433
|
+
}
|
|
8500
10434
|
function createProductionPrimitiveResources(node) {
|
|
10435
|
+
const sdfWarnings = [];
|
|
10436
|
+
const sdfResource = createSdfTextPrimitiveResource(node, (message) => { sdfWarnings.push(message); });
|
|
8501
10437
|
const levels = node.lod?.levels;
|
|
8502
10438
|
if (!levels?.length) {
|
|
8503
|
-
|
|
10439
|
+
if (sdfResource)
|
|
10440
|
+
return [sdfResource];
|
|
10441
|
+
const fallback = {
|
|
10442
|
+
geometry: createProductionPrimitiveGeometry(node),
|
|
10443
|
+
material: createProductionPrimitiveMaterial(node),
|
|
10444
|
+
bounds: primitiveGeometryBounds(node),
|
|
10445
|
+
name: node.name ?? node.primitive,
|
|
10446
|
+
materialSpec: node.material,
|
|
10447
|
+
sourceNode: node,
|
|
10448
|
+
...blankProductionPrimitiveTextureState()
|
|
10449
|
+
};
|
|
10450
|
+
// Sampler failures stay visible on the fallback resource (surfaced via
|
|
10451
|
+
// texturedMaterials diagnostics), never swallowed by the mesh swap.
|
|
10452
|
+
fallback.textureWarnings.push(...sdfWarnings);
|
|
10453
|
+
return [fallback];
|
|
8504
10454
|
}
|
|
8505
10455
|
return levels.map((level, index) => {
|
|
8506
10456
|
if (!level.primitive && !level.geometry)
|
|
@@ -8512,18 +10462,40 @@ function createProductionPrimitiveResources(node) {
|
|
|
8512
10462
|
material: level.material ?? node.material,
|
|
8513
10463
|
lod: undefined
|
|
8514
10464
|
};
|
|
8515
|
-
|
|
10465
|
+
const levelWarnings = [];
|
|
10466
|
+
const levelSdf = createSdfTextPrimitiveResource(levelNode, (message) => { levelWarnings.push(message); });
|
|
10467
|
+
if (levelSdf)
|
|
10468
|
+
return { ...levelSdf, name: level.name };
|
|
10469
|
+
const levelFallback = {
|
|
10470
|
+
geometry: createProductionPrimitiveGeometry(levelNode),
|
|
10471
|
+
material: createProductionPrimitiveMaterial(levelNode),
|
|
10472
|
+
bounds: primitiveGeometryBounds(levelNode),
|
|
10473
|
+
name: level.name,
|
|
10474
|
+
materialSpec: levelNode.material,
|
|
10475
|
+
sourceNode: levelNode,
|
|
10476
|
+
...blankProductionPrimitiveTextureState()
|
|
10477
|
+
};
|
|
10478
|
+
levelFallback.textureWarnings.push(...levelWarnings);
|
|
10479
|
+
return levelFallback;
|
|
8516
10480
|
});
|
|
8517
10481
|
}
|
|
8518
|
-
function selectProductionPrimitiveResource(entry, node, cameraPosition) {
|
|
10482
|
+
function selectProductionPrimitiveResource(entry, node, cameraPosition, lodBias = 1) {
|
|
8519
10483
|
if (!node.lod?.levels.length)
|
|
8520
10484
|
return entry.resources[0];
|
|
8521
10485
|
const position = node.position ?? [0, 0, 0];
|
|
8522
10486
|
const distance = Math.hypot(cameraPosition[0] - position[0], cameraPosition[1] - position[1], cameraPosition[2] - position[2]);
|
|
8523
|
-
const selection = selectAuraRootLodLevel(distance, node.lod.levels, entry.currentLodIndex, node.lod.hysteresis ?? 0);
|
|
10487
|
+
const selection = selectAuraRootLodLevel(distance * lodBias, node.lod.levels, entry.currentLodIndex, node.lod.hysteresis ?? 0);
|
|
8524
10488
|
entry.currentLodIndex = selection.levelIndex;
|
|
8525
10489
|
return entry.resources[selection.levelIndex] ?? entry.resources[entry.resources.length - 1];
|
|
8526
10490
|
}
|
|
10491
|
+
function createProductionModelInstanceTransforms(transforms, node, bounds, time) {
|
|
10492
|
+
const matrices = new Float32Array(transforms.length * 16);
|
|
10493
|
+
transforms.forEach((transform, index) => {
|
|
10494
|
+
const localNode = { ...node, ...transform };
|
|
10495
|
+
matrices.set(createModelMatrix(localNode, bounds, shouldNormalizeModelNode(node), time), index * 16);
|
|
10496
|
+
});
|
|
10497
|
+
return matrices;
|
|
10498
|
+
}
|
|
8527
10499
|
function createProductionInstanceTransforms(transforms, node) {
|
|
8528
10500
|
const matrices = new Float32Array(transforms.length * 16);
|
|
8529
10501
|
transforms.forEach((transform, index) => {
|
|
@@ -8559,37 +10531,7 @@ function resolveProductionPrimitiveRuntimeState(entry, runtimeNodes) {
|
|
|
8559
10531
|
visible: runtimeSnapshot.visible
|
|
8560
10532
|
};
|
|
8561
10533
|
}
|
|
8562
|
-
|
|
8563
|
-
* Build the production `PBRMaterial` for a primitive node.
|
|
8564
|
-
*
|
|
8565
|
-
* ## WS-2.1a — the parameter drop this function used to be
|
|
8566
|
-
*
|
|
8567
|
-
* Until 1.6 this forwarded `clearcoat` and **nothing else** from the extended material surface.
|
|
8568
|
-
* `AuraMaterialSpec` accepts `sheen`, `sheenRoughness`, `sheenColor`, `iridescence`,
|
|
8569
|
-
* `iridescenceIOR`, `iridescenceThicknessRange`, `anisotropy`, `anisotropyRotation`, `transmission`,
|
|
8570
|
-
* `thickness`, `ior`, `attenuationColor` and `attenuationDistance`; `PBRMaterial` accepts a uniform
|
|
8571
|
-
* for every one of them and binds them all. The bridge between the two dropped them on the floor.
|
|
8572
|
-
*
|
|
8573
|
-
* How that presented, measured by `tools/material-structural-parity` before the fix:
|
|
8574
|
-
*
|
|
8575
|
-
* ```
|
|
8576
|
-
* material.pbr({ ..., sheen: 1 }) -> byte-identical frame to sheen: 0
|
|
8577
|
-
* material.pbr({ ..., iridescence: 1 }) -> byte-identical frame to iridescence: 0
|
|
8578
|
-
* material.pbr({ ..., clearcoat: 1 }) -> byte-identical frame to clearcoat: 0
|
|
8579
|
-
* ```
|
|
8580
|
-
*
|
|
8581
|
-
* `clearcoat` was *forwarded* and still produced an identical frame, which pins the second, separate
|
|
8582
|
-
* defect: `a3dPbrExtensionEnvironmentLight` in `ShaderChunks.ts` adds its lobes to
|
|
8583
|
-
* `specularRadiance`, a term that is zero without an environment map. Forwarding alone is necessary
|
|
8584
|
-
* and not sufficient, so `environmentIntensity` now also has a floor when an extension factor is
|
|
8585
|
-
* present — otherwise a developer setting `clearcoat: 1` on a scene with no environment sees nothing
|
|
8586
|
-
* and has no way to find out why.
|
|
8587
|
-
*
|
|
8588
|
-
* The reason this survived so long is worth stating: three of the four lobes are scalar
|
|
8589
|
-
* approximations rather than real BRDFs, and **nobody could tell**, because the parameters never
|
|
8590
|
-
* arrived. The plumbing defect concealed the shading defect.
|
|
8591
|
-
*/
|
|
8592
|
-
function createProductionPrimitiveMaterial(node) {
|
|
10534
|
+
function resolveProductionPrimitiveScalars(node) {
|
|
8593
10535
|
const materialSpec = node.material;
|
|
8594
10536
|
const baseColor = colorToLinearRgba(materialSpec?.color ?? materialSpec?.emissive ?? "#d7dee8");
|
|
8595
10537
|
const opacity = clamp01(materialSpec?.opacity ?? baseColor[3] ?? 1);
|
|
@@ -8620,6 +10562,17 @@ function createProductionPrimitiveMaterial(node) {
|
|
|
8620
10562
|
const environmentIntensity = declaresExtension
|
|
8621
10563
|
? Math.max(0.35, requestedEnvironmentIntensity)
|
|
8622
10564
|
: requestedEnvironmentIntensity;
|
|
10565
|
+
// B3: the sampled env-map scale defaults to 1 (no look change) and honors
|
|
10566
|
+
// an explicitly authored envMapIntensity, including 0 (killed response).
|
|
10567
|
+
const envMapIntensity = materialSpec?.envMapIntensity === undefined ? 1 : Math.max(0, materialSpec.envMapIntensity);
|
|
10568
|
+
return {
|
|
10569
|
+
materialSpec, baseColor, opacity, emissiveColor, clearcoat, sheen, iridescence,
|
|
10570
|
+
anisotropy, transmission, thicknessRange, sheenColor, declaresExtension, environmentIntensity, envMapIntensity
|
|
10571
|
+
};
|
|
10572
|
+
}
|
|
10573
|
+
function createProductionPrimitiveMaterial(node) {
|
|
10574
|
+
const materialSpec = node.material;
|
|
10575
|
+
const { baseColor, opacity, emissiveColor, clearcoat, sheenColor, declaresExtension, transmission, thicknessRange, sheen, iridescence, anisotropy, environmentIntensity, envMapIntensity } = resolveProductionPrimitiveScalars(node);
|
|
8623
10576
|
/*
|
|
8624
10577
|
* Native instance submission requires the renderer's instanced shader contract. The ordinary
|
|
8625
10578
|
* PBR shader intentionally has no instance matrix inputs and ForwardPass therefore expands it.
|
|
@@ -8637,6 +10590,7 @@ function createProductionPrimitiveMaterial(node) {
|
|
|
8637
10590
|
emissiveColor,
|
|
8638
10591
|
emissiveStrength: Math.max(0, materialSpec?.emissiveIntensity ?? (materialSpec?.emissive ? 1.35 : 0)),
|
|
8639
10592
|
environmentIntensity,
|
|
10593
|
+
envMapIntensity,
|
|
8640
10594
|
renderState: {
|
|
8641
10595
|
blend: opacity < 0.999,
|
|
8642
10596
|
depthWrite: opacity >= 0.999,
|
|
@@ -8667,6 +10621,7 @@ function createProductionPrimitiveMaterial(node) {
|
|
|
8667
10621
|
...(materialSpec?.attenuationColor ? { volumeAttenuationColor: colorToLinearRgb(materialSpec.attenuationColor) } : {}),
|
|
8668
10622
|
...(materialSpec?.attenuationDistance === undefined ? {} : { volumeAttenuationDistance: Math.max(0, materialSpec.attenuationDistance) }),
|
|
8669
10623
|
environmentIntensity,
|
|
10624
|
+
envMapIntensity,
|
|
8670
10625
|
renderState: {
|
|
8671
10626
|
blend: opacity < 0.999,
|
|
8672
10627
|
depthWrite: opacity >= 0.999,
|
|
@@ -8689,6 +10644,12 @@ function primitiveGeometryBounds(nodeOrPrimitive) {
|
|
|
8689
10644
|
throw new Error("Aura3D custom primitive requires geometry.");
|
|
8690
10645
|
return createProductionPrimitiveMesh(primitive).bounds;
|
|
8691
10646
|
}
|
|
10647
|
+
function tilingSecondUnwrap(uvs) {
|
|
10648
|
+
const uv1s = new Float32Array(uvs.length);
|
|
10649
|
+
for (let i = 0; i < uvs.length; i += 1)
|
|
10650
|
+
uv1s[i] = (uvs[i] ?? 0) * 2;
|
|
10651
|
+
return uv1s;
|
|
10652
|
+
}
|
|
8692
10653
|
function createProductionPrimitiveMesh(primitive) {
|
|
8693
10654
|
if (primitive === "sphere")
|
|
8694
10655
|
return createSphereGeometry();
|
|
@@ -8740,11 +10701,24 @@ function calculateCustomGeometryNormals(spec) {
|
|
|
8740
10701
|
}
|
|
8741
10702
|
function createProductionGeometryFromPrimitiveMesh(mesh) {
|
|
8742
10703
|
const vertexCount = mesh.positions.length / 3;
|
|
8743
|
-
const
|
|
10704
|
+
const textured = Boolean(mesh.uvs && mesh.uvs.length === vertexCount * 2
|
|
10705
|
+
&& mesh.tangents && mesh.tangents.length === vertexCount * 4);
|
|
10706
|
+
const vertices = new VertexBuffer(textured ? VertexFormat.P3N3T4T2T2 : VertexFormat.P3N3, vertexCount);
|
|
8744
10707
|
for (let index = 0; index < vertexCount; index += 1) {
|
|
8745
10708
|
const base = index * 3;
|
|
8746
10709
|
vertices.setAttribute(index, "position", [mesh.positions[base] ?? 0, mesh.positions[base + 1] ?? 0, mesh.positions[base + 2] ?? 0]);
|
|
8747
10710
|
vertices.setAttribute(index, "normal", [mesh.normals[base] ?? 0, mesh.normals[base + 1] ?? 1, mesh.normals[base + 2] ?? 0]);
|
|
10711
|
+
if (textured) {
|
|
10712
|
+
const uvBase = index * 2;
|
|
10713
|
+
const tangentBase = index * 4;
|
|
10714
|
+
vertices.setAttribute(index, "tangent", [
|
|
10715
|
+
mesh.tangents[tangentBase] ?? 1, mesh.tangents[tangentBase + 1] ?? 0,
|
|
10716
|
+
mesh.tangents[tangentBase + 2] ?? 0, mesh.tangents[tangentBase + 3] ?? 1
|
|
10717
|
+
]);
|
|
10718
|
+
vertices.setAttribute(index, "uv", [mesh.uvs[uvBase] ?? 0, mesh.uvs[uvBase + 1] ?? 0]);
|
|
10719
|
+
const uv1 = mesh.uv1s && mesh.uv1s.length === vertexCount * 2 ? mesh.uv1s : mesh.uvs;
|
|
10720
|
+
vertices.setAttribute(index, "uv1", [uv1[uvBase] ?? 0, uv1[uvBase + 1] ?? 0]);
|
|
10721
|
+
}
|
|
8748
10722
|
}
|
|
8749
10723
|
return new Geometry(vertices, new IndexBuffer(Array.from(mesh.indices), vertexCount), "triangles", mesh.bounds);
|
|
8750
10724
|
}
|
|
@@ -8766,10 +10740,12 @@ function resolveProductionActorRuntimeState(entry, runtimeNodes) {
|
|
|
8766
10740
|
animation: runtimeSnapshot.animation ?? entry.node.animation
|
|
8767
10741
|
},
|
|
8768
10742
|
animationBinding: runtimeSnapshot.animationBinding,
|
|
10743
|
+
animationPose: runtimeSnapshot.animationPose,
|
|
10744
|
+
animationPoseTime: runtimeSnapshot.animationPoseBinding?.localTime ?? runtimeSnapshot.animationPoseBinding?.captureTime,
|
|
8769
10745
|
...(Object.keys(morphTargets).length > 0 ? { morphTargets } : {})
|
|
8770
10746
|
};
|
|
8771
10747
|
}
|
|
8772
|
-
function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings) {
|
|
10748
|
+
function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings, modelMatrix, refreshAfterMovement) {
|
|
8773
10749
|
const animation = node.animation;
|
|
8774
10750
|
if (!animation?.clip || isModelTransformAnimationClip(animation.clip))
|
|
8775
10751
|
return;
|
|
@@ -8779,12 +10755,84 @@ function applyProductionActorAnimation(entry, node, animationBinding, time, runt
|
|
|
8779
10755
|
return;
|
|
8780
10756
|
}
|
|
8781
10757
|
try {
|
|
8782
|
-
|
|
10758
|
+
const binding = animationBinding?.rootMotion;
|
|
10759
|
+
if (binding && animationBinding) {
|
|
10760
|
+
const observed = animationBinding.rootMotionSamples ?? [{
|
|
10761
|
+
playbackId: animationBinding.playbackId ?? clipName, clipName,
|
|
10762
|
+
time: animationBinding.rootMotionTime ?? 0, weight: 1,
|
|
10763
|
+
loop: animationBinding.loop ?? animation.loop ?? true, additive: false
|
|
10764
|
+
}];
|
|
10765
|
+
if (observed.length === 0) {
|
|
10766
|
+
entry.rootMotionCursors = undefined;
|
|
10767
|
+
return;
|
|
10768
|
+
}
|
|
10769
|
+
const cursors = entry.rootMotionCursors ?? new Map();
|
|
10770
|
+
const samples = observed.map(sample => {
|
|
10771
|
+
if (!Number.isFinite(sample.time) || sample.time < 0)
|
|
10772
|
+
throw new Error("Root motion requires a finite nonnegative unwrapped controller time.");
|
|
10773
|
+
const previous = cursors.get(sample.playbackId);
|
|
10774
|
+
return { clipName: sample.clipName, target: binding.target,
|
|
10775
|
+
fromTime: previous === undefined ? 0 : Math.min(previous, sample.time),
|
|
10776
|
+
toTime: sample.time, weight: sample.weight, loop: sample.loop, additive: sample.additive };
|
|
10777
|
+
});
|
|
10778
|
+
const commitCursors = () => { entry.rootMotionCursors = new Map(observed.map(sample => [sample.playbackId, sample.time])); };
|
|
10779
|
+
const result = entry.actor.playRootMotionClips(samples, {
|
|
10780
|
+
worldFromLocal: [...modelMatrix],
|
|
10781
|
+
move: requested => {
|
|
10782
|
+
if (samples.every(sample => sample.fromTime === sample.toTime))
|
|
10783
|
+
return [0, 0, 0];
|
|
10784
|
+
const accepted = binding.move(requested);
|
|
10785
|
+
// Advance cursors before pose/IK work: retrying a failed pose cannot move twice.
|
|
10786
|
+
commitCursors();
|
|
10787
|
+
refreshAfterMovement();
|
|
10788
|
+
return accepted;
|
|
10789
|
+
}
|
|
10790
|
+
});
|
|
10791
|
+
commitCursors();
|
|
10792
|
+
binding.onSample?.(result.motion);
|
|
10793
|
+
}
|
|
10794
|
+
else {
|
|
10795
|
+
entry.rootMotionCursors = undefined;
|
|
10796
|
+
entry.actor.playClip(clipName, resolveProductionActorAnimationSeconds(animation, animationBinding, time));
|
|
10797
|
+
}
|
|
8783
10798
|
}
|
|
8784
10799
|
catch (error) {
|
|
8785
10800
|
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
|
|
8786
10801
|
}
|
|
8787
10802
|
}
|
|
10803
|
+
/**
|
|
10804
|
+
* Applies controller-bound foot planting (E2) to the typed GLB actor. The resolved config
|
|
10805
|
+
* is refreshed every frame with the actor's live model matrix: time-animated model nodes
|
|
10806
|
+
* (float/orbit/turntable) move, and the post-pass solves in world space, so a stale matrix
|
|
10807
|
+
* would plant feet in yesterday's frame. Foot-lock state survives the refresh — the runtime
|
|
10808
|
+
* keeps its rig across matrix-only updates and resets it only when the leg set, ground, or
|
|
10809
|
+
* solve parameters change.
|
|
10810
|
+
*/
|
|
10811
|
+
function applyProductionActorFootPlanting(entry, animationBinding, modelMatrix, runtimeWarnings) {
|
|
10812
|
+
const footPlanting = animationBinding?.footPlanting;
|
|
10813
|
+
try {
|
|
10814
|
+
entry.actor.animation.setFootPlanting((footPlanting === undefined
|
|
10815
|
+
? undefined
|
|
10816
|
+
: { ...footPlanting, legs: footPlanting.legs.map(leg => ({ ...leg, ...(leg.contactPhase ? { contact: leg.contactPhase(animationBinding?.localTime ?? 0) } : {}) })), ...spreadFootPlantingWorldMatrix(modelMatrix) }));
|
|
10817
|
+
}
|
|
10818
|
+
catch (error) {
|
|
10819
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply foot planting: ${productionRenderErrorMessage(error)}`);
|
|
10820
|
+
}
|
|
10821
|
+
}
|
|
10822
|
+
/**
|
|
10823
|
+
* Packages the actor model matrix as the post-pass `worldFromLocal`. The matrix the
|
|
10824
|
+
* renderer draws with (column-major 16, model-local → world) is exactly the space the
|
|
10825
|
+
* foot-planting ground speaks, so the same array threads both paths. Returns {} when the
|
|
10826
|
+
* matrix is missing or malformed so the pass falls back to actor-local solving.
|
|
10827
|
+
*/
|
|
10828
|
+
function spreadFootPlantingWorldMatrix(modelMatrix) {
|
|
10829
|
+
if (!modelMatrix || modelMatrix.length !== 16)
|
|
10830
|
+
return {};
|
|
10831
|
+
const worldFromLocal = Array.from(modelMatrix, (component) => Number(component));
|
|
10832
|
+
if (worldFromLocal.some((component) => !Number.isFinite(component)))
|
|
10833
|
+
return {};
|
|
10834
|
+
return { worldFromLocal };
|
|
10835
|
+
}
|
|
8788
10836
|
function applyProductionActorMorphTargets(entry, morphTargets, runtimeWarnings) {
|
|
8789
10837
|
if (!morphTargets || Object.keys(morphTargets).length === 0)
|
|
8790
10838
|
return;
|
|
@@ -8832,7 +10880,17 @@ function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
|
|
|
8832
10880
|
},
|
|
8833
10881
|
renderItemCount: renderItems.length,
|
|
8834
10882
|
skinnedRenderItemCount: renderItems.filter((item) => item.skinning).length,
|
|
8835
|
-
morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length
|
|
10883
|
+
morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length,
|
|
10884
|
+
lastMaterialTracksApplied: actorEvidence.lastMaterialTracksApplied,
|
|
10885
|
+
lastLightTracksApplied: actorEvidence.lastLightTracksApplied,
|
|
10886
|
+
lastFootPlantingGroundedFeet: actorEvidence.lastFootPlantingGroundedFeet,
|
|
10887
|
+
lastFootPlantingTargetError: actorEvidence.lastFootPlantingTargetError,
|
|
10888
|
+
lastFootPlantingHipOffset: actorEvidence.lastFootPlantingHipOffset,
|
|
10889
|
+
lastFootPlantingMissingLegs: [...actorEvidence.lastFootPlantingMissingLegs],
|
|
10890
|
+
lastFootPlantingFeet: actorEvidence.lastFootPlantingFeet,
|
|
10891
|
+
lastFootPlantingSurfaces: actorEvidence.lastFootPlantingSurfaces,
|
|
10892
|
+
lastFootPlantingDeformation: actorEvidence.lastFootPlantingDeformation,
|
|
10893
|
+
footPlantingConfigured: actorEvidence.footPlantingConfigured
|
|
8836
10894
|
});
|
|
8837
10895
|
}
|
|
8838
10896
|
function createProductionRuntimeMetadata(actorEntries, primitiveEntries) {
|
|
@@ -9277,7 +11335,9 @@ function collectRuntimeEffectNodes(snapshot) {
|
|
|
9277
11335
|
return groups.flatten(snapshot.nodes).filter((node) => node.kind === "effect");
|
|
9278
11336
|
}
|
|
9279
11337
|
function hasRuntimePostProcessEffects(effectNodes) {
|
|
9280
|
-
return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion"
|
|
11338
|
+
return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion"
|
|
11339
|
+
|| node.effect === "color-grade" || node.effect === "anti-alias" || node.effect === "outline"
|
|
11340
|
+
|| node.effect === "screen-space-reflections" || node.effect === "depth-of-field" || node.effect === "motion-blur");
|
|
9281
11341
|
}
|
|
9282
11342
|
async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runtimeWarnings = [], runtimeNodes) {
|
|
9283
11343
|
const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
|
|
@@ -9313,7 +11373,8 @@ async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runti
|
|
|
9313
11373
|
contactOcclusionReceiver: false,
|
|
9314
11374
|
pixelBacked: false,
|
|
9315
11375
|
actualPasses: [],
|
|
9316
|
-
fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
|
|
11376
|
+
fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : [],
|
|
11377
|
+
executionMode: "none"
|
|
9317
11378
|
},
|
|
9318
11379
|
warnings: [
|
|
9319
11380
|
"Aura3D WebGL2 agent runtime is an explicit safe-basic fallback; advanced postprocess, environment prefiltering, shadow maps, and GLB animation mixers are reported as unsupported unless the production runtime proves them.",
|
|
@@ -10549,6 +12610,8 @@ function textureRef(texture, uvs, key) {
|
|
|
10549
12610
|
return { [key]: texture.index };
|
|
10550
12611
|
}
|
|
10551
12612
|
function createPlaneGeometry() {
|
|
12613
|
+
const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
12614
|
+
const tangents = new Float32Array([1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1]);
|
|
10552
12615
|
return {
|
|
10553
12616
|
positions: new Float32Array([
|
|
10554
12617
|
-0.5, 0, -0.5,
|
|
@@ -10569,10 +12632,21 @@ function createPlaneGeometry() {
|
|
|
10569
12632
|
// up-normal downward. The plane then faced away from every overhead light and
|
|
10570
12633
|
// received no direct lighting or visible cast shadows.
|
|
10571
12634
|
indices: new Uint16Array([0, 2, 1, 0, 3, 2]),
|
|
10572
|
-
bounds: { min: [-0.5, 0, -0.5], max: [0.5, 0, 0.5] }
|
|
12635
|
+
bounds: { min: [-0.5, 0, -0.5], max: [0.5, 0, 0.5] },
|
|
12636
|
+
uvs,
|
|
12637
|
+
uv1s: tilingSecondUnwrap(uvs),
|
|
12638
|
+
tangents
|
|
10573
12639
|
};
|
|
10574
12640
|
}
|
|
10575
12641
|
function createBoxGeometry() {
|
|
12642
|
+
// One full 0-1 unwrap per face, matching the 4-vertex face order below.
|
|
12643
|
+
const faceUvs = [0, 0, 1, 0, 1, 1, 0, 1];
|
|
12644
|
+
const uvs = new Float32Array([...faceUvs, ...faceUvs, ...faceUvs, ...faceUvs, ...faceUvs, ...faceUvs]);
|
|
12645
|
+
// Analytic tangents along each face's +u direction (face order +z,-z,+y,-y,+x,-x).
|
|
12646
|
+
const faceTangents = [
|
|
12647
|
+
[1, 0, 0, 1], [-1, 0, 0, 1], [1, 0, 0, 1], [1, 0, 0, 1], [0, 0, -1, 1], [0, 0, 1, 1]
|
|
12648
|
+
];
|
|
12649
|
+
const tangents = new Float32Array(faceTangents.flatMap((tangent) => [...tangent, ...tangent, ...tangent, ...tangent]));
|
|
10576
12650
|
const positions = new Float32Array([
|
|
10577
12651
|
-0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
|
|
10578
12652
|
0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5,
|
|
@@ -10600,7 +12674,10 @@ function createBoxGeometry() {
|
|
|
10600
12674
|
16, 17, 18, 16, 18, 19,
|
|
10601
12675
|
20, 21, 22, 20, 22, 23
|
|
10602
12676
|
]),
|
|
10603
|
-
bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] }
|
|
12677
|
+
bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] },
|
|
12678
|
+
uvs,
|
|
12679
|
+
uv1s: tilingSecondUnwrap(uvs),
|
|
12680
|
+
tangents
|
|
10604
12681
|
};
|
|
10605
12682
|
}
|
|
10606
12683
|
function createSphereGeometry() {
|
|
@@ -10608,6 +12685,8 @@ function createSphereGeometry() {
|
|
|
10608
12685
|
const columns = 16;
|
|
10609
12686
|
const positions = [];
|
|
10610
12687
|
const normals = [];
|
|
12688
|
+
const uvs = [];
|
|
12689
|
+
const tangents = [];
|
|
10611
12690
|
const indices = [];
|
|
10612
12691
|
for (let row = 0; row <= rows; row += 1) {
|
|
10613
12692
|
const v = row / rows;
|
|
@@ -10620,6 +12699,8 @@ function createSphereGeometry() {
|
|
|
10620
12699
|
const z = Math.sin(theta) * Math.sin(phi);
|
|
10621
12700
|
positions.push(x * 0.5, y * 0.5, z * 0.5);
|
|
10622
12701
|
normals.push(x, y, z);
|
|
12702
|
+
uvs.push(u, 1 - v);
|
|
12703
|
+
tangents.push(-Math.sin(phi), 0, Math.cos(phi), 1);
|
|
10623
12704
|
}
|
|
10624
12705
|
}
|
|
10625
12706
|
for (let row = 0; row < rows; row += 1) {
|
|
@@ -10632,11 +12713,15 @@ function createSphereGeometry() {
|
|
|
10632
12713
|
indices.push(a, a + 1, b, b, a + 1, b + 1);
|
|
10633
12714
|
}
|
|
10634
12715
|
}
|
|
12716
|
+
const sphereUvs = new Float32Array(uvs);
|
|
10635
12717
|
return {
|
|
10636
12718
|
positions: new Float32Array(positions),
|
|
10637
12719
|
normals: new Float32Array(normals),
|
|
10638
12720
|
indices: new Uint16Array(indices),
|
|
10639
|
-
bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] }
|
|
12721
|
+
bounds: { min: [-0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5] },
|
|
12722
|
+
uvs: sphereUvs,
|
|
12723
|
+
uv1s: tilingSecondUnwrap(sphereUvs),
|
|
12724
|
+
tangents: new Float32Array(tangents)
|
|
10640
12725
|
};
|
|
10641
12726
|
}
|
|
10642
12727
|
function createCylinderGeometry() {
|
|
@@ -10973,11 +13058,7 @@ function createViewProjection(snapshot, aspect, time, runtimeNodes) {
|
|
|
10973
13058
|
const cameraSpec = snapshot.camera;
|
|
10974
13059
|
const { target, eye } = resolveCameraFrame(snapshot, cameraSpec, time, runtimeNodes);
|
|
10975
13060
|
const view = lookAtMat4([...eye], [...target], [0, 1, 0]);
|
|
10976
|
-
const
|
|
10977
|
-
const halfWidth = Math.max(1e-4, halfHeight * aspect);
|
|
10978
|
-
const projection = isOrthographicCameraMode(cameraSpec.mode)
|
|
10979
|
-
? orthographicMat4(-halfWidth, halfWidth, -halfHeight, halfHeight, 0.05, 100)
|
|
10980
|
-
: perspectiveMat4(((cameraSpec.fov ?? 45) * Math.PI) / 180, aspect, 0.05, 100);
|
|
13061
|
+
const projection = createCameraProjection(cameraSpec, aspect);
|
|
10981
13062
|
return new Float32Array(multiplyMat4(projection, view));
|
|
10982
13063
|
}
|
|
10983
13064
|
function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
|
|
@@ -11050,6 +13131,10 @@ function isModelTransformAnimationClip(clip) {
|
|
|
11050
13131
|
return clip === "turntable" || clip === "float" || clip === "orbit";
|
|
11051
13132
|
}
|
|
11052
13133
|
function primitiveSize(node) {
|
|
13134
|
+
// SDF quad positions are already authored in world units by layoutSdfText.
|
|
13135
|
+
// Applying the public text size again here shrinks/grows the GPU quad twice.
|
|
13136
|
+
if (node.text3D?.backend === "sdf")
|
|
13137
|
+
return [1, 1, 1];
|
|
11053
13138
|
if (typeof node.size === "number")
|
|
11054
13139
|
return [node.size, node.size, node.size];
|
|
11055
13140
|
return node.size ?? [1, 1, 1];
|
|
@@ -11370,7 +13455,9 @@ function snapshotDiagnostics(value) {
|
|
|
11370
13455
|
renderer: value.renderer,
|
|
11371
13456
|
warnings: [...value.warnings],
|
|
11372
13457
|
errors: [...value.errors],
|
|
11373
|
-
...(value.labels ? { labels: value.labels } : {})
|
|
13458
|
+
...(value.labels ? { labels: value.labels } : {}),
|
|
13459
|
+
...(value.labelTelemetry ? { labelTelemetry: value.labelTelemetry } : {}),
|
|
13460
|
+
...(value.textBuckets ? { textBuckets: value.textBuckets } : {})
|
|
11374
13461
|
};
|
|
11375
13462
|
}
|
|
11376
13463
|
function collectGeneratedCodeWarnings(snapshot) {
|
|
@@ -11381,6 +13468,17 @@ function collectGeneratedCodeWarnings(snapshot) {
|
|
|
11381
13468
|
if (!snapshot.nodes.some((node) => node.kind === "interaction")) {
|
|
11382
13469
|
warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
|
|
11383
13470
|
}
|
|
13471
|
+
const flatNodes = groups.flatten(snapshot.nodes);
|
|
13472
|
+
// muse3jsparity-PRD C1: procedural texture inputs have no rasterizer. The
|
|
13473
|
+
// intent is compile-time known, so warn here; fetch outcomes stay dynamic.
|
|
13474
|
+
for (const node of flatNodes) {
|
|
13475
|
+
if (node.kind !== "primitive" || !node.material)
|
|
13476
|
+
continue;
|
|
13477
|
+
const label = node.name ?? `aura-primitive-${node.primitive}`;
|
|
13478
|
+
for (const procedural of createProductionPrimitiveTextureIntent(node.material).proceduralInputs) {
|
|
13479
|
+
warnings.push(`procedural texture ${procedural} on "${label}" has no rasterizer; recorded only, scalar material retained`);
|
|
13480
|
+
}
|
|
13481
|
+
}
|
|
11384
13482
|
for (const node of snapshot.nodes) {
|
|
11385
13483
|
if (node.kind === "model" && createAssetProvenance(node.asset).source === "unsafe-url") {
|
|
11386
13484
|
warnings.push(`Model uses unsafeModelUrl("${node.asset.url}"). Suggested fix: run assets add and use typed assets.`);
|
|
@@ -11544,6 +13642,16 @@ function drawEffect(context, width, height, node, time) {
|
|
|
11544
13642
|
context.fillStyle = toAlphaColor(node.color ?? "#9fb7d9", node.density ?? 0.12);
|
|
11545
13643
|
context.fillRect(0, height * 0.2, width, height * 0.8);
|
|
11546
13644
|
}
|
|
13645
|
+
if (node.effect === "volumetric-fog") {
|
|
13646
|
+
context.fillStyle = toAlphaColor(node.color ?? "#6f84b9", node.density ?? 0.18);
|
|
13647
|
+
context.fillRect(0, height * 0.2, width, height * 0.8);
|
|
13648
|
+
const anchor = node.lightPosition ?? [0.5, 0.18];
|
|
13649
|
+
const glow = context.createRadialGradient(width * anchor[0], height * anchor[1], 8, width * anchor[0], height * anchor[1], width * 0.3);
|
|
13650
|
+
glow.addColorStop(0, toAlphaColor(node.color ?? "#fff3d6", (node.intensity ?? 0.7) * 0.35));
|
|
13651
|
+
glow.addColorStop(1, "rgba(255,255,255,0)");
|
|
13652
|
+
context.fillStyle = glow;
|
|
13653
|
+
context.fillRect(0, 0, width, height);
|
|
13654
|
+
}
|
|
11547
13655
|
if (node.effect === "bloom") {
|
|
11548
13656
|
const gradient = context.createRadialGradient(width * 0.5, height * 0.45, 20, width * 0.5, height * 0.45, width * 0.46);
|
|
11549
13657
|
gradient.addColorStop(0, toAlphaColor(node.color ?? "#ffffff", (node.intensity ?? 0.35) * 0.3));
|
|
@@ -11596,6 +13704,29 @@ function drawEffect(context, width, height, node, time) {
|
|
|
11596
13704
|
}
|
|
11597
13705
|
}
|
|
11598
13706
|
}
|
|
13707
|
+
if (node.effect === "snow") {
|
|
13708
|
+
const density = Math.max(0.2, Math.min(1.6, node.density ?? node.intensity ?? 0.68));
|
|
13709
|
+
const intensity = Math.max(0.1, Math.min(1.4, node.intensity ?? 0.4));
|
|
13710
|
+
const color = node.color ?? "#e8f1ff";
|
|
13711
|
+
if (node.mist !== false) {
|
|
13712
|
+
context.fillStyle = toAlphaColor(color, Math.min(0.1, 0.03 + intensity * 0.04));
|
|
13713
|
+
context.fillRect(0, height * 0.2, width, height * 0.72);
|
|
13714
|
+
}
|
|
13715
|
+
const drift = (node.wind?.[0] ?? -0.85) * 0.02;
|
|
13716
|
+
for (let layer = 0; layer < 3; layer += 1) {
|
|
13717
|
+
const count = Math.round((layer === 2 ? 30 : 52) * density);
|
|
13718
|
+
const size = layer === 2 ? 3.1 : layer === 1 ? 2.2 : 1.4;
|
|
13719
|
+
context.fillStyle = toAlphaColor(color, Math.min(0.85, 0.34 + intensity * (0.3 - layer * 0.06)));
|
|
13720
|
+
for (let i = 0; i < count; i += 1) {
|
|
13721
|
+
const sway = Math.sin(time * 0.0011 * (node.speed ?? 1) + i * 1.7 + layer) * (6 + layer * 5);
|
|
13722
|
+
const x = (((i * 61 + layer * 197 + time * 0.008 * (node.speed ?? 1) * drift * 60) % width) + width) % width + sway * 0.14;
|
|
13723
|
+
const y = ((i * 97 + layer * 131 + time * 0.028 * (node.speed ?? 1) * (1 + layer * 0.3)) % (height * 0.86)) + height * 0.06;
|
|
13724
|
+
context.beginPath();
|
|
13725
|
+
context.arc(x, y, size, 0, Math.PI * 2);
|
|
13726
|
+
context.fill();
|
|
13727
|
+
}
|
|
13728
|
+
}
|
|
13729
|
+
}
|
|
11599
13730
|
if (node.effect === "particles") {
|
|
11600
13731
|
const isFountain = node.emitter === "fountain";
|
|
11601
13732
|
// Fountain cap matches prefabs.particleFountain's 2400 maximum so rendered counts never undercut the scene JSON.
|