@aura3d/engine 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -19
- package/dist/animation/library/performanceStateGraph.d.ts +11 -3
- package/dist/animation/library/performanceStateGraph.d.ts.map +1 -1
- package/dist/animation/library/performanceStateGraph.js +61 -5
- package/dist/animation/library/performanceStateGraph.js.map +1 -1
- package/dist/animation/library/standardHumanoidClips.d.ts +3 -3
- package/dist/animation/library/standardHumanoidClips.d.ts.map +1 -1
- package/dist/animation/library/standardHumanoidClips.js +179 -4
- package/dist/animation/library/standardHumanoidClips.js.map +1 -1
- package/dist/assets/GLTFLoader.js +1 -0
- package/dist/assets/GLTFLoader.js.map +1 -1
- package/dist/assets/GLTFRenderResources.d.ts.map +1 -1
- package/dist/assets/GLTFRenderResources.js +68 -16
- package/dist/assets/GLTFRenderResources.js.map +1 -1
- package/dist/assets/KTX2BasisTextureTranscoder.js +1 -0
- package/dist/assets/KTX2BasisTextureTranscoder.js.map +1 -1
- package/dist/aura3d-cli/asset-constants.d.ts +5 -0
- package/dist/aura3d-cli/asset-constants.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-constants.js +5 -0
- package/dist/aura3d-cli/asset-constants.js.map +1 -0
- package/dist/aura3d-cli/asset-core-types.d.ts +206 -0
- package/dist/aura3d-cli/asset-core-types.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-core-types.js +2 -0
- package/dist/aura3d-cli/asset-core-types.js.map +1 -0
- package/dist/aura3d-cli/asset-inspection-types.d.ts +131 -0
- package/dist/aura3d-cli/asset-inspection-types.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-inspection-types.js +2 -0
- package/dist/aura3d-cli/asset-inspection-types.js.map +1 -0
- package/dist/aura3d-cli/asset-manifest.d.ts +8 -0
- package/dist/aura3d-cli/asset-manifest.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-manifest.js +116 -0
- package/dist/aura3d-cli/asset-manifest.js.map +1 -0
- package/dist/aura3d-cli/asset-readiness-types.d.ts +164 -0
- package/dist/aura3d-cli/asset-readiness-types.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-readiness-types.js +2 -0
- package/dist/aura3d-cli/asset-readiness-types.js.map +1 -0
- package/dist/aura3d-cli/asset-source-ast.d.ts +8 -0
- package/dist/aura3d-cli/asset-source-ast.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-source-ast.js +241 -0
- package/dist/aura3d-cli/asset-source-ast.js.map +1 -0
- package/dist/aura3d-cli/asset-source-roles.d.ts +4 -0
- package/dist/aura3d-cli/asset-source-roles.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-source-roles.js +79 -0
- package/dist/aura3d-cli/asset-source-roles.js.map +1 -0
- package/dist/aura3d-cli/asset-source-validation.d.ts +4 -0
- package/dist/aura3d-cli/asset-source-validation.d.ts.map +1 -0
- package/dist/aura3d-cli/asset-source-validation.js +200 -0
- package/dist/aura3d-cli/asset-source-validation.js.map +1 -0
- package/dist/aura3d-cli/character-assembly-types.d.ts +39 -0
- package/dist/aura3d-cli/character-assembly-types.d.ts.map +1 -0
- package/dist/aura3d-cli/character-assembly-types.js +2 -0
- package/dist/aura3d-cli/character-assembly-types.js.map +1 -0
- package/dist/aura3d-cli/cli-help.d.ts +4 -0
- package/dist/aura3d-cli/cli-help.d.ts.map +1 -0
- package/dist/aura3d-cli/cli-help.js +48 -0
- package/dist/aura3d-cli/cli-help.js.map +1 -0
- package/dist/aura3d-cli/cli-options.d.ts +66 -0
- package/dist/aura3d-cli/cli-options.d.ts.map +1 -0
- package/dist/aura3d-cli/cli-options.js +234 -0
- package/dist/aura3d-cli/cli-options.js.map +1 -0
- package/dist/aura3d-cli/cli.js +20 -177
- package/dist/aura3d-cli/cli.js.map +1 -1
- package/dist/aura3d-cli/index.d.ts +12 -402
- package/dist/aura3d-cli/index.d.ts.map +1 -1
- package/dist/aura3d-cli/index.js +948 -90
- package/dist/aura3d-cli/index.js.map +1 -1
- package/dist/aura3d-cli/pull-bridge/adapters.d.ts +4 -0
- package/dist/aura3d-cli/pull-bridge/adapters.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/adapters.js +38 -0
- package/dist/aura3d-cli/pull-bridge/adapters.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/download.d.ts +6 -0
- package/dist/aura3d-cli/pull-bridge/download.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/download.js +208 -0
- package/dist/aura3d-cli/pull-bridge/download.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/profiles.d.ts +14 -0
- package/dist/aura3d-cli/pull-bridge/profiles.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/profiles.js +63 -0
- package/dist/aura3d-cli/pull-bridge/profiles.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/provenance.d.ts +9 -0
- package/dist/aura3d-cli/pull-bridge/provenance.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/provenance.js +99 -0
- package/dist/aura3d-cli/pull-bridge/provenance.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/pullable.d.ts +14 -0
- package/dist/aura3d-cli/pull-bridge/pullable.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/pullable.js +58 -0
- package/dist/aura3d-cli/pull-bridge/pullable.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/scoring.d.ts +21 -0
- package/dist/aura3d-cli/pull-bridge/scoring.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/scoring.js +177 -0
- package/dist/aura3d-cli/pull-bridge/scoring.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/search.d.ts +39 -0
- package/dist/aura3d-cli/pull-bridge/search.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/search.js +114 -0
- package/dist/aura3d-cli/pull-bridge/search.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/types.d.ts +9 -0
- package/dist/aura3d-cli/pull-bridge/types.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/types.js +2 -0
- package/dist/aura3d-cli/pull-bridge/types.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge/vector3.d.ts +2 -0
- package/dist/aura3d-cli/pull-bridge/vector3.d.ts.map +1 -0
- package/dist/aura3d-cli/pull-bridge/vector3.js +4 -0
- package/dist/aura3d-cli/pull-bridge/vector3.js.map +1 -0
- package/dist/aura3d-cli/pull-bridge.d.ts +18 -100
- package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -1
- package/dist/aura3d-cli/pull-bridge.js +59 -526
- package/dist/aura3d-cli/pull-bridge.js.map +1 -1
- package/dist/create-aura3d/cli.js +10 -0
- package/dist/create-aura3d/cli.js.map +1 -1
- package/dist/create-aura3d/index.d.ts +1 -1
- package/dist/create-aura3d/index.d.ts.map +1 -1
- package/dist/create-aura3d/index.js +11 -2
- package/dist/create-aura3d/index.js.map +1 -1
- package/dist/create-aura3d/showcase-spec-artifacts.d.ts +104 -0
- package/dist/create-aura3d/showcase-spec-artifacts.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-artifacts.js +142 -0
- package/dist/create-aura3d/showcase-spec-artifacts.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-category-plan.d.ts +15 -0
- package/dist/create-aura3d/showcase-spec-category-plan.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-category-plan.js +45 -0
- package/dist/create-aura3d/showcase-spec-category-plan.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-compiler.d.ts +5 -0
- package/dist/create-aura3d/showcase-spec-compiler.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-compiler.js +240 -0
- package/dist/create-aura3d/showcase-spec-compiler.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-evidence.d.ts +7 -0
- package/dist/create-aura3d/showcase-spec-evidence.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-evidence.js +815 -0
- package/dist/create-aura3d/showcase-spec-evidence.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts +21 -0
- package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js +725 -0
- package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-game-replacement-evidence.d.ts +14 -0
- package/dist/create-aura3d/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-game-replacement-evidence.js +219 -0
- package/dist/create-aura3d/showcase-spec-game-replacement-evidence.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-game-template-evidence.d.ts +72 -0
- package/dist/create-aura3d/showcase-spec-game-template-evidence.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-game-template-evidence.js +435 -0
- package/dist/create-aura3d/showcase-spec-game-template-evidence.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-gameplay-evidence.d.ts +3 -0
- package/dist/create-aura3d/showcase-spec-gameplay-evidence.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-gameplay-evidence.js +108 -0
- package/dist/create-aura3d/showcase-spec-gameplay-evidence.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-parser.d.ts +3 -0
- package/dist/create-aura3d/showcase-spec-parser.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-parser.js +662 -0
- package/dist/create-aura3d/showcase-spec-parser.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts +3 -0
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.js +276 -0
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-racing-artifacts.d.ts +3 -0
- package/dist/create-aura3d/showcase-spec-racing-artifacts.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-racing-artifacts.js +273 -0
- package/dist/create-aura3d/showcase-spec-racing-artifacts.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-apply.d.ts +10 -0
- package/dist/create-aura3d/showcase-spec-replacement-apply.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-apply.js +166 -0
- package/dist/create-aura3d/showcase-spec-replacement-apply.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-candidates.d.ts +16 -0
- package/dist/create-aura3d/showcase-spec-replacement-candidates.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-candidates.js +244 -0
- package/dist/create-aura3d/showcase-spec-replacement-candidates.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-game-geometry.d.ts +4 -0
- package/dist/create-aura3d/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-game-geometry.js +175 -0
- package/dist/create-aura3d/showcase-spec-replacement-game-geometry.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-manifest.d.ts +41 -0
- package/dist/create-aura3d/showcase-spec-replacement-manifest.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-manifest.js +62 -0
- package/dist/create-aura3d/showcase-spec-replacement-manifest.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-values.d.ts +9 -0
- package/dist/create-aura3d/showcase-spec-replacement-values.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement-values.js +41 -0
- package/dist/create-aura3d/showcase-spec-replacement-values.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement.d.ts +13 -0
- package/dist/create-aura3d/showcase-spec-replacement.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-replacement.js +144 -0
- package/dist/create-aura3d/showcase-spec-replacement.js.map +1 -0
- package/dist/create-aura3d/showcase-spec-types.d.ts +304 -0
- package/dist/create-aura3d/showcase-spec-types.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-types.js +2 -0
- package/dist/create-aura3d/showcase-spec-types.js.map +1 -0
- package/dist/ecs/SystemScheduler.js +5 -4
- package/dist/ecs/SystemScheduler.js.map +1 -1
- package/dist/ecs/components/CameraComponent.d.ts +30 -0
- package/dist/ecs/components/CameraComponent.d.ts.map +1 -0
- package/dist/ecs/components/CameraComponent.js +27 -0
- package/dist/ecs/components/CameraComponent.js.map +1 -0
- package/dist/ecs/components/HierarchyComponent.d.ts +10 -0
- package/dist/ecs/components/HierarchyComponent.d.ts.map +1 -1
- package/dist/ecs/components/HierarchyComponent.js +10 -0
- package/dist/ecs/components/HierarchyComponent.js.map +1 -1
- package/dist/ecs/components/LightComponent.d.ts +18 -0
- package/dist/ecs/components/LightComponent.d.ts.map +1 -0
- package/dist/ecs/components/LightComponent.js +17 -0
- package/dist/ecs/components/LightComponent.js.map +1 -0
- package/dist/ecs/components/MeshComponent.d.ts +19 -0
- package/dist/ecs/components/MeshComponent.d.ts.map +1 -0
- package/dist/ecs/components/MeshComponent.js +21 -0
- package/dist/ecs/components/MeshComponent.js.map +1 -0
- package/dist/ecs/components/WorldTransformComponent.d.ts +12 -0
- package/dist/ecs/components/WorldTransformComponent.d.ts.map +1 -0
- package/dist/ecs/components/WorldTransformComponent.js +22 -0
- package/dist/ecs/components/WorldTransformComponent.js.map +1 -0
- package/dist/ecs/index.d.ts +4 -0
- package/dist/ecs/index.d.ts.map +1 -1
- package/dist/ecs/index.js +4 -0
- package/dist/ecs/index.js.map +1 -1
- package/dist/ecs/systems/CameraSystem.d.ts +17 -0
- package/dist/ecs/systems/CameraSystem.d.ts.map +1 -0
- package/dist/ecs/systems/CameraSystem.js +38 -0
- package/dist/ecs/systems/CameraSystem.js.map +1 -0
- package/dist/ecs/systems/TransformSystem.d.ts +16 -0
- package/dist/ecs/systems/TransformSystem.d.ts.map +1 -0
- package/dist/ecs/systems/TransformSystem.js +99 -0
- package/dist/ecs/systems/TransformSystem.js.map +1 -0
- package/dist/ecs/systems/index.d.ts +2 -0
- package/dist/ecs/systems/index.d.ts.map +1 -1
- package/dist/ecs/systems/index.js +2 -0
- package/dist/ecs/systems/index.js.map +1 -1
- package/dist/engine/agent-api/GameAssetValidation.d.ts +5 -0
- package/dist/engine/agent-api/GameAssetValidation.d.ts.map +1 -1
- package/dist/engine/agent-api/GameAssetValidation.js +103 -0
- package/dist/engine/agent-api/GameAssetValidation.js.map +1 -1
- package/dist/engine/agent-api/GameEvidence.d.ts +14 -1
- package/dist/engine/agent-api/GameEvidence.d.ts.map +1 -1
- package/dist/engine/agent-api/GameEvidence.js +77 -6
- package/dist/engine/agent-api/GameEvidence.js.map +1 -1
- package/dist/engine/agent-api/GameGenreKits.d.ts +473 -0
- package/dist/engine/agent-api/GameGenreKits.d.ts.map +1 -0
- package/dist/engine/agent-api/GameGenreKits.js +1269 -0
- package/dist/engine/agent-api/GameGenreKits.js.map +1 -0
- package/dist/engine/agent-api/GameRuntime.d.ts +190 -3
- package/dist/engine/agent-api/GameRuntime.d.ts.map +1 -1
- package/dist/engine/agent-api/GameRuntime.js +431 -2
- package/dist/engine/agent-api/GameRuntime.js.map +1 -1
- package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts +181 -0
- package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts.map +1 -0
- package/dist/engine/agent-api/GameSceneGeometryBindings.js +488 -0
- package/dist/engine/agent-api/GameSceneGeometryBindings.js.map +1 -0
- package/dist/engine/agent-api/GameSceneGeometryMath.d.ts +39 -0
- package/dist/engine/agent-api/GameSceneGeometryMath.d.ts.map +1 -0
- package/dist/engine/agent-api/GameSceneGeometryMath.js +89 -0
- package/dist/engine/agent-api/GameSceneGeometryMath.js.map +1 -0
- package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +4 -0
- package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
- package/dist/engine/agent-api/RuntimeNodeHandle.js +2 -0
- package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
- package/dist/engine/agent-api/SceneGroundingUtils.d.ts +86 -0
- package/dist/engine/agent-api/SceneGroundingUtils.d.ts.map +1 -1
- package/dist/engine/agent-api/SceneGroundingUtils.js +116 -0
- package/dist/engine/agent-api/SceneGroundingUtils.js.map +1 -1
- package/dist/engine/agent-api/index.d.ts +149 -5
- package/dist/engine/agent-api/index.d.ts.map +1 -1
- package/dist/engine/agent-api/index.js +1964 -141
- 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/ecs/ECSRenderSource.d.ts +29 -0
- package/dist/engine/ecs/ECSRenderSource.d.ts.map +1 -0
- package/dist/engine/ecs/ECSRenderSource.js +189 -0
- package/dist/engine/ecs/ECSRenderSource.js.map +1 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +1 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/production-runtime/TypedGLBActor.d.ts +14 -2
- package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
- package/dist/engine/production-runtime/TypedGLBActor.js +95 -4
- package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
- package/dist/physics/Constraint.d.ts.map +1 -1
- package/dist/physics/Constraint.js +3 -3
- package/dist/physics/Constraint.js.map +1 -1
- package/dist/physics/Shape.d.ts +1 -0
- package/dist/physics/Shape.d.ts.map +1 -1
- package/dist/physics/Shape.js +12 -0
- package/dist/physics/Shape.js.map +1 -1
- package/dist/rendering/ForwardPass.d.ts +1 -0
- package/dist/rendering/ForwardPass.d.ts.map +1 -1
- package/dist/rendering/ForwardPass.js +29 -6
- package/dist/rendering/ForwardPass.js.map +1 -1
- package/dist/rendering/MorphTarget.d.ts +1 -0
- package/dist/rendering/MorphTarget.d.ts.map +1 -1
- package/dist/rendering/MorphTarget.js.map +1 -1
- package/dist/rendering/Renderer.js +7 -1
- package/dist/rendering/Renderer.js.map +1 -1
- package/dist/rendering/SkinnedLitMaterial.d.ts +9 -0
- package/dist/rendering/SkinnedLitMaterial.d.ts.map +1 -1
- package/dist/rendering/SkinnedLitMaterial.js +20 -1
- package/dist/rendering/SkinnedLitMaterial.js.map +1 -1
- package/dist/rendering/performance/RenderItemSorting.d.ts +2 -1
- package/dist/rendering/performance/RenderItemSorting.d.ts.map +1 -1
- package/dist/rendering/performance/RenderItemSorting.js +5 -2
- package/dist/rendering/performance/RenderItemSorting.js.map +1 -1
- package/package.json +24 -24
- package/templates/mini-game/README.md +13 -0
- package/templates/mini-game/index.html +1 -1
- package/templates/mini-game/package.json +1 -1
- package/templates/mini-game/src/main.ts +251 -21
- package/templates/mini-game/tests/playable.spec.ts +49 -0
- package/templates/mini-game/tests/screenshot.spec.ts +37 -32
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { PhysicsDebugDraw, PhysicsStepper, PhysicsWorld, ScenePhysicsBridge, Shape as PhysicsShapeFactory } from "../../physics/index.js";
|
|
2
|
+
import { createExternalParityEnvironmentLighting, Geometry, IndexBuffer, PBRMaterial, ProductionRuntimeRenderer, VertexBuffer, VertexFormat } from "../../rendering/index.js";
|
|
3
|
+
import { DirectionalLight } from "../../scene/index.js";
|
|
4
|
+
import { createTypedGLBActor } from "../production-runtime/TypedGLBActor.js";
|
|
5
|
+
import { AURA_NORMALIZED_MODEL_MAX_DIMENSION, boundsFromAsset, boundsHeight, boundsMaxDimension, boundsSize } from "./SceneGroundingUtils.js";
|
|
2
6
|
export * from "./FrameEncoder.js";
|
|
3
7
|
export * from "./BrowserFrameCaptureAdapter.js";
|
|
4
8
|
export * from "./MediaRecorderFrameEncoder.js";
|
|
@@ -43,11 +47,15 @@ export * from "./AnimationMotionQuality.js";
|
|
|
43
47
|
export * from "./AnimationRouteProof.js";
|
|
44
48
|
export * from "./AnimationEpisodePackage.js";
|
|
45
49
|
import { createFrameLoop } from "./FrameLoop.js";
|
|
46
|
-
import { createCombatWorld, createGameCameraDirector, createGameEffects, applyGameCombatEventsToRuntime, createGameAccessibilityFocus, createGameAccessibilityLabel, createGameAccessibilityRuntimeSettings, createGameHighContrastSource, createGameHudBindings, createGameHudComboBinding, createGameHudDebugToggleBinding, createGameHudHealthBinding, createGameHudMeterBinding, createGameHudRoundBinding, createGameHudSnapshot, createGameHudTimerBinding, createGameBoxCollider, createGameCapsuleCollider, createGameColliderDebugGeometry, createGameCombatDebugGeometry, 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";
|
|
50
|
+
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, 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";
|
|
47
51
|
import { collectGameRuntimeEvidence as collectGameRuntimeEvidenceV105 } from "./GameEvidence.js";
|
|
48
52
|
import { calculateRuntimeNodeBounds } from "./RuntimeNodeHandle.js";
|
|
49
53
|
import { createRuntimeNodeSpec } from "./GameSceneBridge.js";
|
|
50
54
|
import { createFightingGameKit, fighting as fightingGameKit } from "./game-kits/fighting.js";
|
|
55
|
+
import { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
|
|
56
|
+
import { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
|
|
57
|
+
export { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
|
|
58
|
+
export { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
|
|
51
59
|
import { createPromptAnimationEpisodePlan, createPromptAnimationStoryBible, definePromptAnimationStoryboard } from "./PromptAnimationContract.js";
|
|
52
60
|
import { applyShotPlaybackFrame, createShotPlaybackPlan, createShotTimeline, installShotPlayback, sampleShotPlaybackPlan } from "./ShotTimeline.js";
|
|
53
61
|
import { captionCueAtTime, createCaptionTimingProof, deriveCaptionTrackFromDialogue } from "./DialoguePerformance.js";
|
|
@@ -157,6 +165,11 @@ export function model(asset, options = {}) {
|
|
|
157
165
|
castShadow: options.castShadow ?? true,
|
|
158
166
|
receiveShadow: options.receiveShadow ?? true,
|
|
159
167
|
visible: options.visible ?? true,
|
|
168
|
+
role: options.role,
|
|
169
|
+
scaleMode: options.scaleMode,
|
|
170
|
+
targetHeight: options.targetHeight,
|
|
171
|
+
targetMaxDimension: options.targetMaxDimension,
|
|
172
|
+
targetLength: options.targetLength,
|
|
160
173
|
physics: options.physics
|
|
161
174
|
});
|
|
162
175
|
}
|
|
@@ -184,6 +197,7 @@ const builtInCharacterAssets = defineAuraAssets({
|
|
|
184
197
|
hash: "sha256-dfb230fc1f942f259dd00281a1186953ad602fc5d69067ce63e24b2aa439736b",
|
|
185
198
|
metadata: {
|
|
186
199
|
materials: ["skinned soldier body", "uniform armor", "visor"],
|
|
200
|
+
// Exactly the clips authored in humanoid-fixture.glb (guarded by a GLB-parsing test); do not add names that are not in the binary.
|
|
187
201
|
animations: ["Idle", "Run", "TPose", "Walk"],
|
|
188
202
|
textures: ["embedded soldier/vanguard textures"],
|
|
189
203
|
license: "Aura3D bundled soldier fixture from the existing repository corpus"
|
|
@@ -553,8 +567,205 @@ export const material = {
|
|
|
553
567
|
fabric: material.fabric()
|
|
554
568
|
}),
|
|
555
569
|
inspector: (name, spec) => createMaterialInspector(name, spec),
|
|
556
|
-
visualQA: (nodes) => validateMaterialVisualQA(nodes)
|
|
570
|
+
visualQA: (nodes) => validateMaterialVisualQA(nodes),
|
|
571
|
+
capabilityDiagnostics: (input) => createMaterialCapabilityDiagnostics(input)
|
|
557
572
|
};
|
|
573
|
+
const materialCapabilityCatalog = [
|
|
574
|
+
{
|
|
575
|
+
id: "base-color",
|
|
576
|
+
label: "Base color",
|
|
577
|
+
rootSafeApi: "supported",
|
|
578
|
+
productionRuntime: "supported",
|
|
579
|
+
evidence: "AuraMaterialSpec.color and imported GLB baseColorFactor are consumed by the root renderer.",
|
|
580
|
+
claimRule: "Can be claimed with a route screenshot."
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
id: "base-color-texture",
|
|
584
|
+
label: "Base-color texture",
|
|
585
|
+
rootSafeApi: "partial",
|
|
586
|
+
productionRuntime: "supported",
|
|
587
|
+
evidence: "Imported GLB baseColorTexture is sampled by the root WebGL path, but the root material contract currently keeps texture support partial until controlled texture on/off pixels are retained.",
|
|
588
|
+
claimRule: "Can describe typed texture metadata and rendered textured assets; do not claim full texture-material parity without controlled root pixels."
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
id: "metallic-roughness",
|
|
592
|
+
label: "Metallic/roughness",
|
|
593
|
+
rootSafeApi: "partial",
|
|
594
|
+
productionRuntime: "supported",
|
|
595
|
+
evidence: "Root material specs expose values, but production-style material response needs route-specific pixel proof.",
|
|
596
|
+
claimRule: "Do not claim full PBR parity without browser evidence."
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id: "normal-map",
|
|
600
|
+
label: "Normal maps",
|
|
601
|
+
rootSafeApi: "partial",
|
|
602
|
+
productionRuntime: "supported",
|
|
603
|
+
evidence: "Root specs carry normal/procedural-normal intent; production shader parity is not guaranteed.",
|
|
604
|
+
claimRule: "Claim only with material-specific screenshot evidence."
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: "emissive",
|
|
608
|
+
label: "Emissive",
|
|
609
|
+
rootSafeApi: "partial",
|
|
610
|
+
productionRuntime: "supported",
|
|
611
|
+
evidence: "Root specs and effects expose emissive color; pixel-backed bloom is reported separately.",
|
|
612
|
+
claimRule: "Distinguish emissive material from postprocess bloom."
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: "alpha",
|
|
616
|
+
label: "Alpha/opacity",
|
|
617
|
+
rootSafeApi: "partial",
|
|
618
|
+
productionRuntime: "supported",
|
|
619
|
+
evidence: "Opacity is accepted by the public spec, but sorting and blending must be visually verified.",
|
|
620
|
+
claimRule: "Verify alpha behavior in screenshots before claiming."
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
id: "double-sided",
|
|
624
|
+
label: "Double-sided materials",
|
|
625
|
+
rootSafeApi: "metadata-only",
|
|
626
|
+
productionRuntime: "supported",
|
|
627
|
+
evidence: "Root diagnostics may preserve metadata, but public material specs do not expose a full double-sided render contract.",
|
|
628
|
+
claimRule: "Claim only when tested route shows backface behavior."
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
id: "clearcoat",
|
|
632
|
+
label: "Clearcoat",
|
|
633
|
+
rootSafeApi: "partial",
|
|
634
|
+
productionRuntime: "supported",
|
|
635
|
+
evidence: "Root specs and inspectors expose clearcoat values; physically accurate layered highlights need pixel proof.",
|
|
636
|
+
claimRule: "Do not call clearcoat physically accurate without renderer evidence."
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
id: "sheen",
|
|
640
|
+
label: "Sheen",
|
|
641
|
+
rootSafeApi: "partial",
|
|
642
|
+
productionRuntime: "supported",
|
|
643
|
+
evidence: "Root specs expose sheen values for inspection and future renderer binding.",
|
|
644
|
+
claimRule: "Treat as partial unless screenshot-proven."
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
id: "transmission",
|
|
648
|
+
label: "Transmission/glass",
|
|
649
|
+
rootSafeApi: "partial",
|
|
650
|
+
productionRuntime: "supported",
|
|
651
|
+
evidence: "Root specs expose glass intent, opacity, IOR, and thickness; real refraction/volume is not proven in the root path.",
|
|
652
|
+
claimRule: "Do not claim real transmission/refraction through root createAuraApp without pixels."
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: "variants",
|
|
656
|
+
label: "Material variants",
|
|
657
|
+
rootSafeApi: "metadata-only",
|
|
658
|
+
productionRuntime: "supported",
|
|
659
|
+
evidence: "Variant metadata and route logic can be inspected; renderer-level variant switching needs route tests.",
|
|
660
|
+
claimRule: "Claim selected variant behavior only when tested."
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
id: "hdr-ibl",
|
|
664
|
+
label: "HDR/IBL",
|
|
665
|
+
rootSafeApi: "partial",
|
|
666
|
+
productionRuntime: "supported",
|
|
667
|
+
evidence: "Root environment nodes request lighting intent; environment prefiltering is not proven in the root path.",
|
|
668
|
+
claimRule: "Root HDR/IBL claims require diagnostics and browser pixels."
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: "shadow-maps",
|
|
672
|
+
label: "Shadow maps",
|
|
673
|
+
rootSafeApi: "partial",
|
|
674
|
+
productionRuntime: "supported",
|
|
675
|
+
evidence: "Root path reports contact-shadow cues; production shadow-map sampling is not proven by spec alone.",
|
|
676
|
+
claimRule: "Production-shadow claims require pixel proof."
|
|
677
|
+
}
|
|
678
|
+
];
|
|
679
|
+
function createMaterialCapabilityDiagnostics(input) {
|
|
680
|
+
const specs = extractMaterialCapabilitySpecs(input);
|
|
681
|
+
const requested = new Set();
|
|
682
|
+
if (specs.length > 0)
|
|
683
|
+
requested.add("base-color");
|
|
684
|
+
for (const spec of specs) {
|
|
685
|
+
if (spec.texture)
|
|
686
|
+
requested.add("base-color-texture");
|
|
687
|
+
if (spec.metallic !== undefined || spec.metalness !== undefined || spec.roughness !== undefined || spec.roughnessMap || spec.metalnessMap)
|
|
688
|
+
requested.add("metallic-roughness");
|
|
689
|
+
if (spec.normal)
|
|
690
|
+
requested.add("normal-map");
|
|
691
|
+
if (spec.emissive || spec.emissiveIntensity !== undefined)
|
|
692
|
+
requested.add("emissive");
|
|
693
|
+
if (spec.opacity !== undefined && spec.opacity < 1)
|
|
694
|
+
requested.add("alpha");
|
|
695
|
+
if (spec.clearcoat !== undefined || spec.clearcoatRoughness !== undefined)
|
|
696
|
+
requested.add("clearcoat");
|
|
697
|
+
if (spec.sheen !== undefined || spec.sheenColor !== undefined || spec.sheenRoughness !== undefined)
|
|
698
|
+
requested.add("sheen");
|
|
699
|
+
if (spec.transmission !== undefined || spec.thickness !== undefined || spec.ior !== undefined || spec.attenuationColor !== undefined || spec.attenuationDistance !== undefined)
|
|
700
|
+
requested.add("transmission");
|
|
701
|
+
if (spec.envMapIntensity !== undefined)
|
|
702
|
+
requested.add("hdr-ibl");
|
|
703
|
+
}
|
|
704
|
+
const features = materialCapabilityCatalog.map((feature) => ({ ...feature, requested: requested.has(feature.id) }));
|
|
705
|
+
const unsupportedRequestedFeatures = features
|
|
706
|
+
.filter((feature) => feature.requested && (feature.rootSafeApi === "unsupported" || feature.rootSafeApi === "internal" || feature.rootSafeApi === "metadata-only"))
|
|
707
|
+
.map((feature) => feature.id);
|
|
708
|
+
const partialRequestedFeatures = features
|
|
709
|
+
.filter((feature) => feature.requested && feature.rootSafeApi === "partial")
|
|
710
|
+
.map((feature) => feature.id);
|
|
711
|
+
const warnings = [];
|
|
712
|
+
if (partialRequestedFeatures.length > 0) {
|
|
713
|
+
warnings.push(`Partial root material features requested: ${partialRequestedFeatures.join(", ")}. Capture route pixels before claiming production material quality.`);
|
|
714
|
+
}
|
|
715
|
+
if (unsupportedRequestedFeatures.length > 0) {
|
|
716
|
+
warnings.push(`Metadata/internal material features requested: ${unsupportedRequestedFeatures.join(", ")}. Do not claim rendered support from root createAuraApp alone.`);
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
kind: "aura-material-capability-diagnostics",
|
|
720
|
+
rendererPath: "root-createAuraApp",
|
|
721
|
+
requestedFeatures: [...requested].sort(),
|
|
722
|
+
unsupportedRequestedFeatures,
|
|
723
|
+
partialRequestedFeatures,
|
|
724
|
+
features,
|
|
725
|
+
warnings,
|
|
726
|
+
claimBoundary: "Material diagnostics describe public root createAuraApp support. They are not a substitute for browser screenshot evidence."
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function extractMaterialCapabilitySpecs(input) {
|
|
730
|
+
if (!input)
|
|
731
|
+
return [];
|
|
732
|
+
if (Array.isArray(input)) {
|
|
733
|
+
if (input.every(isAuraMaterialSpecLike))
|
|
734
|
+
return input;
|
|
735
|
+
return groups.flatten(input)
|
|
736
|
+
.filter((node) => (node.kind === "primitive" || node.kind === "model") && Boolean(node.material))
|
|
737
|
+
.map((node) => node.material);
|
|
738
|
+
}
|
|
739
|
+
if (isAuraMaterialSpecLike(input))
|
|
740
|
+
return [input];
|
|
741
|
+
const snapshot = normalizeSceneSnapshot(input);
|
|
742
|
+
return groups.flatten(snapshot.nodes)
|
|
743
|
+
.filter((node) => (node.kind === "primitive" || node.kind === "model") && Boolean(node.material))
|
|
744
|
+
.map((node) => node.material);
|
|
745
|
+
}
|
|
746
|
+
function isAuraMaterialSpecLike(value) {
|
|
747
|
+
if (!value || typeof value !== "object")
|
|
748
|
+
return false;
|
|
749
|
+
const candidate = value;
|
|
750
|
+
if (candidate.kind !== undefined || candidate.nodes !== undefined)
|
|
751
|
+
return false;
|
|
752
|
+
return Boolean(candidate.color !== undefined ||
|
|
753
|
+
candidate.roughness !== undefined ||
|
|
754
|
+
candidate.metallic !== undefined ||
|
|
755
|
+
candidate.metalness !== undefined ||
|
|
756
|
+
candidate.emissive !== undefined ||
|
|
757
|
+
candidate.opacity !== undefined ||
|
|
758
|
+
candidate.transmission !== undefined ||
|
|
759
|
+
candidate.clearcoat !== undefined ||
|
|
760
|
+
candidate.sheen !== undefined ||
|
|
761
|
+
candidate.texture !== undefined ||
|
|
762
|
+
candidate.normal !== undefined ||
|
|
763
|
+
candidate.roughnessMap !== undefined ||
|
|
764
|
+
candidate.metalnessMap !== undefined ||
|
|
765
|
+
candidate.envMapIntensity !== undefined ||
|
|
766
|
+
candidate.shader !== undefined ||
|
|
767
|
+
candidate.name !== undefined);
|
|
768
|
+
}
|
|
558
769
|
function createMaterialInspector(name, spec) {
|
|
559
770
|
const metalness = spec.metalness ?? spec.metallic ?? 0;
|
|
560
771
|
const parameters = [
|
|
@@ -806,6 +1017,7 @@ export const camera = {
|
|
|
806
1017
|
distance: options.distance ?? 5,
|
|
807
1018
|
position: options.position,
|
|
808
1019
|
target: options.target ?? [0, 1, 0],
|
|
1020
|
+
offset: options.offset,
|
|
809
1021
|
fov: options.fov ?? 50,
|
|
810
1022
|
easing: options.easing,
|
|
811
1023
|
captureTime: options.captureTime,
|
|
@@ -843,6 +1055,37 @@ export const camera = {
|
|
|
843
1055
|
const distance = extent * (options.padding ?? 2.15);
|
|
844
1056
|
return camera.orbit({ target: center, distance, fov: options.fov ?? 42 });
|
|
845
1057
|
},
|
|
1058
|
+
frameAsset: (asset, options = {}) => {
|
|
1059
|
+
const bounds = boundsFromAsset(asset);
|
|
1060
|
+
const size = boundsSize(bounds);
|
|
1061
|
+
const renderedScale = resolveFrameAssetRenderScale(bounds, options);
|
|
1062
|
+
const renderedSize = [
|
|
1063
|
+
Math.max(0.001, size[0] * renderedScale),
|
|
1064
|
+
Math.max(0.001, size[1] * renderedScale),
|
|
1065
|
+
Math.max(0.001, size[2] * renderedScale)
|
|
1066
|
+
];
|
|
1067
|
+
const position = options.position ?? [0, options.floorY ?? 0, 0];
|
|
1068
|
+
const target = options.target ?? [
|
|
1069
|
+
position[0],
|
|
1070
|
+
position[1] + renderedSize[1] * 0.28,
|
|
1071
|
+
position[2]
|
|
1072
|
+
];
|
|
1073
|
+
const fov = options.fov ?? 36;
|
|
1074
|
+
const fovRadians = Math.max(1, Math.min(120, fov)) * Math.PI / 180;
|
|
1075
|
+
const radius = Math.hypot(renderedSize[0], renderedSize[1], renderedSize[2]) / 2;
|
|
1076
|
+
const verticalFitDistance = renderedSize[1] / (2 * Math.tan(fovRadians / 2));
|
|
1077
|
+
const horizontalFitDistance = Math.max(renderedSize[0], renderedSize[2]) / (2 * Math.tan(fovRadians / 2));
|
|
1078
|
+
const distance = Math.max(options.minDistance ?? 0.85, verticalFitDistance * (options.padding ?? 1.42), horizontalFitDistance * (options.padding ?? 1.42), radius / Math.tan(fovRadians / 2) * (options.padding ?? 1.42));
|
|
1079
|
+
const azimuth = options.azimuth ?? 0.62;
|
|
1080
|
+
const elevation = options.elevation ?? 0.28;
|
|
1081
|
+
const horizontal = Math.max(0.001, Math.cos(elevation));
|
|
1082
|
+
const eye = [
|
|
1083
|
+
target[0] + Math.sin(azimuth) * horizontal * distance,
|
|
1084
|
+
target[1] + Math.sin(elevation) * distance,
|
|
1085
|
+
target[2] + Math.cos(azimuth) * horizontal * distance
|
|
1086
|
+
];
|
|
1087
|
+
return camera.orbit({ target, position: eye, distance, fov });
|
|
1088
|
+
},
|
|
846
1089
|
physics: () => camera.orbit({ target: [0, 0.58, -0.35], distance: 5.8, fov: 43 }),
|
|
847
1090
|
charts: () => camera.orbit({ target: [0, 0.78, 0], distance: 6.4, fov: 40 }),
|
|
848
1091
|
materials: () => camera.perspective({ position: [0, 2.08, 7.35], target: [0, 0.9, -0.72], fov: 40 }),
|
|
@@ -853,6 +1096,19 @@ export const camera = {
|
|
|
853
1096
|
miniGolf: () => camera.follow({ targetNode: "white physics golf ball", distance: 4.2, fov: 48 }),
|
|
854
1097
|
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 })
|
|
855
1098
|
};
|
|
1099
|
+
function resolveFrameAssetRenderScale(bounds, options) {
|
|
1100
|
+
const size = boundsSize(bounds);
|
|
1101
|
+
const height = Math.max(0.001, boundsHeight(bounds));
|
|
1102
|
+
const horizontalLength = Math.max(0.001, size[0], size[2]);
|
|
1103
|
+
const maxDimension = Math.max(0.001, boundsMaxDimension(bounds));
|
|
1104
|
+
if (isPositiveFinite(options.targetHeight))
|
|
1105
|
+
return options.targetHeight / height;
|
|
1106
|
+
if (isPositiveFinite(options.targetLength))
|
|
1107
|
+
return options.targetLength / horizontalLength;
|
|
1108
|
+
if (isPositiveFinite(options.targetMaxDimension))
|
|
1109
|
+
return options.targetMaxDimension / maxDimension;
|
|
1110
|
+
return AURA_NORMALIZED_MODEL_MAX_DIMENSION / maxDimension;
|
|
1111
|
+
}
|
|
856
1112
|
export const timeline = {
|
|
857
1113
|
loop: (options = {}) => ({
|
|
858
1114
|
mode: "loop",
|
|
@@ -1283,17 +1539,120 @@ const rendererQualityPresets = {
|
|
|
1283
1539
|
evidence: "prioritizes benchmark screenshots with stronger edge quality for labels, axes, and neon rings"
|
|
1284
1540
|
}
|
|
1285
1541
|
};
|
|
1542
|
+
const rendererQualityProfiles = {
|
|
1543
|
+
"safe-basic": {
|
|
1544
|
+
kind: "aura-renderer-quality-profile",
|
|
1545
|
+
id: "safe-basic",
|
|
1546
|
+
label: "Safe Basic",
|
|
1547
|
+
rendererMode: "safe-basic",
|
|
1548
|
+
status: "supported",
|
|
1549
|
+
antialiasing: "msaa",
|
|
1550
|
+
pixelRatio: 1,
|
|
1551
|
+
preserveDrawingBuffer: true,
|
|
1552
|
+
maxRecommendedDrawCalls: 180,
|
|
1553
|
+
requestedFeatures: ["typed models", "primitives", "basic particles", "base-color textures", "runtime nodes"],
|
|
1554
|
+
supportedInRoot: ["typed models", "primitives", "basic particles", "base-color textures", "runtime nodes"],
|
|
1555
|
+
blockedInRoot: ["production PBR parity", "skinned animation mixer", "native WebGPU compute", "postprocess pass chain"],
|
|
1556
|
+
claimBoundary: "Root createAuraApp safe path. Use this for public examples unless a browser test proves a stronger profile."
|
|
1557
|
+
},
|
|
1558
|
+
production: {
|
|
1559
|
+
kind: "aura-renderer-quality-profile",
|
|
1560
|
+
id: "production",
|
|
1561
|
+
label: "Production Typed GLB",
|
|
1562
|
+
rendererMode: "production",
|
|
1563
|
+
status: "supported",
|
|
1564
|
+
antialiasing: "msaa-plus-high-dpi",
|
|
1565
|
+
pixelRatio: 1.5,
|
|
1566
|
+
preserveDrawingBuffer: true,
|
|
1567
|
+
maxRecommendedDrawCalls: 260,
|
|
1568
|
+
requestedFeatures: ["typed GLB actors", "PBR material pipeline", "environment lighting", "shadow maps", "postprocess"],
|
|
1569
|
+
supportedInRoot: ["typed GLB actor bridge", "imported GLB render source", "PBR material pipeline metadata", "runtime diagnostics"],
|
|
1570
|
+
blockedInRoot: ["environment prefiltering without pixel proof", "shadow-map sampling without pixel proof", "postprocess pass chain without pixel proof", "native WebGPU claim without adapter/render evidence"],
|
|
1571
|
+
claimBoundary: "Root createAuraApp can route typed GLB manifest assets through the production runtime. Claim only the specific features proven by route diagnostics and screenshots."
|
|
1572
|
+
},
|
|
1573
|
+
cinematic: {
|
|
1574
|
+
kind: "aura-renderer-quality-profile",
|
|
1575
|
+
id: "cinematic",
|
|
1576
|
+
label: "Cinematic Request",
|
|
1577
|
+
rendererMode: "production",
|
|
1578
|
+
status: "fallback-only",
|
|
1579
|
+
antialiasing: "msaa-plus-high-dpi",
|
|
1580
|
+
pixelRatio: 1.5,
|
|
1581
|
+
preserveDrawingBuffer: true,
|
|
1582
|
+
maxRecommendedDrawCalls: 320,
|
|
1583
|
+
requestedFeatures: ["cinematic lighting", "environment lighting", "postprocess", "motion proof", "high-DPI screenshots"],
|
|
1584
|
+
supportedInRoot: ["high-DPI canvas sizing", "scene diagnostics", "basic particles", "camera/timeline animation"],
|
|
1585
|
+
blockedInRoot: ["renderer-owned bloom pass", "depth-aware postprocess", "production shadow maps", "skinned animation mixer"],
|
|
1586
|
+
claimBoundary: "Cinematic is an explicit request profile, not proof of production cinematic rendering."
|
|
1587
|
+
},
|
|
1588
|
+
"experimental-webgpu": {
|
|
1589
|
+
kind: "aura-renderer-quality-profile",
|
|
1590
|
+
id: "experimental-webgpu",
|
|
1591
|
+
label: "Experimental WebGPU Request",
|
|
1592
|
+
rendererMode: "production",
|
|
1593
|
+
status: "experimental",
|
|
1594
|
+
antialiasing: "msaa",
|
|
1595
|
+
pixelRatio: 1,
|
|
1596
|
+
preserveDrawingBuffer: true,
|
|
1597
|
+
maxRecommendedDrawCalls: 220,
|
|
1598
|
+
requestedFeatures: ["WebGPU adapter", "compute dispatch", "native WebGPU rendering"],
|
|
1599
|
+
supportedInRoot: ["capability diagnostics", "fallback route evidence"],
|
|
1600
|
+
blockedInRoot: ["native WebGPU claim without adapter/backend/dispatch/render evidence"],
|
|
1601
|
+
claimBoundary: "May only claim native WebGPU when route diagnostics and browser screenshots prove adapter, backend, dispatch, and pixels."
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
function resolveRendererQualityProfile(id) {
|
|
1605
|
+
return rendererQualityProfiles[id ?? "safe-basic"] ?? rendererQualityProfiles["safe-basic"];
|
|
1606
|
+
}
|
|
1607
|
+
function normalizeCreateAppRendererOptions(options) {
|
|
1608
|
+
const profile = resolveRendererQualityProfile(options?.qualityProfile);
|
|
1609
|
+
const mode = options?.mode ?? profile.rendererMode;
|
|
1610
|
+
return {
|
|
1611
|
+
mode,
|
|
1612
|
+
fallback: options?.fallback ?? "safe-basic",
|
|
1613
|
+
qualityProfile: profile.id,
|
|
1614
|
+
profile
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function analyzeProductionBridgeEligibility(nodes) {
|
|
1618
|
+
const modelNodes = nodes.filter(isRenderableModelNode);
|
|
1619
|
+
const typedModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "typed-aura-assets-manifest").length;
|
|
1620
|
+
const unsafeModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "unsafe-url").length;
|
|
1621
|
+
const inlineModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "inline-definition").length;
|
|
1622
|
+
const primitiveCount = nodes.filter((node) => node.kind === "primitive").length;
|
|
1623
|
+
const effectCount = nodes.filter((node) => node.kind === "effect").length;
|
|
1624
|
+
const reasons = [];
|
|
1625
|
+
if (typedModelCount < 1)
|
|
1626
|
+
reasons.push("production bridge requires at least one typed GLB from generated aura-assets");
|
|
1627
|
+
if (unsafeModelCount > 0)
|
|
1628
|
+
reasons.push("unsafeModelUrl and remote/raw model URLs are blocked from the production bridge");
|
|
1629
|
+
if (inlineModelCount > 0)
|
|
1630
|
+
reasons.push("inline model definitions without manifest hashes are blocked from the production bridge");
|
|
1631
|
+
return {
|
|
1632
|
+
eligible: reasons.length === 0,
|
|
1633
|
+
typedModelCount,
|
|
1634
|
+
unsafeModelCount,
|
|
1635
|
+
inlineModelCount,
|
|
1636
|
+
primitiveCount,
|
|
1637
|
+
effectCount,
|
|
1638
|
+
reasons
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1286
1641
|
export const renderer = {
|
|
1287
1642
|
colorManagementPreset: () => rendererColorManagementPreset,
|
|
1288
1643
|
exposurePresets: () => sceneExposurePresets,
|
|
1289
1644
|
exposureFor: (category) => sceneExposurePresets[category],
|
|
1290
1645
|
qualityPresets: () => rendererQualityPresets,
|
|
1646
|
+
qualityProfiles: () => rendererQualityProfiles,
|
|
1647
|
+
qualityProfile: (id) => resolveRendererQualityProfile(id),
|
|
1291
1648
|
screenshotQuality: () => rendererQualityPresets.screenshot,
|
|
1292
|
-
diagnostics: (sceneValue) => createRendererDiagnosticReport(flattenSceneSnapshot(normalizeSceneSnapshot(sceneValue)))
|
|
1649
|
+
diagnostics: (sceneValue, options) => createRendererDiagnosticReport(flattenSceneSnapshot(normalizeSceneSnapshot(sceneValue)), undefined, options)
|
|
1293
1650
|
};
|
|
1294
|
-
function createRendererDiagnosticReport(snapshot, runtime) {
|
|
1651
|
+
function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
1295
1652
|
const flattened = groups.flatten(snapshot.nodes);
|
|
1296
1653
|
const names = flattened.map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
|
|
1654
|
+
const rendererSelection = normalizeCreateAppRendererOptions(rendererOptions);
|
|
1655
|
+
const productionEligibility = analyzeProductionBridgeEligibility(flattened);
|
|
1297
1656
|
const sceneCategory = resolveRendererSceneCategory(snapshot, names);
|
|
1298
1657
|
const exposure = sceneExposurePresets[sceneCategory];
|
|
1299
1658
|
const bloom = flattened.find((node) => node.kind === "effect" && node.effect === "bloom");
|
|
@@ -1302,9 +1661,12 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1302
1661
|
const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
|
|
1303
1662
|
const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
|
|
1304
1663
|
const contactOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "contact-occlusion") || contactShadows > 0;
|
|
1305
|
-
const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion;
|
|
1306
|
-
const requestedPasses = requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
|
|
1307
1664
|
const runtimePostprocess = runtime?.postprocess;
|
|
1665
|
+
const runtimePasses = runtimePostprocess?.actualPasses ?? [];
|
|
1666
|
+
const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion || runtimePasses.length > 0;
|
|
1667
|
+
const requestedPasses = runtimePasses.length > 0
|
|
1668
|
+
? runtimePasses
|
|
1669
|
+
: requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
|
|
1308
1670
|
const runtimeStatus = !postprocessRequested
|
|
1309
1671
|
? "disabled"
|
|
1310
1672
|
: runtime?.mounted
|
|
@@ -1323,6 +1685,18 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1323
1685
|
warnings.push("renderer diagnostics are a scene plan only; call createAuraApp(...).diagnostics() after the first render for pixel-backed pass status");
|
|
1324
1686
|
if (postprocessRequested && runtime?.mounted && !runtimePostprocess?.pixelBacked)
|
|
1325
1687
|
warnings.push("postprocess was requested but the runtime composer did not initialize a pixel-backed pass");
|
|
1688
|
+
if (rendererSelection.mode === "production" && !runtime?.mounted && productionEligibility.eligible) {
|
|
1689
|
+
warnings.push(`Renderer profile "${rendererSelection.profile.id}" can use the typed-GLB production bridge after createAuraApp mounts; inspect app.diagnostics().renderer.runtime for pixel-backed status.`);
|
|
1690
|
+
}
|
|
1691
|
+
if (rendererSelection.mode === "production" && !productionEligibility.eligible) {
|
|
1692
|
+
warnings.push(`Renderer profile "${rendererSelection.profile.id}" will use safe-basic fallback for this scene: ${productionEligibility.reasons.join("; ")}.`);
|
|
1693
|
+
}
|
|
1694
|
+
if (rendererSelection.profile.status === "fallback-only") {
|
|
1695
|
+
warnings.push(`Renderer profile "${rendererSelection.profile.id}" is a request profile; public claims require route-specific runtime diagnostics and screenshot proof.`);
|
|
1696
|
+
}
|
|
1697
|
+
if (rendererSelection.profile.id === "experimental-webgpu") {
|
|
1698
|
+
warnings.push("Experimental WebGPU profile cannot claim native WebGPU without adapter, backend, dispatch, render, and pixel evidence.");
|
|
1699
|
+
}
|
|
1326
1700
|
warnings.push(...(runtime?.warnings ?? []));
|
|
1327
1701
|
const environmentStatus = runtime?.environment ?? {
|
|
1328
1702
|
enabled: Boolean(environment),
|
|
@@ -1342,6 +1716,9 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1342
1716
|
return {
|
|
1343
1717
|
kind: "aura-renderer-diagnostics",
|
|
1344
1718
|
colorManagement: rendererColorManagementPreset,
|
|
1719
|
+
rendererMode: rendererSelection.mode,
|
|
1720
|
+
fallbackMode: rendererSelection.fallback,
|
|
1721
|
+
qualityProfile: rendererSelection.profile,
|
|
1345
1722
|
sceneCategory,
|
|
1346
1723
|
exposure,
|
|
1347
1724
|
toneMapping: "aces-filmic",
|
|
@@ -1405,6 +1782,7 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1405
1782
|
},
|
|
1406
1783
|
antialiasing: rendererQualityPresets.screenshot.antialiasing,
|
|
1407
1784
|
screenshotQuality: rendererQualityPresets.screenshot,
|
|
1785
|
+
materialCapabilities: createMaterialCapabilityDiagnostics(snapshot),
|
|
1408
1786
|
warnings
|
|
1409
1787
|
};
|
|
1410
1788
|
}
|
|
@@ -1438,7 +1816,18 @@ function resolveRendererSceneCategory(snapshot, names) {
|
|
|
1438
1816
|
return "physics";
|
|
1439
1817
|
if (hasName("chart") || hasName("data bar") || hasName("axis"))
|
|
1440
1818
|
return "chart";
|
|
1441
|
-
if (hasName("
|
|
1819
|
+
if (hasName("game") ||
|
|
1820
|
+
hasName("golf") ||
|
|
1821
|
+
hasName("score") ||
|
|
1822
|
+
hasName("cup") ||
|
|
1823
|
+
hasName("race") ||
|
|
1824
|
+
hasName("car") ||
|
|
1825
|
+
hasName("circuit") ||
|
|
1826
|
+
hasName("runner") ||
|
|
1827
|
+
hasName("platform") ||
|
|
1828
|
+
hasName("checkpoint") ||
|
|
1829
|
+
hasName("arcade") ||
|
|
1830
|
+
hasName("blockfall"))
|
|
1442
1831
|
return "game";
|
|
1443
1832
|
const background = colorToClearColor(snapshot.background);
|
|
1444
1833
|
const luminance = background[0] * 0.2126 + background[1] * 0.7152 + background[2] * 0.0722;
|
|
@@ -2137,16 +2526,27 @@ const MINI_GOLF_LAYOUT = {
|
|
|
2137
2526
|
aimVector: [1, 0, -0.55]
|
|
2138
2527
|
};
|
|
2139
2528
|
export const prefabs = {
|
|
2140
|
-
particleFountain: (options = {}) =>
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2529
|
+
particleFountain: (options = {}) => {
|
|
2530
|
+
// Cap at 2400 (the advertised fountain maximum); the WebGL and 2D fallback renderers clamp fountain layers to the same 2400 so the scene JSON never claims more particles than render.
|
|
2531
|
+
const count = Math.min(2400, Math.max(320, options.count ?? 420));
|
|
2532
|
+
const emissionRate = options.emissionRate ?? 120;
|
|
2533
|
+
const splashCount = Math.round(count * 0.48);
|
|
2534
|
+
const mistCount = Math.round(count * 0.36);
|
|
2535
|
+
return [
|
|
2536
|
+
primitives.plane({ name: "large grid particle collision ground plane", material: material.pbr({ color: "#101822", roughness: 0.82, metallic: 0.02 }) }).position(0, -0.02, 0).scale([7, 1, 7]).toJSON(),
|
|
2537
|
+
primitives.torus({ name: "painted particle collision splash ring", material: material.pbr({ color: "#174456", roughness: 0.48, metallic: 0.06 }) }).position(0, 0.37, 0).rotate(1.5708, 0, 0).scale([0.72, 0.72, 0.028]).toJSON(),
|
|
2538
|
+
primitives.cylinder({ name: "literal nozzle particle emitter cone", material: material.metal({ color: "#1d2f3f", roughness: 0.2, metallic: 0.38 }) }).position(0, 0.4, 0).scale([0.18, 0.28, 0.18]).toJSON(),
|
|
2539
|
+
primitives.box({ name: "real emission rate slider track", material: material.pbr({ color: "#334155", roughness: 0.72, metallic: 0.04 }) }).position(-1.8, 0.5, 0).scale([0.8, 0.04, 0.06]).toJSON(),
|
|
2540
|
+
primitives.sphere({ name: "real emission rate slider knob high", material: material.emissive({ color: "#fb923c", emissive: "#fb923c", emissiveIntensity: 0.42 }) }).position(-1.4, 0.5, 0).scale(0.04).toJSON(),
|
|
2541
|
+
primitives.box({ name: "hot young particle color swatch", material: material.emissive({ color: "#fff7ad", emissive: "#fff7ad", emissiveIntensity: 0.28 }) }).position(1.4, 0.52, 0).scale([0.12, 0.12, 0.02]).toJSON(),
|
|
2542
|
+
primitives.box({ name: "warm falling particle color swatch", material: material.emissive({ color: "#fb923c", emissive: "#fb923c", emissiveIntensity: 0.28 }) }).position(1.6, 0.52, 0).scale([0.12, 0.12, 0.02]).toJSON(),
|
|
2543
|
+
primitives.box({ name: "cool old particle color swatch", material: material.emissive({ color: "#60a5fa", emissive: "#60a5fa", emissiveIntensity: 0.28 }) }).position(1.8, 0.52, 0).scale([0.12, 0.12, 0.02]).toJSON(),
|
|
2544
|
+
effects.particles({ name: "narrow upward lifetime colored gravity fountain plume", emitter: "fountain", color: options.color ?? "#60a5fa", particleCount: count, radius: 1.04, height: 3.05, intensity: 0.72, speed: 0.72, emissionRate, gravity: 9.8, groundCollision: true, lifetimeColorRamp: ["#fff7ad", "#fef08a", "#fb923c", "#60a5fa", "#38bdf8", "#fb7185"], materialMode: "splash", texturedBillboard: false, sizeOverLife: [0.82, 1.18, 0.8], alphaOverLife: [0.52, 0.82, 0.54], turbulence: 0.004, noise: 0.004, splashes: true, mist: false }).toJSON(),
|
|
2545
|
+
effects.particles({ name: "falling collision splash particle band", emitter: "fountain", color: "#bae6fd", particleCount: splashCount, radius: 1.9, height: 0.42, intensity: 0.58, speed: 0.66, emissionRate: Math.round(emissionRate * 0.32), gravity: 9.8, groundCollision: true, lifetimeColorRamp: ["#60a5fa", "#38bdf8", "#fb923c", "#fb7185", "#fff7ad", "#fef08a"], materialMode: "splash", texturedBillboard: false, sizeOverLife: [0.72, 1.02, 0.68], alphaOverLife: [0.38, 0.68, 0.34], turbulence: 0.006, noise: 0.006, splashes: true, mist: false }).toJSON(),
|
|
2546
|
+
effects.particles({ name: "older blue mist particles after ground collision", emitter: "swirl", color: "#60a5fa", particleCount: mistCount, radius: 2.2, height: 0.55, intensity: 0.42, speed: 0.32, emissionRate: Math.round(emissionRate * 0.24), gravity: 9.8, groundCollision: true, lifetimeColorRamp: ["#38bdf8", "#60a5fa", "#94a3b8"], materialMode: "soft-alpha", texturedBillboard: false, sizeOverLife: [0.48, 0.82, 0.42], alphaOverLife: [0.22, 0.48, 0.18], turbulence: 0.008, noise: 0.008, splashes: false, mist: true }).toJSON(),
|
|
2547
|
+
effects.bloom({ intensity: 0.035, color: options.color ?? "#bae6fd", threshold: 0.96, radius: 0.08, maxIntensity: 0.08 }).toJSON()
|
|
2548
|
+
];
|
|
2549
|
+
},
|
|
2150
2550
|
cityBlock: (options = {}) => {
|
|
2151
2551
|
const blocks = Math.max(3, Math.min(30, options.blocks ?? 20));
|
|
2152
2552
|
const timeOfDay = options.timeOfDay ?? "night";
|
|
@@ -2300,6 +2700,7 @@ export const prefabs = {
|
|
|
2300
2700
|
primitives.plane({ name: "distant seamless product photography backdrop", material: backdropMaterial }).position(0, 1.22, -2.28).rotate(-0.08, 0, 0).scale([3.6, 1, 1.8]).toJSON(),
|
|
2301
2701
|
primitives.cylinder({ name: "low matte hero product plinth", material: plinthMaterial }).position(0, 0.255, -0.65).scale([1.46, 0.26, 1.46]).toJSON(),
|
|
2302
2702
|
primitives.cylinder({ name: "soft product contact shadow from footprint", material: shadowMaterial }).position(0, 0.292, -0.65).scale([1.06, 0.01, 0.66]).toJSON(),
|
|
2703
|
+
primitives.box({ name: "subtle turntable orbit cue on product plinth", material: material.emissive({ color: "#93c5fd", emissive: "#38bdf8", emissiveIntensity: 0.22, opacity: 0.38 }) }).position(0, 0.52, -0.65).scale([1.02, 0.012, 1.02]).toJSON(),
|
|
2303
2704
|
lights.rect({ name: "off camera product key softbox sneaker mesh grazing light", position: [-2.4, 2.25, 2.25], intensity: 0.9, width: 3.2, height: 1.55, color: "#ffffff" }).toJSON(),
|
|
2304
2705
|
lights.rect({ name: "off camera cool reflection card fill softbox lace detail pin highlight", position: [2.4, 1.75, 1.85], intensity: 0.44, width: 2.6, height: 1.25, color: "#dbeafe" }).toJSON(),
|
|
2305
2706
|
lights.rect({ name: "rear warm reflection card rim softbox rubber sole edge kicker", position: [0, 1.85, -2.85], intensity: 0.72, width: 3.4, height: 0.9, color: "#c7d2fe" }).toJSON(),
|
|
@@ -2402,11 +2803,11 @@ export const prefabs = {
|
|
|
2402
2803
|
{ name: "Saturn", radius: 2.6, size: 0.19, color: "#fde68a", speed: 0.32, angle: 4.56, preset: "ringed" }
|
|
2403
2804
|
];
|
|
2404
2805
|
const nodes = [
|
|
2405
|
-
primitives.sphere({ name: "glowing labeled sun
|
|
2406
|
-
primitives.sphere({ name: "transparent golden sun corona
|
|
2806
|
+
primitives.sphere({ name: "glowing labeled sun", material: material.solarSun({ color: "#ffd166", coreColor: "#fff7ad", rimColor: "#f97316", emissiveIntensity: 2.55 }) }).position(0, 0.14, 0).scale(0.44).animate({ clip: "pulse", speed: 0.32 }).toJSON(),
|
|
2807
|
+
primitives.sphere({ name: "transparent golden sun corona", material: material.solarCorona({ color: "#ff9f1c", coreColor: "#ffd166", rimColor: "#f97316", opacity: 0.32, falloff: 2.6 }) }).position(0, 0.14, 0).scale(0.76).animate({ clip: "pulse", speed: 0.22 }).toJSON(),
|
|
2407
2808
|
primitives.sphere({ name: "wide amber solar glow halo shader", material: material.solarCorona({ color: "#7c2d12", coreColor: "#ffb347", rimColor: "#f97316", opacity: 0.12, falloff: 3.4, emissiveIntensity: 0.92 }) }).position(0, 0.14, 0).scale(1.08).animate({ clip: "pulse", speed: 0.18 }).toJSON(),
|
|
2408
2809
|
lights.point({ name: "warm solar key light", position: [0, 0.72, 0], color: "#ffd166", intensity: 0.75 }).toJSON(),
|
|
2409
|
-
effects.bloom({ intensity: 0.
|
|
2810
|
+
effects.bloom({ intensity: 0.32, color: "#ffd166", threshold: 0.84, radius: 0.22, maxIntensity: 0.32 }).toJSON()
|
|
2410
2811
|
];
|
|
2411
2812
|
const orbitAnimationFor = (position, speed) => ({
|
|
2412
2813
|
clip: "orbit",
|
|
@@ -2519,6 +2920,8 @@ export const prefabs = {
|
|
|
2519
2920
|
primitives.box({ name: "matte chart floor slab", material: material.pbr({ color: themePalette.floor, roughness: 0.68, metallic: 0.08 }) }).position(0, -0.035, 0).scale([floorSpan, 0.035, floorSpan]).toJSON(),
|
|
2520
2921
|
primitives.box({ name: "dark rear chart wall", material: material.pbr({ color: themePalette.wall, roughness: 0.52, metallic: 0.1, opacity: 0.72 }) }).position(0, 1.12, -halfSpan - 0.55).scale([floorSpan, 2.3, 0.055]).toJSON(),
|
|
2521
2922
|
primitives.box({ name: "left analytics side wall", material: material.pbr({ color: themePalette.side, roughness: 0.58, metallic: 0.08, opacity: 0.58 }) }).position(-halfSpan - 0.55, 1.0, 0).scale([0.055, 2.0, floorSpan]).toJSON(),
|
|
2923
|
+
primitives.box({ name: "readable 3D chart title backplate", material: material.emissive({ color: "#0f172a", emissive: "#38bdf8", emissiveIntensity: 0.42, opacity: 0.78 }) }).position(0, 2.38, -halfSpan - 0.48).scale([2.6, 0.28, 0.04]).toJSON(),
|
|
2924
|
+
primitives.box({ name: "selected metric hover readout panel", material: material.pbr({ color: "#020617", roughness: 0.68, metallic: 0.02, opacity: 0.56 }) }).position(halfSpan + 0.62, 1.85, -halfSpan - 0.42).scale([0.72, 0.52, 0.04]).toJSON(),
|
|
2522
2925
|
primitives.box({ name: "x axis rail", material: material.emissive({ color: "#d9f8ff", emissive: "#d9f8ff" }) }).position(0, 0.025, halfSpan + 0.36).scale([floorSpan - 0.55, 0.035, 0.035]).toJSON(),
|
|
2523
2926
|
primitives.box({ name: "z axis rail", material: material.emissive({ color: "#ffd166", emissive: "#ffd166" }) }).position(-halfSpan - 0.36, 0.025, 0).scale([0.035, 0.035, floorSpan - 0.55]).toJSON(),
|
|
2524
2927
|
primitives.box({ name: "height axis rail", material: material.emissive({ color: "#8fd7e8", emissive: "#4bb7d0" }) }).position(-halfSpan - 0.36, maxHeight / 2, halfSpan + 0.36).scale([0.04, maxHeight, 0.04]).toJSON(),
|
|
@@ -2658,26 +3061,49 @@ export const prefabs = {
|
|
|
2658
3061
|
primitives.box({ name: "center flythrough camera path glow", material: material.emissive({ color: "#2d98ba", emissive: "#67e8f9", emissiveIntensity: 0.92 }) }).position(0, -0.5, -3.9).scale([0.04, 0.028, 8.0]).toJSON(),
|
|
2659
3062
|
primitives.box({ name: "tiny vanishing point glow beyond tunnel", material: material.emissive({ color: "#4c1d95", emissive: "#a78bfa", emissiveIntensity: 1.05 }) }).position(0, 0.32, -10.4).scale([0.42, 0.42, 0.052]).toJSON()
|
|
2660
3063
|
];
|
|
2661
|
-
// Default hero frames use restrained rings and floor reflections; inspection
|
|
2662
|
-
// clutter such as braces and speed dashes is intentionally not emitted.
|
|
2663
3064
|
for (let index = 0; index < rings; index += 1) {
|
|
2664
3065
|
const progress = rings <= 1 ? 0 : index / (rings - 1);
|
|
2665
3066
|
const z = 0.45 - index * 0.38;
|
|
2666
3067
|
const scale = 1.36 - progress * 0.58;
|
|
2667
3068
|
const color = palette[index % palette.length];
|
|
2668
3069
|
const mat = material.emissive({ color, emissive: color, emissiveIntensity: 1.02 - progress * 0.5 });
|
|
3070
|
+
nodes.push(primitives.box({ name: `receding neon tunnel top segment ${index + 1}`, material: mat }).position(0, 1.12 * scale, z).scale([1.94 * scale, 0.035, 0.18]).animate({ clip: "pulse", speed: 0.16 + (index % 4) * 0.04 }).toJSON());
|
|
2669
3071
|
nodes.push(primitives.torus({ name: `true circular neon tunnel tube ring ${index + 1}`, material: mat }).position(0, 0.32 * scale, z - 0.018).scale([1.94 * scale, 1.48 * scale, 1]).animate({ clip: "pulse", speed: 0.16 + (index % 4) * 0.04 }).toJSON());
|
|
2670
3072
|
nodes.push(primitives.box({ name: `floor reflection streak ${index + 1}`, material: material.emissive({ color, emissive: color, opacity: 0.34, emissiveIntensity: 0.62 }) }).position(0, -0.505, z + 0.06).scale([1.08 * scale, 0.018, 0.082]).animate({ clip: "pulse", speed: 0.12 + (index % 3) * 0.05 }).toJSON());
|
|
3073
|
+
nodes.push(primitives.box({ name: `left wall speed dash ${index + 1}`, material: material.emissive({ color, emissive: color, opacity: 0.52, emissiveIntensity: 0.72 }) }).position(-1.04 * scale, 0.18, z).rotate(0, 0, 0.12).scale([0.028, 0.12, 0.18]).animate({ clip: "pulse", speed: 0.14 + (index % 5) * 0.03 }).toJSON());
|
|
3074
|
+
}
|
|
3075
|
+
for (let index = 0; index < rings * 2; index += 1) {
|
|
3076
|
+
const progress = rings <= 1 ? 0 : (index % rings) / (rings - 1);
|
|
3077
|
+
const z = 0.45 - (index % rings) * 0.38;
|
|
3078
|
+
const scale = 1.36 - progress * 0.58;
|
|
3079
|
+
const color = palette[index % palette.length];
|
|
3080
|
+
const isLeft = index % 2 === 0;
|
|
3081
|
+
nodes.push(primitives.box({
|
|
3082
|
+
name: `${isLeft ? "left" : "right"} curved tube wall chord ${index + 1}`,
|
|
3083
|
+
material: material.emissive({ color, emissive: color, opacity: 0.28, emissiveIntensity: 0.48 })
|
|
3084
|
+
}).position(isLeft ? -0.97 * scale : 0.97 * scale, 0.72 * scale, z).rotate(0, isLeft ? 0.18 : -0.18, 0).scale([0.035, 0.82 * scale, 0.18]).toJSON());
|
|
3085
|
+
}
|
|
3086
|
+
for (let index = 0; index < rings * 4; index += 1) {
|
|
3087
|
+
const ringIndex = Math.floor(index / 4);
|
|
3088
|
+
const progress = rings <= 1 ? 0 : ringIndex / (rings - 1);
|
|
3089
|
+
const z = 0.45 - ringIndex * 0.38;
|
|
3090
|
+
const scale = 1.36 - progress * 0.58;
|
|
3091
|
+
const color = palette[index % palette.length];
|
|
3092
|
+
const offset = (index % 4) * 0.25;
|
|
3093
|
+
nodes.push(primitives.box({
|
|
3094
|
+
name: `neon tunnel diagonal brace ${index + 1}`,
|
|
3095
|
+
material: material.emissive({ color, emissive: color, opacity: 0.38, emissiveIntensity: 0.58 })
|
|
3096
|
+
}).position(-0.82 * scale + offset * scale, 0.45 * scale, z).rotate(0, 0, 0.42 + offset * 0.3).scale([0.24 * scale, 0.028, 0.028]).toJSON());
|
|
2671
3097
|
}
|
|
2672
|
-
for (let index = 0; index <
|
|
3098
|
+
for (let index = 0; index < 14; index += 1) {
|
|
2673
3099
|
const color = palette[index % palette.length];
|
|
2674
3100
|
nodes.push(primitives.sphere({
|
|
2675
3101
|
name: `floating tunnel spark ${index + 1}`,
|
|
2676
3102
|
material: material.emissive({ color, emissive: color })
|
|
2677
|
-
}).position(seededRange(index, 901, -0.
|
|
3103
|
+
}).position(seededRange(index, 901, -0.92, 0.92), seededRange(index, 902, -0.42, 1.12), seededRange(index, 903, -9.2, -0.6)).scale(seededRange(index, 904, 0.018, 0.048)).animate({ clip: "float", speed: seededRange(index, 905, 0.14, 0.46) }).toJSON());
|
|
2678
3104
|
}
|
|
2679
3105
|
nodes.push(effects.fog({ density: 0.065, color: "#3b4f7a" }).toJSON());
|
|
2680
|
-
nodes.push(effects.particles({ name: "ambient tunnel dust particles", emitter: "ambient", color: "#a5f3fc", particleCount:
|
|
3106
|
+
nodes.push(effects.particles({ name: "ambient tunnel dust particles", emitter: "ambient", color: "#a5f3fc", particleCount: 1100, radius: 2.4, height: 1.6, intensity: 0.28, speed: 0.38 }).toJSON());
|
|
2681
3107
|
nodes.push(effects.bloom({ intensity: Math.min(0.3, options.bloomIntensity ?? 0.2), color: palette[1], threshold: 0.9, radius: 0.22, maxIntensity: 0.28 }).toJSON());
|
|
2682
3108
|
return nodes;
|
|
2683
3109
|
},
|
|
@@ -3200,6 +3626,287 @@ export function createAuraGameRuntime(options = {}) {
|
|
|
3200
3626
|
bodies: []
|
|
3201
3627
|
};
|
|
3202
3628
|
}
|
|
3629
|
+
export function createGameRacingPresentationTrackNodes(options) {
|
|
3630
|
+
const mode = options.mode ?? "standalone";
|
|
3631
|
+
const routePoints = options.route.points.map((point) => options.sceneBinding.toScenePoint(point, options.roadY ?? 0.012));
|
|
3632
|
+
if (routePoints.length < 2)
|
|
3633
|
+
return [];
|
|
3634
|
+
const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
|
|
3635
|
+
if (mode === "asset-overlay" && guideVisibility === "public")
|
|
3636
|
+
return [];
|
|
3637
|
+
const isCircuitStage = mode === "game-circuit";
|
|
3638
|
+
const roadWidth = Math.max((options.route.width ?? 0.18) * options.sceneBinding.transform.scale * (isCircuitStage ? 3.05 : 1.5), isCircuitStage ? 0.42 : 0.18);
|
|
3639
|
+
const nodes = [];
|
|
3640
|
+
if (mode === "standalone" || mode === "game-circuit") {
|
|
3641
|
+
const bounds = boundsForPresentationPoints(routePoints, roadWidth * (isCircuitStage ? 4.5 : 3.2));
|
|
3642
|
+
nodes.push(primitives.box({
|
|
3643
|
+
name: "scene-bound racing terrain pad",
|
|
3644
|
+
material: material.pbr({ color: options.terrainColor ?? (isCircuitStage ? "#1f342e" : "#263b35"), roughness: 0.92, metallic: 0 })
|
|
3645
|
+
})
|
|
3646
|
+
.position(bounds.centerX, -0.032, bounds.centerZ)
|
|
3647
|
+
.scale([bounds.width, 0.035, bounds.depth])
|
|
3648
|
+
.toJSON());
|
|
3649
|
+
if (isCircuitStage) {
|
|
3650
|
+
nodes.push(primitives.box({
|
|
3651
|
+
name: "scene-bound racing pit lane apron",
|
|
3652
|
+
material: material.pbr({ color: "#27323a", roughness: 0.86, metallic: 0 })
|
|
3653
|
+
})
|
|
3654
|
+
.position(roundGamePresentation(bounds.centerX - bounds.width * 0.12), 0.002, roundGamePresentation(bounds.centerZ + bounds.depth * 0.23))
|
|
3655
|
+
.rotate(0, -0.16, 0)
|
|
3656
|
+
.scale([Math.max(0.8, bounds.width * 0.42), 0.026, roadWidth * 0.58])
|
|
3657
|
+
.toJSON());
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
for (let index = 0; index < routePoints.length - 1; index += 1) {
|
|
3661
|
+
const start = routePoints[index];
|
|
3662
|
+
const end = routePoints[index + 1];
|
|
3663
|
+
if (!start || !end)
|
|
3664
|
+
continue;
|
|
3665
|
+
const segment = segmentPresentation(start, end);
|
|
3666
|
+
if (segment.length <= 0.02)
|
|
3667
|
+
continue;
|
|
3668
|
+
if (mode === "asset-overlay") {
|
|
3669
|
+
nodes.push(primitives.box({
|
|
3670
|
+
name: `asset-bound racing topology line ${index + 1}`,
|
|
3671
|
+
material: material.emissive({
|
|
3672
|
+
color: options.laneColor ?? "#d8f6ff",
|
|
3673
|
+
emissive: options.laneColor ?? "#8df4ff",
|
|
3674
|
+
emissiveIntensity: 0.48,
|
|
3675
|
+
roughness: 0.42
|
|
3676
|
+
})
|
|
3677
|
+
})
|
|
3678
|
+
.position(segment.midX, segment.midY + 0.038, segment.midZ)
|
|
3679
|
+
.rotate(0, -segment.angle, 0)
|
|
3680
|
+
.scale([Math.max(0.06, segment.length * 0.68), 0.008, 0.014])
|
|
3681
|
+
.toJSON());
|
|
3682
|
+
continue;
|
|
3683
|
+
}
|
|
3684
|
+
const sideX = -Math.sin(segment.angle);
|
|
3685
|
+
const sideZ = Math.cos(segment.angle);
|
|
3686
|
+
const y = segment.midY;
|
|
3687
|
+
nodes.push(primitives.box({
|
|
3688
|
+
name: `scene-bound racing road segment ${index + 1}`,
|
|
3689
|
+
material: material.pbr({ color: options.roadColor ?? "#343b3f", roughness: 0.76, metallic: 0 })
|
|
3690
|
+
})
|
|
3691
|
+
.position(segment.midX, y, segment.midZ)
|
|
3692
|
+
.rotate(0, -segment.angle, 0)
|
|
3693
|
+
.scale([segment.length + roadWidth * 0.6, 0.03, roadWidth])
|
|
3694
|
+
.toJSON(), primitives.box({
|
|
3695
|
+
name: `scene-bound racing center stripe ${index + 1}`,
|
|
3696
|
+
material: material.emissive({
|
|
3697
|
+
color: options.laneColor ?? "#d8f6ff",
|
|
3698
|
+
emissive: options.laneColor ?? "#8df4ff",
|
|
3699
|
+
emissiveIntensity: 0.7,
|
|
3700
|
+
roughness: 0.4
|
|
3701
|
+
})
|
|
3702
|
+
})
|
|
3703
|
+
.position(segment.midX, y + 0.022, segment.midZ)
|
|
3704
|
+
.rotate(0, -segment.angle, 0)
|
|
3705
|
+
.scale([Math.max(0.08, segment.length * (isCircuitStage ? 0.44 : 0.72)), 0.012, 0.014])
|
|
3706
|
+
.toJSON(), primitives.box({
|
|
3707
|
+
name: `scene-bound racing left curb ${index + 1}`,
|
|
3708
|
+
material: material.pbr({ color: index % 2 === 0 ? "#f4f7fb" : options.curbColor ?? "#df3550", roughness: 0.62 })
|
|
3709
|
+
})
|
|
3710
|
+
.position(roundGamePresentation(segment.midX + sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ + sideZ * roadWidth * 0.54))
|
|
3711
|
+
.rotate(0, -segment.angle, 0)
|
|
3712
|
+
.scale([Math.max(0.08, segment.length * 0.82), 0.015, 0.02])
|
|
3713
|
+
.toJSON(), primitives.box({
|
|
3714
|
+
name: `scene-bound racing right curb ${index + 1}`,
|
|
3715
|
+
material: material.pbr({ color: index % 2 === 0 ? options.curbColor ?? "#df3550" : "#f4f7fb", roughness: 0.62 })
|
|
3716
|
+
})
|
|
3717
|
+
.position(roundGamePresentation(segment.midX - sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ - sideZ * roadWidth * 0.54))
|
|
3718
|
+
.rotate(0, -segment.angle, 0)
|
|
3719
|
+
.scale([Math.max(0.08, segment.length * 0.82), 0.015, 0.02])
|
|
3720
|
+
.toJSON());
|
|
3721
|
+
}
|
|
3722
|
+
options.sceneBinding.checkpointScenePoints.forEach((point, index) => {
|
|
3723
|
+
nodes.push(primitives.box({
|
|
3724
|
+
name: `${mode === "asset-overlay" ? "asset-bound" : "scene-bound"} racing checkpoint gate ${index + 1}`,
|
|
3725
|
+
material: material.emissive({
|
|
3726
|
+
color: "#f8f1bf",
|
|
3727
|
+
emissive: "#f6e27a",
|
|
3728
|
+
emissiveIntensity: mode === "asset-overlay" ? 0.38 : 0.56,
|
|
3729
|
+
roughness: 0.46
|
|
3730
|
+
})
|
|
3731
|
+
})
|
|
3732
|
+
.position(point[0], point[1] + (mode === "asset-overlay" ? 0.045 : 0.026), point[2])
|
|
3733
|
+
.scale([roadWidth * (mode === "asset-overlay" ? 0.9 : 1.25), mode === "asset-overlay" ? 0.01 : 0.014, 0.026])
|
|
3734
|
+
.toJSON());
|
|
3735
|
+
});
|
|
3736
|
+
const start = routePoints[0];
|
|
3737
|
+
if (start) {
|
|
3738
|
+
nodes.push(primitives.box({
|
|
3739
|
+
name: `${mode === "asset-overlay" ? "asset-bound" : "scene-bound"} racing start finish band`,
|
|
3740
|
+
material: material.emissive({
|
|
3741
|
+
color: "#ffffff",
|
|
3742
|
+
emissive: "#ffffff",
|
|
3743
|
+
emissiveIntensity: mode === "asset-overlay" ? 0.36 : 0.5,
|
|
3744
|
+
roughness: 0.3
|
|
3745
|
+
})
|
|
3746
|
+
})
|
|
3747
|
+
.position(start[0], start[1] + (mode === "asset-overlay" ? 0.052 : 0.03), start[2])
|
|
3748
|
+
.scale([roadWidth * (mode === "asset-overlay" ? 1.05 : 1.45), mode === "asset-overlay" ? 0.01 : 0.014, 0.035])
|
|
3749
|
+
.toJSON());
|
|
3750
|
+
}
|
|
3751
|
+
return nodes;
|
|
3752
|
+
}
|
|
3753
|
+
export function createGamePlatformerPresentationSurfaceNodes(options) {
|
|
3754
|
+
const mode = options.mode ?? "standalone";
|
|
3755
|
+
const platforms = options.level.platforms ?? [];
|
|
3756
|
+
const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
|
|
3757
|
+
if (mode === "asset-overlay" && guideVisibility === "public")
|
|
3758
|
+
return [];
|
|
3759
|
+
const isGameLevel = mode === "game-level";
|
|
3760
|
+
const nodes = [];
|
|
3761
|
+
if ((mode === "standalone" || mode === "game-level") && platforms.length > 0) {
|
|
3762
|
+
const rects = platforms.map((surface) => options.sceneBinding.surfaceToSceneRect(surface));
|
|
3763
|
+
const bounds = boundsForPresentationRects(rects, isGameLevel ? 0.78 : 0.42);
|
|
3764
|
+
nodes.push(primitives.box({
|
|
3765
|
+
name: "scene-bound platformer backdrop",
|
|
3766
|
+
material: material.pbr({ color: isGameLevel ? "#263d48" : "#314d59", roughness: 0.94, metallic: 0, opacity: isGameLevel ? 0.2 : 0.28 })
|
|
3767
|
+
})
|
|
3768
|
+
.position(bounds.centerX, bounds.centerY + (isGameLevel ? 0.18 : 0.08), bounds.backZ)
|
|
3769
|
+
.scale([bounds.width, Math.max(0.2, bounds.height * (isGameLevel ? 2.25 : 1.8)), 0.035])
|
|
3770
|
+
.toJSON());
|
|
3771
|
+
}
|
|
3772
|
+
for (const surface of platforms) {
|
|
3773
|
+
const rect = options.sceneBinding.surfaceToSceneRect(surface);
|
|
3774
|
+
const width = Math.max(rect.size[0], 0.08);
|
|
3775
|
+
const height = Math.max(rect.size[1], 0.055);
|
|
3776
|
+
if (mode === "asset-overlay") {
|
|
3777
|
+
nodes.push(primitives.box({
|
|
3778
|
+
name: `asset-bound platformer contact strip ${surface.id}`,
|
|
3779
|
+
material: material.emissive({
|
|
3780
|
+
color: options.platformTrimColor ?? "#c9f7ff",
|
|
3781
|
+
emissive: options.platformTrimColor ?? "#a8f4ff",
|
|
3782
|
+
emissiveIntensity: 0.28,
|
|
3783
|
+
roughness: 0.44
|
|
3784
|
+
})
|
|
3785
|
+
})
|
|
3786
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.026), rect.center[2] + 0.24)
|
|
3787
|
+
.scale([width * 0.78, 0.012, 0.024])
|
|
3788
|
+
.toJSON());
|
|
3789
|
+
continue;
|
|
3790
|
+
}
|
|
3791
|
+
nodes.push(primitives.box({
|
|
3792
|
+
name: `scene-bound platformer surface ${surface.id}`,
|
|
3793
|
+
material: material.pbr({ color: options.platformColor ?? (isGameLevel ? "#526972" : "#718994"), roughness: 0.72, metallic: 0 })
|
|
3794
|
+
})
|
|
3795
|
+
.position(rect.center[0], rect.center[1], rect.center[2])
|
|
3796
|
+
.scale([width, height, isGameLevel ? 0.64 : 0.5])
|
|
3797
|
+
.toJSON(), primitives.box({
|
|
3798
|
+
name: `scene-bound platformer surface trim ${surface.id}`,
|
|
3799
|
+
material: material.emissive({
|
|
3800
|
+
color: options.platformTrimColor ?? "#c9f7ff",
|
|
3801
|
+
emissive: options.platformTrimColor ?? "#a8f4ff",
|
|
3802
|
+
emissiveIntensity: 0.36,
|
|
3803
|
+
roughness: 0.38
|
|
3804
|
+
})
|
|
3805
|
+
})
|
|
3806
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.012), rect.center[2] + (isGameLevel ? 0.334 : 0.262))
|
|
3807
|
+
.scale([width * 0.92, 0.016, 0.035])
|
|
3808
|
+
.toJSON());
|
|
3809
|
+
}
|
|
3810
|
+
for (const hazard of options.level.hazards ?? []) {
|
|
3811
|
+
const rect = options.sceneBinding.surfaceToSceneRect(hazard);
|
|
3812
|
+
nodes.push(primitives.box({
|
|
3813
|
+
name: `scene-bound platformer hazard ${hazard.id}`,
|
|
3814
|
+
material: material.emissive({
|
|
3815
|
+
color: options.hazardColor ?? "#ff6b6b",
|
|
3816
|
+
emissive: options.hazardColor ?? "#ff6b6b",
|
|
3817
|
+
emissiveIntensity: 0.7,
|
|
3818
|
+
roughness: 0.36
|
|
3819
|
+
})
|
|
3820
|
+
})
|
|
3821
|
+
.position(rect.center[0], rect.center[1], rect.center[2] + (isGameLevel ? 0.39 : 0.31))
|
|
3822
|
+
.scale([Math.max(rect.size[0], 0.045), Math.max(rect.size[1], 0.045), 0.055])
|
|
3823
|
+
.toJSON());
|
|
3824
|
+
}
|
|
3825
|
+
for (const collectible of options.level.collectibles ?? []) {
|
|
3826
|
+
const point = options.sceneBinding.toScenePoint(collectible, 0.04);
|
|
3827
|
+
nodes.push(primitives.torus({
|
|
3828
|
+
name: `scene-bound platformer collectible ${collectible.id}`,
|
|
3829
|
+
material: material.emissive({
|
|
3830
|
+
color: options.collectibleColor ?? "#fff3a3",
|
|
3831
|
+
emissive: options.collectibleColor ?? "#f6dc67",
|
|
3832
|
+
emissiveIntensity: 0.62,
|
|
3833
|
+
roughness: 0.4
|
|
3834
|
+
})
|
|
3835
|
+
})
|
|
3836
|
+
.position(point[0], point[1], point[2] + (isGameLevel ? 0.26 : 0.16))
|
|
3837
|
+
.rotate(Math.PI / 2, 0, 0)
|
|
3838
|
+
.scale([0.045, 0.045, 0.012])
|
|
3839
|
+
.toJSON());
|
|
3840
|
+
}
|
|
3841
|
+
for (const checkpoint of options.level.checkpoints ?? []) {
|
|
3842
|
+
const point = options.sceneBinding.toScenePoint(checkpoint, 0.02);
|
|
3843
|
+
nodes.push(primitives.box({
|
|
3844
|
+
name: `scene-bound platformer checkpoint ${checkpoint.id}`,
|
|
3845
|
+
material: material.emissive({ color: "#d7f9ff", emissive: "#a6f4ff", emissiveIntensity: 0.48, roughness: 0.4 })
|
|
3846
|
+
})
|
|
3847
|
+
.position(point[0], point[1], point[2] + (isGameLevel ? 0.29 : 0.19))
|
|
3848
|
+
.scale([0.026, 0.22, 0.034])
|
|
3849
|
+
.toJSON());
|
|
3850
|
+
}
|
|
3851
|
+
if (options.level.finish) {
|
|
3852
|
+
const finish = options.sceneBinding.toScenePoint(options.level.finish, 0.04);
|
|
3853
|
+
nodes.push(primitives.box({
|
|
3854
|
+
name: "scene-bound platformer finish marker",
|
|
3855
|
+
material: material.emissive({
|
|
3856
|
+
color: options.finishColor ?? "#b6ffbd",
|
|
3857
|
+
emissive: options.finishColor ?? "#83f58f",
|
|
3858
|
+
emissiveIntensity: 0.6,
|
|
3859
|
+
roughness: 0.42
|
|
3860
|
+
})
|
|
3861
|
+
})
|
|
3862
|
+
.position(finish[0], finish[1], finish[2] + (isGameLevel ? 0.34 : 0.22))
|
|
3863
|
+
.scale([0.06, 0.34, 0.05])
|
|
3864
|
+
.toJSON());
|
|
3865
|
+
}
|
|
3866
|
+
return nodes;
|
|
3867
|
+
}
|
|
3868
|
+
function boundsForPresentationPoints(points, padding) {
|
|
3869
|
+
const xs = points.map((point) => point[0]);
|
|
3870
|
+
const zs = points.map((point) => point[2]);
|
|
3871
|
+
const minX = Math.min(...xs);
|
|
3872
|
+
const maxX = Math.max(...xs);
|
|
3873
|
+
const minZ = Math.min(...zs);
|
|
3874
|
+
const maxZ = Math.max(...zs);
|
|
3875
|
+
return {
|
|
3876
|
+
centerX: roundGamePresentation((minX + maxX) / 2),
|
|
3877
|
+
centerZ: roundGamePresentation((minZ + maxZ) / 2),
|
|
3878
|
+
width: roundGamePresentation(Math.max(1.2, maxX - minX + padding)),
|
|
3879
|
+
depth: roundGamePresentation(Math.max(1.2, maxZ - minZ + padding))
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
function boundsForPresentationRects(rects, padding) {
|
|
3883
|
+
const minX = Math.min(...rects.map((rect) => rect.center[0] - rect.size[0] / 2));
|
|
3884
|
+
const maxX = Math.max(...rects.map((rect) => rect.center[0] + rect.size[0] / 2));
|
|
3885
|
+
const minY = Math.min(...rects.map((rect) => rect.center[1] - rect.size[1] / 2));
|
|
3886
|
+
const maxY = Math.max(...rects.map((rect) => rect.center[1] + rect.size[1] / 2));
|
|
3887
|
+
const z = Math.min(...rects.map((rect) => rect.center[2]));
|
|
3888
|
+
return {
|
|
3889
|
+
centerX: roundGamePresentation((minX + maxX) / 2),
|
|
3890
|
+
centerY: roundGamePresentation((minY + maxY) / 2),
|
|
3891
|
+
backZ: roundGamePresentation(z - 0.22),
|
|
3892
|
+
width: roundGamePresentation(Math.max(1, maxX - minX + padding)),
|
|
3893
|
+
height: roundGamePresentation(Math.max(0.6, maxY - minY + padding))
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
function segmentPresentation(start, end) {
|
|
3897
|
+
const dx = end[0] - start[0];
|
|
3898
|
+
const dz = end[2] - start[2];
|
|
3899
|
+
return {
|
|
3900
|
+
midX: roundGamePresentation((start[0] + end[0]) / 2),
|
|
3901
|
+
midY: roundGamePresentation((start[1] + end[1]) / 2),
|
|
3902
|
+
midZ: roundGamePresentation((start[2] + end[2]) / 2),
|
|
3903
|
+
length: roundGamePresentation(Math.hypot(dx, dz)),
|
|
3904
|
+
angle: Math.atan2(dz, dx)
|
|
3905
|
+
};
|
|
3906
|
+
}
|
|
3907
|
+
function roundGamePresentation(value) {
|
|
3908
|
+
return Math.round(value * 1000) / 1000;
|
|
3909
|
+
}
|
|
3203
3910
|
export const game = {
|
|
3204
3911
|
createRuntime: createAuraGameRuntime,
|
|
3205
3912
|
rules: gameRules,
|
|
@@ -3220,8 +3927,23 @@ export const game = {
|
|
|
3220
3927
|
simulation: createGameSimulation,
|
|
3221
3928
|
runSimulation: runGameSimulation,
|
|
3222
3929
|
inspector: createGameInspector,
|
|
3930
|
+
locomotion: createGameLocomotionKit,
|
|
3931
|
+
assetBoundPlatformerLevel: createGameAssetBoundPlatformerLevel,
|
|
3932
|
+
platformerSceneBinding: createGamePlatformerSceneBinding,
|
|
3933
|
+
platformerPresentationCamera: createGamePlatformerPresentationCamera,
|
|
3934
|
+
platformerPresentationSurfaces: createGamePlatformerPresentationSurfaceNodes,
|
|
3935
|
+
platformer: createGamePlatformerKit,
|
|
3936
|
+
assetBoundRacingRoute: createGameAssetBoundRacingRoute,
|
|
3937
|
+
racingSceneBinding: createGameRacingSceneBinding,
|
|
3938
|
+
racingPresentationCamera: createGameRacingPresentationCamera,
|
|
3939
|
+
racingPresentationTrack: createGameRacingPresentationTrackNodes,
|
|
3940
|
+
racing: createGameRacingKit,
|
|
3941
|
+
fallingBlocks: createGameFallingBlocksKit,
|
|
3942
|
+
fallingBlockPieces: GAME_FALLING_BLOCK_PIECES,
|
|
3943
|
+
eventLog: createGameEventLog,
|
|
3223
3944
|
touchControls: createGameTouchControlLayout,
|
|
3224
3945
|
kinematicBody: createGameKinematicBody,
|
|
3946
|
+
collisionWorld: createGameCollisionWorld,
|
|
3225
3947
|
jumpAssist: createGameJumpAssist,
|
|
3226
3948
|
collider: {
|
|
3227
3949
|
box: createGameBoxCollider,
|
|
@@ -3254,6 +3976,12 @@ export const game = {
|
|
|
3254
3976
|
timer: createGameHudTimerBinding,
|
|
3255
3977
|
combo: createGameHudComboBinding,
|
|
3256
3978
|
round: createGameHudRoundBinding,
|
|
3979
|
+
score: createGameHudScoreBinding,
|
|
3980
|
+
lives: createGameHudLivesBinding,
|
|
3981
|
+
objective: createGameHudObjectiveBinding,
|
|
3982
|
+
checkpoint: createGameHudCheckpointBinding,
|
|
3983
|
+
value: createGameHudValueBinding,
|
|
3984
|
+
eventLog: createGameHudEventLogBinding,
|
|
3257
3985
|
debugToggle: createGameHudDebugToggleBinding,
|
|
3258
3986
|
bindings: createGameHudBindings,
|
|
3259
3987
|
snapshot: createGameHudSnapshot
|
|
@@ -3506,10 +4234,10 @@ function createHierarchicalPrimitiveHumanoid(options = {}) {
|
|
|
3506
4234
|
];
|
|
3507
4235
|
}
|
|
3508
4236
|
function createLowPolyHumanoid(options = {}) {
|
|
3509
|
-
//
|
|
3510
|
-
// bundled GLB
|
|
3511
|
-
//
|
|
3512
|
-
return
|
|
4237
|
+
// Deliberate decision (1ca640ed, confirmed 2026-06): the benchmark humanoid defaults to the
|
|
4238
|
+
// bundled soldier GLB, superseding the earlier "Prompt 09 stays asset-free" constraint.
|
|
4239
|
+
// Asset-free primitive rigs remain available via character.primitiveHumanoid / prefabs.primitiveHumanoid.
|
|
4240
|
+
return createAuthoredLowPolyHumanoid(options);
|
|
3513
4241
|
}
|
|
3514
4242
|
function createBenchmarkBoxLowPolyHumanoid(options = {}) {
|
|
3515
4243
|
const clip = options.clip ?? "benchmark-pose";
|
|
@@ -3628,29 +4356,30 @@ function createAuthoredLowPolyHumanoid(options = {}) {
|
|
|
3628
4356
|
const speed = clip === "run" ? 1.18 : clip === "idle" || clip === "pose" ? 0.42 : 0.78;
|
|
3629
4357
|
const facing = pose === "side-view" ? 0.72 : pose === "planted-foot" ? -0.08 : -0.24;
|
|
3630
4358
|
const motionModel = createAuthoredHumanoidMotionModel(clip);
|
|
4359
|
+
const captureTime = clip === "benchmark-pose" ? 0.72 : motionModel.footPlanting.captureTime;
|
|
3631
4360
|
const nodes = [
|
|
3632
4361
|
primitives.plane({ name: "humanoid grounded capture floor", material: material.pbr({ color: "#17251c", roughness: 0.9, metallic: 0.01 }) }).position(0, -0.024, -0.55).scale([3.8, 1, 2.6]).toJSON(),
|
|
3633
4362
|
primitives.box({ name: "subtle authored humanoid walking path stripe", material: material.pbr({ color: "#2f3b45", roughness: 0.84, metallic: 0.01 }) }).position(0, 0.006, -0.52).scale([1.58, 0.012, 0.16]).toJSON(),
|
|
3634
4363
|
primitives.cylinder({ name: "authored humanoid soft contact shadow", material: material.pbr({ color: "#020617", roughness: 0.95, metallic: 0.01, opacity: 0.42 }) }).position(0.02, 0.012, -0.5).scale([0.62, 0.012, 0.4]).toJSON(),
|
|
3635
4364
|
model(builtInCharacterAssets.humanoid, {
|
|
3636
|
-
name: "authored skinned
|
|
4365
|
+
name: "authored skinned humanoid character model",
|
|
3637
4366
|
castShadow: true,
|
|
3638
4367
|
receiveShadow: true
|
|
3639
4368
|
})
|
|
3640
4369
|
.position(0, 0, -0.56)
|
|
3641
4370
|
.rotate(0, facing, 0)
|
|
3642
4371
|
.scale(1.0)
|
|
3643
|
-
.animate({ clip: glbClip, speed, loop: true, captureTime
|
|
4372
|
+
.animate({ clip: glbClip, speed, loop: true, captureTime })
|
|
3644
4373
|
.toJSON(),
|
|
3645
4374
|
effects.contactOcclusion({ name: "authored humanoid renderer contact occlusion", intensity: 0.3, radius: 0.58 }).toJSON(),
|
|
3646
|
-
group("authored skinned
|
|
4375
|
+
group("authored skinned humanoid rig metadata", [], {
|
|
3647
4376
|
character: {
|
|
3648
4377
|
skeleton: createPrimitiveHumanoidSkeleton("mannequin"),
|
|
3649
4378
|
clip,
|
|
3650
4379
|
pose,
|
|
3651
4380
|
rootBob: clip !== "idle" && clip !== "pose",
|
|
3652
4381
|
limbSwing: "joint-hierarchy",
|
|
3653
|
-
footPlanting: motionModel.footPlanting,
|
|
4382
|
+
footPlanting: { ...motionModel.footPlanting, captureTime },
|
|
3654
4383
|
rootMotion: motionModel.rootMotion,
|
|
3655
4384
|
constraints: motionModel.constraints
|
|
3656
4385
|
},
|
|
@@ -3697,6 +4426,8 @@ function createAuthoredHumanoidMotionModel(clip) {
|
|
|
3697
4426
|
};
|
|
3698
4427
|
}
|
|
3699
4428
|
function mapAuraClipToBuiltInHumanoidClip(clip) {
|
|
4429
|
+
// Only Idle/Run/TPose/Walk exist in humanoid-fixture.glb; aura clips without an
|
|
4430
|
+
// authored equivalent (wave, turn) fall back to the nearest stationary clip.
|
|
3700
4431
|
if (clip === "idle")
|
|
3701
4432
|
return "Idle";
|
|
3702
4433
|
if (clip === "walk" || clip === "benchmark-pose")
|
|
@@ -5006,6 +5737,8 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
|
|
|
5006
5737
|
}
|
|
5007
5738
|
} : {}),
|
|
5008
5739
|
morphTargets,
|
|
5740
|
+
activeMorphTargets: { ...(input.activeMorphTargets ?? {}) },
|
|
5741
|
+
missingMorphTargets: [...(input.missingMorphTargets ?? [])],
|
|
5009
5742
|
...(input.bounds ? { bounds: isRuntimeNodeEvidenceBounds(input.bounds) ? input.bounds : calculateRuntimeNodeBounds(input.bounds) } : {}),
|
|
5010
5743
|
renderItemCount: input.renderItemCount ?? 0,
|
|
5011
5744
|
skinnedRenderItemCount: input.skinnedRenderItemCount ?? 0,
|
|
@@ -5256,6 +5989,8 @@ function cloneRuntimeImportedAssetEvidence(evidence) {
|
|
|
5256
5989
|
clips: [...evidence.clips],
|
|
5257
5990
|
skinningPalette: evidence.skinningPalette ? { ...evidence.skinningPalette } : undefined,
|
|
5258
5991
|
morphTargets: [...evidence.morphTargets],
|
|
5992
|
+
activeMorphTargets: { ...evidence.activeMorphTargets },
|
|
5993
|
+
missingMorphTargets: [...evidence.missingMorphTargets],
|
|
5259
5994
|
bounds: evidence.bounds
|
|
5260
5995
|
? {
|
|
5261
5996
|
...evidence.bounds,
|
|
@@ -5279,10 +6014,11 @@ function sanitizeRuntimeMorphWeight(weight) {
|
|
|
5279
6014
|
export function createAuraApp(target, options) {
|
|
5280
6015
|
let snapshot = normalizeSceneSnapshot(options.scene);
|
|
5281
6016
|
let renderSnapshot = flattenSceneSnapshot(snapshot);
|
|
5282
|
-
const
|
|
6017
|
+
const rendererSelection = normalizeCreateAppRendererOptions(options.renderer);
|
|
6018
|
+
const diagnosticsState = createInitialDiagnostics(renderSnapshot, options.renderer);
|
|
5283
6019
|
const canvas = resolveCanvas(target);
|
|
5284
6020
|
if (canvas) {
|
|
5285
|
-
configureCanvas(canvas, options.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
|
|
6021
|
+
configureCanvas(canvas, options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
|
|
5286
6022
|
}
|
|
5287
6023
|
const overlay = canvas && shouldRenderOverlay(options.diagnostics, snapshot) ? createDiagnosticsOverlay(canvas, diagnosticsState) : undefined;
|
|
5288
6024
|
let disposed = false;
|
|
@@ -5319,7 +6055,7 @@ export function createAuraApp(target, options) {
|
|
|
5319
6055
|
};
|
|
5320
6056
|
const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && renderSnapshot.nodes.some(isWebGLRenderableNode) && typeof window !== "undefined");
|
|
5321
6057
|
const resetDiagnosticsForCurrentScene = (backend) => {
|
|
5322
|
-
const fresh = createInitialDiagnostics(renderSnapshot);
|
|
6058
|
+
const fresh = createInitialDiagnostics(renderSnapshot, options.renderer);
|
|
5323
6059
|
diagnosticsState.backend = backend;
|
|
5324
6060
|
diagnosticsState.fps = fresh.fps;
|
|
5325
6061
|
diagnosticsState.drawCalls = fresh.drawCalls;
|
|
@@ -5368,7 +6104,7 @@ export function createAuraApp(target, options) {
|
|
|
5368
6104
|
overlay?.update();
|
|
5369
6105
|
const revision = ++mountRevision;
|
|
5370
6106
|
if (shouldUseProductionRenderer && canvas) {
|
|
5371
|
-
void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused)
|
|
6107
|
+
void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused, runtimeNodes)
|
|
5372
6108
|
.then((controller) => {
|
|
5373
6109
|
if (disposed || revision !== mountRevision) {
|
|
5374
6110
|
controller.dispose();
|
|
@@ -5696,12 +6432,12 @@ function isRenderableModelNode(node) {
|
|
|
5696
6432
|
function isWebGLRenderableNode(node) {
|
|
5697
6433
|
return isRenderableModelNode(node) || node.kind === "primitive";
|
|
5698
6434
|
}
|
|
5699
|
-
async function startProductionRender(canvas, snapshot, diagnosticsState, options, overlay, beforeRender, isPaused = () => false) {
|
|
6435
|
+
async function startProductionRender(canvas, snapshot, diagnosticsState, options, overlay, beforeRender, isPaused = () => false, runtimeNodes) {
|
|
5700
6436
|
const renderableNode = snapshot.nodes.find(isWebGLRenderableNode);
|
|
5701
6437
|
if (!renderableNode) {
|
|
5702
6438
|
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().");
|
|
5703
6439
|
}
|
|
5704
|
-
const renderer = await createProductionSceneRenderer(canvas, snapshot);
|
|
6440
|
+
const renderer = await createProductionSceneRenderer(canvas, snapshot, options.renderer, runtimeNodes);
|
|
5705
6441
|
diagnosticsState.renderer = renderer.diagnostics;
|
|
5706
6442
|
const continuousRender = shouldContinuouslyRender(snapshot);
|
|
5707
6443
|
let disposed = false;
|
|
@@ -5715,7 +6451,7 @@ async function startProductionRender(canvas, snapshot, diagnosticsState, options
|
|
|
5715
6451
|
if (!isPaused())
|
|
5716
6452
|
beforeRender?.(delta / 1000, "raf");
|
|
5717
6453
|
const drawCalls = renderer.render(time);
|
|
5718
|
-
diagnosticsState.backend =
|
|
6454
|
+
diagnosticsState.backend = renderer.backend;
|
|
5719
6455
|
diagnosticsState.fps = diagnosticsState.fps || 60;
|
|
5720
6456
|
diagnosticsState.drawCalls = drawCalls;
|
|
5721
6457
|
diagnosticsState.renderSize = [canvas.width, canvas.height];
|
|
@@ -5754,8 +6490,494 @@ function shouldContinuouslyRender(snapshot) {
|
|
|
5754
6490
|
return node.effect === "particles" || node.effect === "rain";
|
|
5755
6491
|
});
|
|
5756
6492
|
}
|
|
5757
|
-
async function createProductionSceneRenderer(canvas, snapshot) {
|
|
5758
|
-
|
|
6493
|
+
async function createProductionSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes) {
|
|
6494
|
+
const rendererSelection = normalizeCreateAppRendererOptions(rendererOptions);
|
|
6495
|
+
if (rendererSelection.mode !== "production") {
|
|
6496
|
+
return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [], runtimeNodes);
|
|
6497
|
+
}
|
|
6498
|
+
const flattened = groups.flatten(snapshot.nodes);
|
|
6499
|
+
const eligibility = analyzeProductionBridgeEligibility(flattened);
|
|
6500
|
+
if (!eligibility.eligible) {
|
|
6501
|
+
return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [
|
|
6502
|
+
`Production bridge skipped: ${eligibility.reasons.join("; ")}.`
|
|
6503
|
+
], runtimeNodes);
|
|
6504
|
+
}
|
|
6505
|
+
try {
|
|
6506
|
+
return await createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes);
|
|
6507
|
+
}
|
|
6508
|
+
catch (error) {
|
|
6509
|
+
return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [
|
|
6510
|
+
`Production bridge failed and safe-basic fallback rendered instead: ${productionRenderErrorMessage(error)}`
|
|
6511
|
+
], runtimeNodes);
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
const PRODUCTION_RUNTIME_POSTPROCESS = {
|
|
6515
|
+
targetFormat: "rgba8",
|
|
6516
|
+
toneMapping: {
|
|
6517
|
+
exposure: 1.14,
|
|
6518
|
+
whitePoint: 1.18,
|
|
6519
|
+
operator: "filmic",
|
|
6520
|
+
inputColorSpace: "linear",
|
|
6521
|
+
outputColorSpace: "srgb"
|
|
6522
|
+
},
|
|
6523
|
+
colorGrade: {
|
|
6524
|
+
contrast: 1.1,
|
|
6525
|
+
saturation: 1.06,
|
|
6526
|
+
vibrance: 0.12,
|
|
6527
|
+
vignette: 0.08,
|
|
6528
|
+
sharpening: 0.24
|
|
6529
|
+
},
|
|
6530
|
+
fxaa: {
|
|
6531
|
+
edgeThreshold: 0.08,
|
|
6532
|
+
subpixelBlend: 0.55
|
|
6533
|
+
}
|
|
6534
|
+
};
|
|
6535
|
+
const PRODUCTION_RUNTIME_SHADOWS = {
|
|
6536
|
+
enabled: true,
|
|
6537
|
+
size: 1024,
|
|
6538
|
+
bias: 0.0014,
|
|
6539
|
+
strength: 0.28,
|
|
6540
|
+
pcfRadius: 1.2,
|
|
6541
|
+
pcfSamples: 9,
|
|
6542
|
+
filter: "pcf",
|
|
6543
|
+
label: "aura3d-root-production-shadow-map"
|
|
6544
|
+
};
|
|
6545
|
+
let cachedProductionRuntimeLights;
|
|
6546
|
+
function createProductionRuntimeEnvironment(snapshot) {
|
|
6547
|
+
const names = groups.flatten(snapshot.nodes).map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
|
|
6548
|
+
const category = resolveRendererSceneCategory(snapshot, names);
|
|
6549
|
+
const preset = category === "city-day" ? "daylight"
|
|
6550
|
+
: category === "city-night" ? "evening"
|
|
6551
|
+
: category === "game" ? "gameplay"
|
|
6552
|
+
: category === "material" ? "inspection"
|
|
6553
|
+
: "studio";
|
|
6554
|
+
const bundle = createExternalParityEnvironmentLighting(preset);
|
|
6555
|
+
return {
|
|
6556
|
+
preset,
|
|
6557
|
+
intensity: bundle.lighting.environmentMapIntensity ?? bundle.lighting.intensity,
|
|
6558
|
+
evidence: `production bridge submitted generated HDR ${preset} environment lighting through RenderSource.environmentLighting`,
|
|
6559
|
+
lighting: bundle.lighting
|
|
6560
|
+
};
|
|
6561
|
+
}
|
|
6562
|
+
function createProductionRuntimePostprocess() {
|
|
6563
|
+
return PRODUCTION_RUNTIME_POSTPROCESS;
|
|
6564
|
+
}
|
|
6565
|
+
function createProductionRuntimeShadowOptions() {
|
|
6566
|
+
return PRODUCTION_RUNTIME_SHADOWS;
|
|
6567
|
+
}
|
|
6568
|
+
function createProductionRuntimePostprocessObservation(diagnostics) {
|
|
6569
|
+
const actualPasses = diagnostics.postprocessPassNames ?? [];
|
|
6570
|
+
const pixelBacked = actualPasses.length > 0 && diagnostics.lastError === null;
|
|
6571
|
+
return {
|
|
6572
|
+
renderPass: actualPasses.length > 0,
|
|
6573
|
+
outputPass: pixelBacked,
|
|
6574
|
+
bloomPass: actualPasses.includes("bloom"),
|
|
6575
|
+
ambientOcclusionPass: actualPasses.includes("ssao"),
|
|
6576
|
+
contactOcclusionReceiver: actualPasses.includes("contact-shadow"),
|
|
6577
|
+
pixelBacked,
|
|
6578
|
+
actualPasses,
|
|
6579
|
+
fallbackPasses: pixelBacked ? [] : ["direct-render"]
|
|
6580
|
+
};
|
|
6581
|
+
}
|
|
6582
|
+
function createProductionRuntimeCollectedLights() {
|
|
6583
|
+
if (cachedProductionRuntimeLights)
|
|
6584
|
+
return cachedProductionRuntimeLights;
|
|
6585
|
+
const key = new DirectionalLight("aura3d-root-production-key-shadow");
|
|
6586
|
+
const fill = new DirectionalLight("aura3d-root-production-fill");
|
|
6587
|
+
const rim = new DirectionalLight("aura3d-root-production-rim");
|
|
6588
|
+
key.castsShadow = true;
|
|
6589
|
+
key.color = [1, 0.94, 0.82];
|
|
6590
|
+
key.intensity = 2.6;
|
|
6591
|
+
fill.color = [0.52, 0.64, 0.9];
|
|
6592
|
+
fill.intensity = 0.64;
|
|
6593
|
+
rim.color = [0.88, 0.94, 1];
|
|
6594
|
+
rim.intensity = 0.88;
|
|
6595
|
+
cachedProductionRuntimeLights = [
|
|
6596
|
+
{
|
|
6597
|
+
kind: "directional",
|
|
6598
|
+
color: [1, 0.94, 0.82],
|
|
6599
|
+
intensity: 2.6,
|
|
6600
|
+
position: [0, 0, 0],
|
|
6601
|
+
direction: [0.44, -0.64, -0.63],
|
|
6602
|
+
range: 0,
|
|
6603
|
+
spotAngle: 0,
|
|
6604
|
+
penumbra: 0,
|
|
6605
|
+
castsShadow: true,
|
|
6606
|
+
layerMask: 0xffffffff,
|
|
6607
|
+
source: key
|
|
6608
|
+
},
|
|
6609
|
+
{
|
|
6610
|
+
kind: "directional",
|
|
6611
|
+
color: [0.52, 0.64, 0.9],
|
|
6612
|
+
intensity: 0.64,
|
|
6613
|
+
position: [0, 0, 0],
|
|
6614
|
+
direction: [-0.5, -0.28, -0.82],
|
|
6615
|
+
range: 0,
|
|
6616
|
+
spotAngle: 0,
|
|
6617
|
+
penumbra: 0,
|
|
6618
|
+
castsShadow: false,
|
|
6619
|
+
layerMask: 0xffffffff,
|
|
6620
|
+
source: fill
|
|
6621
|
+
},
|
|
6622
|
+
{
|
|
6623
|
+
kind: "directional",
|
|
6624
|
+
color: [0.88, 0.94, 1],
|
|
6625
|
+
intensity: 0.88,
|
|
6626
|
+
position: [0, 0, 0],
|
|
6627
|
+
direction: [-0.24, -0.36, 0.9],
|
|
6628
|
+
range: 0,
|
|
6629
|
+
spotAngle: 0,
|
|
6630
|
+
penumbra: 0,
|
|
6631
|
+
castsShadow: false,
|
|
6632
|
+
layerMask: 0xffffffff,
|
|
6633
|
+
source: rim
|
|
6634
|
+
}
|
|
6635
|
+
];
|
|
6636
|
+
return cachedProductionRuntimeLights;
|
|
6637
|
+
}
|
|
6638
|
+
async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes) {
|
|
6639
|
+
const flattened = groups.flatten(snapshot.nodes);
|
|
6640
|
+
const modelNodes = flattened.filter((node) => isRenderableModelNode(node) && createAssetProvenance(node.asset).source === "typed-aura-assets-manifest");
|
|
6641
|
+
const actorEntries = await Promise.all(modelNodes.map(async (node, index) => ({
|
|
6642
|
+
node,
|
|
6643
|
+
actor: await createTypedGLBActor({
|
|
6644
|
+
asset: node.asset,
|
|
6645
|
+
id: node.runtime?.id ?? node.asset.id ?? `model-${index + 1}`,
|
|
6646
|
+
name: node.name ?? node.asset.id ?? `model-${index + 1}`,
|
|
6647
|
+
width: canvas.width,
|
|
6648
|
+
height: canvas.height,
|
|
6649
|
+
...(node.material?.color ? { tint: { baseColor: colorToRgba(node.material.color) } } : {})
|
|
6650
|
+
})
|
|
6651
|
+
})));
|
|
6652
|
+
const primitiveEntries = createProductionRuntimePrimitiveEntries(flattened);
|
|
6653
|
+
const productionRenderer = await ProductionRuntimeRenderer.create({
|
|
6654
|
+
canvas,
|
|
6655
|
+
width: canvas.width,
|
|
6656
|
+
height: canvas.height,
|
|
6657
|
+
backend: "webgl2",
|
|
6658
|
+
antialias: true,
|
|
6659
|
+
preserveDrawingBuffer: true,
|
|
6660
|
+
clearColor: colorToClearColor(snapshot.background)
|
|
6661
|
+
});
|
|
6662
|
+
let latestDeviceDiagnostics = productionRenderer.getDiagnostics();
|
|
6663
|
+
let latestFeatures = productionRenderer.getFeatures();
|
|
6664
|
+
const runtimeWarnings = new Set();
|
|
6665
|
+
const productionEnvironment = createProductionRuntimeEnvironment(snapshot);
|
|
6666
|
+
const buildDiagnostics = () => createRendererDiagnosticReport(snapshot, {
|
|
6667
|
+
mounted: true,
|
|
6668
|
+
backend: "production-runtime",
|
|
6669
|
+
postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
|
|
6670
|
+
environment: {
|
|
6671
|
+
enabled: true,
|
|
6672
|
+
preset: productionEnvironment.preset,
|
|
6673
|
+
intensity: productionEnvironment.intensity,
|
|
6674
|
+
evidence: productionEnvironment.evidence
|
|
6675
|
+
},
|
|
6676
|
+
warnings: [
|
|
6677
|
+
`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}.`,
|
|
6678
|
+
...(flattened.some((node) => node.kind === "effect")
|
|
6679
|
+
? ["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."]
|
|
6680
|
+
: []),
|
|
6681
|
+
...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
|
|
6682
|
+
...latestFeatures
|
|
6683
|
+
.filter((feature) => feature.state !== "supported")
|
|
6684
|
+
.map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
|
|
6685
|
+
...runtimeWarnings
|
|
6686
|
+
]
|
|
6687
|
+
}, rendererOptions);
|
|
6688
|
+
return {
|
|
6689
|
+
get backend() {
|
|
6690
|
+
return productionRenderer.backend;
|
|
6691
|
+
},
|
|
6692
|
+
get diagnostics() {
|
|
6693
|
+
return buildDiagnostics();
|
|
6694
|
+
},
|
|
6695
|
+
render(time) {
|
|
6696
|
+
runtimeWarnings.clear();
|
|
6697
|
+
const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, productionEnvironment.lighting);
|
|
6698
|
+
const result = productionRenderer.renderInteractiveFrame(input);
|
|
6699
|
+
latestDeviceDiagnostics = result.diagnostics;
|
|
6700
|
+
latestFeatures = result.features;
|
|
6701
|
+
return latestDeviceDiagnostics.drawCalls;
|
|
6702
|
+
},
|
|
6703
|
+
dispose() {
|
|
6704
|
+
productionRenderer.dispose();
|
|
6705
|
+
for (const { actor } of actorEntries)
|
|
6706
|
+
actor.pipeline.dispose();
|
|
6707
|
+
for (const { geometry, material } of primitiveEntries) {
|
|
6708
|
+
geometry.dispose();
|
|
6709
|
+
material.dispose();
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
};
|
|
6713
|
+
}
|
|
6714
|
+
function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, environmentLighting) {
|
|
6715
|
+
const items = [];
|
|
6716
|
+
for (const entry of actorEntries) {
|
|
6717
|
+
const currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
|
|
6718
|
+
const currentNode = currentState.node;
|
|
6719
|
+
if (currentNode.visible === false)
|
|
6720
|
+
continue;
|
|
6721
|
+
applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings);
|
|
6722
|
+
applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
|
|
6723
|
+
const modelMatrix = [...createModelMatrix(currentNode, boundsFromAuraAsset(currentNode.asset), shouldNormalizeModelNode(currentNode), time)];
|
|
6724
|
+
const actorItems = entry.actor.collectRenderItems({ modelMatrix });
|
|
6725
|
+
items.push(...actorItems);
|
|
6726
|
+
attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
|
|
6727
|
+
}
|
|
6728
|
+
for (const entry of primitiveEntries) {
|
|
6729
|
+
const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
|
|
6730
|
+
if (!currentState.visible)
|
|
6731
|
+
continue;
|
|
6732
|
+
items.push({
|
|
6733
|
+
geometry: entry.geometry,
|
|
6734
|
+
material: entry.material,
|
|
6735
|
+
modelMatrix: createModelMatrix(currentState.node, entry.bounds, false, time),
|
|
6736
|
+
label: currentState.node.name ?? `aura-primitive-${currentState.node.primitive}`,
|
|
6737
|
+
includeInAutoFrame: false
|
|
6738
|
+
});
|
|
6739
|
+
}
|
|
6740
|
+
const viewProjectionMatrix = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
|
|
6741
|
+
const cameraPosition = resolveCameraEye(snapshot, snapshot.camera, time, runtimeNodes);
|
|
6742
|
+
const source = {
|
|
6743
|
+
collectRenderItems: () => items,
|
|
6744
|
+
cameraPolicy: "require",
|
|
6745
|
+
staticBatching: false,
|
|
6746
|
+
frustumCulling: false,
|
|
6747
|
+
collectedLights: createProductionRuntimeCollectedLights(),
|
|
6748
|
+
environmentLighting,
|
|
6749
|
+
postprocess: createProductionRuntimePostprocess(),
|
|
6750
|
+
shadow: createProductionRuntimeShadowOptions(),
|
|
6751
|
+
cameraPosition
|
|
6752
|
+
};
|
|
6753
|
+
const cameraLike = { viewProjectionMatrix };
|
|
6754
|
+
return {
|
|
6755
|
+
source,
|
|
6756
|
+
camera: cameraLike,
|
|
6757
|
+
metadata: createProductionRuntimeMetadata(actorEntries)
|
|
6758
|
+
};
|
|
6759
|
+
}
|
|
6760
|
+
function createProductionRuntimePrimitiveEntries(nodes) {
|
|
6761
|
+
return nodes
|
|
6762
|
+
.filter((node) => node.kind === "primitive")
|
|
6763
|
+
.map((node) => ({
|
|
6764
|
+
node,
|
|
6765
|
+
geometry: createProductionPrimitiveGeometry(node),
|
|
6766
|
+
material: createProductionPrimitiveMaterial(node),
|
|
6767
|
+
bounds: primitiveGeometryBounds(node.primitive)
|
|
6768
|
+
}));
|
|
6769
|
+
}
|
|
6770
|
+
function resolveProductionPrimitiveRuntimeState(entry, runtimeNodes) {
|
|
6771
|
+
const runtimeId = entry.node.runtime?.id;
|
|
6772
|
+
if (!runtimeId)
|
|
6773
|
+
return { node: entry.node, visible: true };
|
|
6774
|
+
const runtimeSnapshot = runtimeNodes?.get(runtimeId)?.snapshot();
|
|
6775
|
+
if (!runtimeSnapshot)
|
|
6776
|
+
return { node: entry.node, visible: true };
|
|
6777
|
+
return {
|
|
6778
|
+
node: {
|
|
6779
|
+
...entry.node,
|
|
6780
|
+
position: runtimeSnapshot.position,
|
|
6781
|
+
rotation: runtimeSnapshot.rotation,
|
|
6782
|
+
scale: runtimeSnapshot.scale,
|
|
6783
|
+
animation: runtimeSnapshot.animation ?? entry.node.animation
|
|
6784
|
+
},
|
|
6785
|
+
visible: runtimeSnapshot.visible
|
|
6786
|
+
};
|
|
6787
|
+
}
|
|
6788
|
+
function createProductionPrimitiveMaterial(node) {
|
|
6789
|
+
const materialSpec = node.material;
|
|
6790
|
+
const baseColor = colorToRgba(materialSpec?.color ?? materialSpec?.emissive ?? "#d7dee8");
|
|
6791
|
+
const opacity = clamp01(materialSpec?.opacity ?? baseColor[3] ?? 1);
|
|
6792
|
+
const emissiveColor = materialSpec?.emissive ? colorToRgb(materialSpec.emissive) : [0, 0, 0];
|
|
6793
|
+
return new PBRMaterial({
|
|
6794
|
+
name: `a3d-production-primitive-${node.primitive}${node.name ? `-${node.name}` : ""}`,
|
|
6795
|
+
baseColor: [baseColor[0], baseColor[1], baseColor[2], opacity],
|
|
6796
|
+
metallic: clamp01(materialSpec?.metallic ?? materialSpec?.metalness ?? 0),
|
|
6797
|
+
roughness: clamp01(materialSpec?.roughness ?? 0.58),
|
|
6798
|
+
emissiveColor,
|
|
6799
|
+
emissiveStrength: Math.max(0, materialSpec?.emissiveIntensity ?? (materialSpec?.emissive ? 1.35 : 0)),
|
|
6800
|
+
clearcoatFactor: clamp01(materialSpec?.clearcoat ?? 0),
|
|
6801
|
+
clearcoatRoughnessFactor: clamp01(materialSpec?.clearcoatRoughness ?? 0.34),
|
|
6802
|
+
environmentIntensity: Math.max(0, materialSpec?.envMapIntensity ?? 0.75),
|
|
6803
|
+
renderState: {
|
|
6804
|
+
blend: opacity < 0.999,
|
|
6805
|
+
depthWrite: opacity >= 0.999,
|
|
6806
|
+
cullMode: node.primitive === "plane" || opacity < 0.999 ? "none" : "back"
|
|
6807
|
+
}
|
|
6808
|
+
});
|
|
6809
|
+
}
|
|
6810
|
+
function createProductionPrimitiveGeometry(node) {
|
|
6811
|
+
return createProductionGeometryFromPrimitiveMesh(createProductionPrimitiveMesh(node.primitive));
|
|
6812
|
+
}
|
|
6813
|
+
function primitiveGeometryBounds(primitive) {
|
|
6814
|
+
return createProductionPrimitiveMesh(primitive).bounds;
|
|
6815
|
+
}
|
|
6816
|
+
function createProductionPrimitiveMesh(primitive) {
|
|
6817
|
+
if (primitive === "sphere")
|
|
6818
|
+
return createSphereGeometry();
|
|
6819
|
+
if (primitive === "capsule")
|
|
6820
|
+
return createCapsuleApproxGeometry();
|
|
6821
|
+
if (primitive === "torus")
|
|
6822
|
+
return createTorusGeometry();
|
|
6823
|
+
if (primitive === "box")
|
|
6824
|
+
return createBoxGeometry();
|
|
6825
|
+
if (primitive === "cylinder")
|
|
6826
|
+
return createCylinderGeometry();
|
|
6827
|
+
return createPlaneGeometry();
|
|
6828
|
+
}
|
|
6829
|
+
function createProductionGeometryFromPrimitiveMesh(mesh) {
|
|
6830
|
+
const vertexCount = mesh.positions.length / 3;
|
|
6831
|
+
const vertices = new VertexBuffer(VertexFormat.P3N3, vertexCount);
|
|
6832
|
+
for (let index = 0; index < vertexCount; index += 1) {
|
|
6833
|
+
const base = index * 3;
|
|
6834
|
+
vertices.setAttribute(index, "position", [mesh.positions[base] ?? 0, mesh.positions[base + 1] ?? 0, mesh.positions[base + 2] ?? 0]);
|
|
6835
|
+
vertices.setAttribute(index, "normal", [mesh.normals[base] ?? 0, mesh.normals[base + 1] ?? 1, mesh.normals[base + 2] ?? 0]);
|
|
6836
|
+
}
|
|
6837
|
+
return new Geometry(vertices, new IndexBuffer(Array.from(mesh.indices), vertexCount), "triangles", mesh.bounds);
|
|
6838
|
+
}
|
|
6839
|
+
function resolveProductionActorRuntimeState(entry, runtimeNodes) {
|
|
6840
|
+
const runtimeId = entry.node.runtime?.id;
|
|
6841
|
+
if (!runtimeId)
|
|
6842
|
+
return { node: entry.node };
|
|
6843
|
+
const runtimeSnapshot = runtimeNodes?.get(runtimeId)?.snapshot();
|
|
6844
|
+
if (!runtimeSnapshot)
|
|
6845
|
+
return { node: entry.node };
|
|
6846
|
+
const morphTargets = runtimeSnapshot.morphTargets ?? {};
|
|
6847
|
+
return {
|
|
6848
|
+
node: {
|
|
6849
|
+
...entry.node,
|
|
6850
|
+
position: runtimeSnapshot.position,
|
|
6851
|
+
rotation: runtimeSnapshot.rotation,
|
|
6852
|
+
scale: runtimeSnapshot.scale,
|
|
6853
|
+
visible: runtimeSnapshot.visible,
|
|
6854
|
+
animation: runtimeSnapshot.animation ?? entry.node.animation
|
|
6855
|
+
},
|
|
6856
|
+
animationBinding: runtimeSnapshot.animationBinding,
|
|
6857
|
+
...(Object.keys(morphTargets).length > 0 ? { morphTargets } : {})
|
|
6858
|
+
};
|
|
6859
|
+
}
|
|
6860
|
+
function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings) {
|
|
6861
|
+
const animation = node.animation;
|
|
6862
|
+
if (!animation?.clip || isModelTransformAnimationClip(animation.clip))
|
|
6863
|
+
return;
|
|
6864
|
+
const clipName = entry.actor.animation.resolveClipName(animation.clip);
|
|
6865
|
+
if (!clipName) {
|
|
6866
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" has no clips for requested animation "${animation.clip}".`);
|
|
6867
|
+
return;
|
|
6868
|
+
}
|
|
6869
|
+
try {
|
|
6870
|
+
entry.actor.playClip(clipName, resolveProductionActorAnimationSeconds(animation, animationBinding, time));
|
|
6871
|
+
}
|
|
6872
|
+
catch (error) {
|
|
6873
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
function applyProductionActorMorphTargets(entry, morphTargets, runtimeWarnings) {
|
|
6877
|
+
if (!morphTargets || Object.keys(morphTargets).length === 0)
|
|
6878
|
+
return;
|
|
6879
|
+
try {
|
|
6880
|
+
const result = entry.actor.applyMorphTargets(morphTargets);
|
|
6881
|
+
for (const missing of result.missingTargets) {
|
|
6882
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" has no morph target "${missing}".`);
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
catch (error) {
|
|
6886
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply morph targets: ${productionRenderErrorMessage(error)}`);
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
function attachProductionActorEvidence(node, actor, renderItems, runtimeNodes) {
|
|
6890
|
+
if (!node.runtime?.id || !runtimeNodes?.has(node.runtime.id))
|
|
6891
|
+
return;
|
|
6892
|
+
const evidence = createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems);
|
|
6893
|
+
runtimeNodes.get(node.runtime.id)?.setImportedAssetEvidence(evidence);
|
|
6894
|
+
}
|
|
6895
|
+
function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
|
|
6896
|
+
const actorEvidence = actor.evidence;
|
|
6897
|
+
const skeletonBones = actor.pipeline.asset.skins.flatMap((skin) => skin.jointNames);
|
|
6898
|
+
const morphTargets = actor.pipeline.asset.meshes.flatMap((mesh) => mesh.morphTargets.map((target, index) => target.name ?? `${mesh.name}-morph-${index + 1}`));
|
|
6899
|
+
const morphApply = actorEvidence.lastMorphApply;
|
|
6900
|
+
return createRuntimeNodeImportedAssetEvidence({
|
|
6901
|
+
assetId: node.asset.id,
|
|
6902
|
+
...(node.runtime?.id ? { nodeId: node.runtime.id } : {}),
|
|
6903
|
+
skeletonBones,
|
|
6904
|
+
clips: actorEvidence.clips,
|
|
6905
|
+
...(actorEvidence.lastClip ? { activeClip: actorEvidence.lastClip } : {}),
|
|
6906
|
+
...(actorEvidence.skinningBindingCount > 0 ? {
|
|
6907
|
+
skinningPalette: {
|
|
6908
|
+
jointCount: actorEvidence.skinningBindingCount,
|
|
6909
|
+
matrixCount: actorEvidence.skinningBindingCount,
|
|
6910
|
+
updated: actorEvidence.lastSkinningPalettesUpdated > 0
|
|
6911
|
+
}
|
|
6912
|
+
} : {}),
|
|
6913
|
+
morphTargets,
|
|
6914
|
+
activeMorphTargets: morphApply?.activeWeights ?? {},
|
|
6915
|
+
missingMorphTargets: morphApply?.missingTargets ?? [],
|
|
6916
|
+
bounds: {
|
|
6917
|
+
position: node.position,
|
|
6918
|
+
scale: node.scale,
|
|
6919
|
+
size: node.asset.bounds
|
|
6920
|
+
},
|
|
6921
|
+
renderItemCount: renderItems.length,
|
|
6922
|
+
skinnedRenderItemCount: renderItems.filter((item) => item.skinning).length,
|
|
6923
|
+
morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length
|
|
6924
|
+
});
|
|
6925
|
+
}
|
|
6926
|
+
function createProductionRuntimeMetadata(actorEntries) {
|
|
6927
|
+
const metadata = actorEntries.map((entry) => entry.actor.pipeline.metadata);
|
|
6928
|
+
const primary = metadata[0];
|
|
6929
|
+
return {
|
|
6930
|
+
assetId: metadata.length === 1 ? primary?.assetId ?? "typed-glb" : `typed-glb-batch-${metadata.length}`,
|
|
6931
|
+
assetUri: metadata.length === 1 ? primary?.assetUri ?? "aura3d://typed-glb" : "aura3d://scene/typed-glb-batch",
|
|
6932
|
+
...(metadata.length === 1 && primary?.assetName ? { assetName: primary.assetName } : metadata.length > 1 ? { assetName: "Aura3D typed GLB scene batch" } : {}),
|
|
6933
|
+
meshCount: sumProductionMetadata(metadata, "meshCount"),
|
|
6934
|
+
primitiveCount: sumProductionMetadata(metadata, "primitiveCount"),
|
|
6935
|
+
materialCount: sumProductionMetadata(metadata, "materialCount"),
|
|
6936
|
+
textureCount: sumProductionMetadata(metadata, "textureCount"),
|
|
6937
|
+
imageCount: sumProductionMetadata(metadata, "imageCount"),
|
|
6938
|
+
animationCount: sumProductionMetadata(metadata, "animationCount"),
|
|
6939
|
+
skinCount: sumProductionMetadata(metadata, "skinCount"),
|
|
6940
|
+
morphTargetCount: sumProductionMetadata(metadata, "morphTargetCount"),
|
|
6941
|
+
extensionsUsed: [...new Set(metadata.flatMap((item) => item.extensionsUsed))]
|
|
6942
|
+
};
|
|
6943
|
+
}
|
|
6944
|
+
function sumProductionMetadata(metadata, key) {
|
|
6945
|
+
return metadata.reduce((total, item) => total + item[key], 0);
|
|
6946
|
+
}
|
|
6947
|
+
function boundsFromAuraAsset(asset) {
|
|
6948
|
+
const metadataBounds = asset.metadata?.boundsMetadata;
|
|
6949
|
+
const metadataMin = vec3FromReadonly(metadataBounds?.min);
|
|
6950
|
+
const metadataMax = vec3FromReadonly(metadataBounds?.max);
|
|
6951
|
+
if (metadataMin && metadataMax)
|
|
6952
|
+
return { min: metadataMin, max: metadataMax };
|
|
6953
|
+
const metadataSize = vec3FromReadonly(metadataBounds?.size);
|
|
6954
|
+
const metadataCenter = vec3FromReadonly(metadataBounds?.center);
|
|
6955
|
+
if (metadataSize) {
|
|
6956
|
+
const center = metadataCenter ?? [0, metadataSize[1] / 2, 0];
|
|
6957
|
+
return {
|
|
6958
|
+
min: [center[0] - metadataSize[0] / 2, center[1] - metadataSize[1] / 2, center[2] - metadataSize[2] / 2],
|
|
6959
|
+
max: [center[0] + metadataSize[0] / 2, center[1] + metadataSize[1] / 2, center[2] + metadataSize[2] / 2]
|
|
6960
|
+
};
|
|
6961
|
+
}
|
|
6962
|
+
const bounds = asset.bounds;
|
|
6963
|
+
if (!bounds || bounds.length < 3)
|
|
6964
|
+
return { min: [-0.5, 0, -0.5], max: [0.5, 1, 0.5] };
|
|
6965
|
+
const halfX = Math.max(0.001, bounds[0] / 2);
|
|
6966
|
+
const halfZ = Math.max(0.001, bounds[2] / 2);
|
|
6967
|
+
return {
|
|
6968
|
+
min: [-halfX, 0, -halfZ],
|
|
6969
|
+
max: [halfX, Math.max(0.001, bounds[1]), halfZ]
|
|
6970
|
+
};
|
|
6971
|
+
}
|
|
6972
|
+
function vec3FromReadonly(value) {
|
|
6973
|
+
if (!value || value.length < 3)
|
|
6974
|
+
return undefined;
|
|
6975
|
+
const vec = [Number(value[0]), Number(value[1]), Number(value[2])];
|
|
6976
|
+
return vec.every((component) => Number.isFinite(component)) ? vec : undefined;
|
|
6977
|
+
}
|
|
6978
|
+
function colorToRgba(color) {
|
|
6979
|
+
const [r, g, b, a] = colorToClearColor(color);
|
|
6980
|
+
return [r, g, b, a];
|
|
5759
6981
|
}
|
|
5760
6982
|
function colorToClearColor(color) {
|
|
5761
6983
|
if (typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color)) {
|
|
@@ -5797,6 +7019,16 @@ function resolveAnimationSeconds(animation, time) {
|
|
|
5797
7019
|
return rawSeconds;
|
|
5798
7020
|
return animation.loop === false ? Math.min(rawSeconds, duration) : rawSeconds % duration;
|
|
5799
7021
|
}
|
|
7022
|
+
function resolveProductionActorAnimationSeconds(animation, animationBinding, time) {
|
|
7023
|
+
if (!animation || animation.captureTime === undefined || !animationBinding?.controllerId) {
|
|
7024
|
+
return resolveAnimationSeconds(animation, time);
|
|
7025
|
+
}
|
|
7026
|
+
const duration = Math.max(0, animation.duration ?? 0);
|
|
7027
|
+
const phase = Math.max(0, animationBinding.captureTime ?? animationBinding.localTime ?? animation.captureTime);
|
|
7028
|
+
if (duration <= 0)
|
|
7029
|
+
return phase;
|
|
7030
|
+
return animation.loop === false ? Math.min(phase, duration) : phase % duration;
|
|
7031
|
+
}
|
|
5800
7032
|
function orbitAnimatedAngle(seconds, speed) {
|
|
5801
7033
|
return seconds * speed * 0.58;
|
|
5802
7034
|
}
|
|
@@ -5876,17 +7108,59 @@ function seededRange(index, salt, min, max) {
|
|
|
5876
7108
|
const normalized = value - Math.floor(value);
|
|
5877
7109
|
return min + (max - min) * normalized;
|
|
5878
7110
|
}
|
|
5879
|
-
function
|
|
7111
|
+
function findRuntimeCameraTarget(cameraSpec, runtimeNodes) {
|
|
7112
|
+
if (cameraSpec.mode !== "follow" || !cameraSpec.targetNode || !runtimeNodes)
|
|
7113
|
+
return undefined;
|
|
7114
|
+
const targetNode = cameraSpec.targetNode;
|
|
7115
|
+
const directHandle = runtimeNodes.get(targetNode);
|
|
7116
|
+
if (directHandle && directHandle.visible !== false)
|
|
7117
|
+
return { position: directHandle.position, rotation: directHandle.rotation };
|
|
7118
|
+
const namedHandle = runtimeNodes.all().find((handle) => handle.visible !== false && (handle.name === targetNode || handle.tags.includes(targetNode)));
|
|
7119
|
+
return namedHandle ? { position: namedHandle.position, rotation: namedHandle.rotation } : undefined;
|
|
7120
|
+
}
|
|
7121
|
+
function rotateVec3BySceneYaw(vector, yaw) {
|
|
7122
|
+
const cos = Math.cos(yaw);
|
|
7123
|
+
const sin = Math.sin(yaw);
|
|
7124
|
+
return [
|
|
7125
|
+
vector[0] * cos + vector[2] * sin,
|
|
7126
|
+
vector[1],
|
|
7127
|
+
-vector[0] * sin + vector[2] * cos
|
|
7128
|
+
];
|
|
7129
|
+
}
|
|
7130
|
+
function applyCameraOffset(cameraSpec, offset, target) {
|
|
7131
|
+
if (cameraSpec.offsetMode !== "target-yaw" || !target)
|
|
7132
|
+
return offset;
|
|
7133
|
+
return rotateVec3BySceneYaw(offset, target.rotation[1]);
|
|
7134
|
+
}
|
|
7135
|
+
function add3(left, right) {
|
|
7136
|
+
return [left[0] + right[0], left[1] + right[1], left[2] + right[2]];
|
|
7137
|
+
}
|
|
7138
|
+
function resolveRuntimeCameraTarget(cameraSpec, runtimeNodes) {
|
|
7139
|
+
return findRuntimeCameraTarget(cameraSpec, runtimeNodes);
|
|
7140
|
+
}
|
|
7141
|
+
function resolveCameraTarget(snapshot, cameraSpec, runtimeNodes) {
|
|
7142
|
+
const runtimeTarget = resolveRuntimeCameraTarget(cameraSpec, runtimeNodes);
|
|
7143
|
+
if (runtimeTarget) {
|
|
7144
|
+
const targetOffset = cameraSpec.targetOffset
|
|
7145
|
+
? applyCameraOffset(cameraSpec, cameraSpec.targetOffset, runtimeTarget)
|
|
7146
|
+
: [0, 0, 0];
|
|
7147
|
+
return add3(runtimeTarget.position, targetOffset);
|
|
7148
|
+
}
|
|
5880
7149
|
if (cameraSpec.mode === "follow" && cameraSpec.targetNode) {
|
|
5881
|
-
const targetNode = snapshot.nodes.find((node) => (node.kind === "model" || node.kind === "primitive") &&
|
|
5882
|
-
(node.name === cameraSpec.targetNode ||
|
|
5883
|
-
|
|
5884
|
-
|
|
7150
|
+
const targetNode = groups.flatten(snapshot.nodes).find((node) => (node.kind === "model" || node.kind === "primitive") &&
|
|
7151
|
+
(node.name === cameraSpec.targetNode ||
|
|
7152
|
+
(node.kind === "model" && node.asset.id === cameraSpec.targetNode) ||
|
|
7153
|
+
node.runtime?.id === cameraSpec.targetNode));
|
|
7154
|
+
if (targetNode?.position) {
|
|
7155
|
+
const targetOffset = cameraSpec.targetOffset ?? [0, 0, 0];
|
|
7156
|
+
return add3(targetNode.position, targetOffset);
|
|
7157
|
+
}
|
|
5885
7158
|
}
|
|
5886
7159
|
return cameraSpec.target ?? [0, 0.7, 0];
|
|
5887
7160
|
}
|
|
5888
|
-
function resolveCameraEye(snapshot, cameraSpec, time) {
|
|
5889
|
-
const target = resolveCameraTarget(snapshot, cameraSpec);
|
|
7161
|
+
function resolveCameraEye(snapshot, cameraSpec, time, runtimeNodes) {
|
|
7162
|
+
const target = resolveCameraTarget(snapshot, cameraSpec, runtimeNodes);
|
|
7163
|
+
const runtimeTarget = resolveRuntimeCameraTarget(cameraSpec, runtimeNodes);
|
|
5890
7164
|
let eye = cameraSpec.position ?? [0, 1.4, cameraSpec.distance ?? 4];
|
|
5891
7165
|
if (cameraSpec.mode === "orbit") {
|
|
5892
7166
|
const distance = cameraSpec.distance ?? 4;
|
|
@@ -5894,7 +7168,11 @@ function resolveCameraEye(snapshot, cameraSpec, time) {
|
|
|
5894
7168
|
}
|
|
5895
7169
|
if (cameraSpec.mode === "follow") {
|
|
5896
7170
|
const distance = cameraSpec.distance ?? 4;
|
|
5897
|
-
|
|
7171
|
+
const offset = cameraSpec.offset ? applyCameraOffset(cameraSpec, cameraSpec.offset, runtimeTarget) : undefined;
|
|
7172
|
+
eye = cameraSpec.position
|
|
7173
|
+
?? (offset
|
|
7174
|
+
? [target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]]
|
|
7175
|
+
: [target[0] - distance * 0.38, target[1] + distance * 0.52, target[2] + distance * 0.82]);
|
|
5898
7176
|
}
|
|
5899
7177
|
if (cameraSpec.mode === "dolly") {
|
|
5900
7178
|
const seconds = cameraSpec.seconds ?? 6;
|
|
@@ -5921,7 +7199,7 @@ function collectRuntimeEffectNodes(snapshot) {
|
|
|
5921
7199
|
function hasRuntimePostProcessEffects(effectNodes) {
|
|
5922
7200
|
return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
|
|
5923
7201
|
}
|
|
5924
|
-
async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
7202
|
+
async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runtimeWarnings = [], runtimeNodes) {
|
|
5925
7203
|
const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
|
|
5926
7204
|
if (!gl) {
|
|
5927
7205
|
throw new AuraRuntimeError("backend-fallback", "Aura3D could not create a WebGL2 renderer. Suggested fix: use a WebGL2-capable browser.");
|
|
@@ -5957,9 +7235,13 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
5957
7235
|
actualPasses: [],
|
|
5958
7236
|
fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
|
|
5959
7237
|
},
|
|
5960
|
-
warnings: [
|
|
5961
|
-
|
|
7238
|
+
warnings: [
|
|
7239
|
+
"Aura3D WebGL2 agent runtime renders typed models, primitives, and basic effects; advanced postprocess, environment prefiltering, shadow maps, and GLB animation mixers are reported as unsupported unless a route uses the typed-GLB production bridge.",
|
|
7240
|
+
...runtimeWarnings
|
|
7241
|
+
]
|
|
7242
|
+
}, rendererOptions);
|
|
5962
7243
|
return {
|
|
7244
|
+
backend: "webgl2",
|
|
5963
7245
|
diagnostics: runtimeRendererDiagnostics,
|
|
5964
7246
|
render(time) {
|
|
5965
7247
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
@@ -5968,14 +7250,23 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
5968
7250
|
let drawCalls = backdrop.render();
|
|
5969
7251
|
gl.useProgram(program.program);
|
|
5970
7252
|
gl.enable(gl.DEPTH_TEST);
|
|
5971
|
-
const viewProjection = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time);
|
|
7253
|
+
const viewProjection = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
|
|
5972
7254
|
gl.uniformMatrix4fv(program.uniforms.viewProjection, false, viewProjection);
|
|
5973
7255
|
gl.uniform3fv(program.uniforms.lightDirection, new Float32Array(normalize3([0.45, 0.82, 0.36])));
|
|
7256
|
+
gl.uniform1i(program.uniforms.baseColorTexture, 0);
|
|
7257
|
+
gl.uniform1i(program.uniforms.metallicRoughnessTexture, 1);
|
|
7258
|
+
gl.uniform1i(program.uniforms.occlusionTexture, 2);
|
|
7259
|
+
gl.uniform1i(program.uniforms.emissiveTexture, 3);
|
|
5974
7260
|
for (const modelEntry of models) {
|
|
7261
|
+
modelEntry.update?.(time);
|
|
5975
7262
|
const modelMatrix = modelEntry.modelMatrix ?? createModelMatrix(modelEntry.node, modelEntry.bounds, modelEntry.normalizeToUnit, time);
|
|
5976
|
-
gl.uniformMatrix4fv(program.uniforms.model, false, modelMatrix);
|
|
5977
7263
|
for (const primitiveEntry of modelEntry.primitives) {
|
|
7264
|
+
const primitiveMatrix = primitiveEntry.modelMatrix ? multiply4(modelMatrix, primitiveEntry.modelMatrix(time)) : modelMatrix;
|
|
7265
|
+
gl.uniformMatrix4fv(program.uniforms.model, false, primitiveMatrix);
|
|
5978
7266
|
gl.uniform3fv(program.uniforms.color, new Float32Array(primitiveEntry.color ?? modelEntry.color));
|
|
7267
|
+
gl.uniform1f(program.uniforms.metallic, primitiveEntry.metallic ?? 0);
|
|
7268
|
+
gl.uniform1f(program.uniforms.roughness, primitiveEntry.roughness ?? 0.72);
|
|
7269
|
+
gl.uniform3fv(program.uniforms.emissive, new Float32Array(primitiveEntry.emissive ?? [0, 0, 0]));
|
|
5979
7270
|
gl.bindBuffer(gl.ARRAY_BUFFER, primitiveEntry.position);
|
|
5980
7271
|
gl.enableVertexAttribArray(program.attributes.position);
|
|
5981
7272
|
gl.vertexAttribPointer(program.attributes.position, 3, gl.FLOAT, false, 0, 0);
|
|
@@ -5991,6 +7282,55 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
5991
7282
|
gl.disableVertexAttribArray(program.attributes.color);
|
|
5992
7283
|
gl.vertexAttrib3f(program.attributes.color, 1, 1, 1);
|
|
5993
7284
|
}
|
|
7285
|
+
if (primitiveEntry.uv) {
|
|
7286
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, primitiveEntry.uv);
|
|
7287
|
+
gl.enableVertexAttribArray(program.attributes.uv);
|
|
7288
|
+
gl.vertexAttribPointer(program.attributes.uv, 2, gl.FLOAT, false, 0, 0);
|
|
7289
|
+
}
|
|
7290
|
+
else {
|
|
7291
|
+
gl.disableVertexAttribArray(program.attributes.uv);
|
|
7292
|
+
gl.vertexAttrib2f(program.attributes.uv, 0, 0);
|
|
7293
|
+
}
|
|
7294
|
+
if (primitiveEntry.texture) {
|
|
7295
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
7296
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.texture);
|
|
7297
|
+
gl.uniform1i(program.uniforms.useTexture, 1);
|
|
7298
|
+
}
|
|
7299
|
+
else {
|
|
7300
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
7301
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7302
|
+
gl.uniform1i(program.uniforms.useTexture, 0);
|
|
7303
|
+
}
|
|
7304
|
+
if (primitiveEntry.metallicRoughnessTexture) {
|
|
7305
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
7306
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.metallicRoughnessTexture);
|
|
7307
|
+
gl.uniform1i(program.uniforms.useMetallicRoughnessTexture, 1);
|
|
7308
|
+
}
|
|
7309
|
+
else {
|
|
7310
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
7311
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7312
|
+
gl.uniform1i(program.uniforms.useMetallicRoughnessTexture, 0);
|
|
7313
|
+
}
|
|
7314
|
+
if (primitiveEntry.occlusionTexture) {
|
|
7315
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
7316
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.occlusionTexture);
|
|
7317
|
+
gl.uniform1i(program.uniforms.useOcclusionTexture, 1);
|
|
7318
|
+
}
|
|
7319
|
+
else {
|
|
7320
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
7321
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7322
|
+
gl.uniform1i(program.uniforms.useOcclusionTexture, 0);
|
|
7323
|
+
}
|
|
7324
|
+
if (primitiveEntry.emissiveTexture) {
|
|
7325
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
7326
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.emissiveTexture);
|
|
7327
|
+
gl.uniform1i(program.uniforms.useEmissiveTexture, 1);
|
|
7328
|
+
}
|
|
7329
|
+
else {
|
|
7330
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
7331
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7332
|
+
gl.uniform1i(program.uniforms.useEmissiveTexture, 0);
|
|
7333
|
+
}
|
|
5994
7334
|
if (primitiveEntry.index && primitiveEntry.indexType) {
|
|
5995
7335
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, primitiveEntry.index);
|
|
5996
7336
|
gl.drawElements(primitiveEntry.mode, primitiveEntry.count, primitiveEntry.indexType, 0);
|
|
@@ -6005,12 +7345,25 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
6005
7345
|
},
|
|
6006
7346
|
dispose() {
|
|
6007
7347
|
for (const modelEntry of models) {
|
|
7348
|
+
const textures = new Set();
|
|
6008
7349
|
for (const primitiveEntry of modelEntry.primitives) {
|
|
6009
7350
|
gl.deleteBuffer(primitiveEntry.position);
|
|
6010
7351
|
gl.deleteBuffer(primitiveEntry.normal);
|
|
7352
|
+
if (primitiveEntry.uv)
|
|
7353
|
+
gl.deleteBuffer(primitiveEntry.uv);
|
|
6011
7354
|
if (primitiveEntry.index)
|
|
6012
7355
|
gl.deleteBuffer(primitiveEntry.index);
|
|
7356
|
+
if (primitiveEntry.texture)
|
|
7357
|
+
textures.add(primitiveEntry.texture);
|
|
7358
|
+
if (primitiveEntry.metallicRoughnessTexture)
|
|
7359
|
+
textures.add(primitiveEntry.metallicRoughnessTexture);
|
|
7360
|
+
if (primitiveEntry.occlusionTexture)
|
|
7361
|
+
textures.add(primitiveEntry.occlusionTexture);
|
|
7362
|
+
if (primitiveEntry.emissiveTexture)
|
|
7363
|
+
textures.add(primitiveEntry.emissiveTexture);
|
|
6013
7364
|
}
|
|
7365
|
+
for (const texture of textures)
|
|
7366
|
+
gl.deleteTexture(texture);
|
|
6014
7367
|
}
|
|
6015
7368
|
gl.deleteProgram(program.program);
|
|
6016
7369
|
backdrop.dispose();
|
|
@@ -6104,27 +7457,156 @@ function createBackdropPalette(snapshot) {
|
|
|
6104
7457
|
};
|
|
6105
7458
|
}
|
|
6106
7459
|
function createWebGLModel(gl, node, modelData) {
|
|
7460
|
+
const textures = new Map();
|
|
7461
|
+
const textureFor = (index) => {
|
|
7462
|
+
if (index === undefined)
|
|
7463
|
+
return undefined;
|
|
7464
|
+
const existing = textures.get(index);
|
|
7465
|
+
if (existing)
|
|
7466
|
+
return existing;
|
|
7467
|
+
const source = modelData.textures[index];
|
|
7468
|
+
if (!source)
|
|
7469
|
+
return undefined;
|
|
7470
|
+
const texture = createTexture2D(gl, source.image);
|
|
7471
|
+
textures.set(index, texture);
|
|
7472
|
+
return texture;
|
|
7473
|
+
};
|
|
6107
7474
|
return {
|
|
6108
7475
|
node,
|
|
6109
7476
|
bounds: modelData.bounds,
|
|
6110
7477
|
color: colorToRgb(node.material?.color ?? "#8fb4ff"),
|
|
6111
|
-
normalizeToUnit:
|
|
7478
|
+
normalizeToUnit: shouldNormalizeModelNode(node),
|
|
6112
7479
|
primitives: modelData.primitives.map((primitiveEntry) => {
|
|
6113
7480
|
const position = createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.positions);
|
|
6114
7481
|
const normal = createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.normals);
|
|
7482
|
+
const uv = primitiveEntry.uvs ? createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.uvs) : undefined;
|
|
6115
7483
|
const index = primitiveEntry.indices ? createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, primitiveEntry.indices) : undefined;
|
|
7484
|
+
const texture = textureFor(primitiveEntry.textureIndex);
|
|
7485
|
+
const metallicRoughnessTexture = textureFor(primitiveEntry.metallicRoughnessTextureIndex);
|
|
7486
|
+
const occlusionTexture = textureFor(primitiveEntry.occlusionTextureIndex);
|
|
7487
|
+
const emissiveTexture = textureFor(primitiveEntry.emissiveTextureIndex);
|
|
7488
|
+
const primitiveModelMatrix = createGltfPrimitiveModelMatrixResolver(modelData, node.animation, primitiveEntry);
|
|
6116
7489
|
return {
|
|
6117
7490
|
position,
|
|
6118
7491
|
normal,
|
|
7492
|
+
...(uv ? { uv } : {}),
|
|
6119
7493
|
...(index ? { index } : {}),
|
|
6120
7494
|
count: primitiveEntry.indices?.length ?? primitiveEntry.positions.length / 3,
|
|
6121
7495
|
mode: webglDrawMode(gl, primitiveEntry.mode),
|
|
6122
7496
|
color: node.material?.color ? colorToRgb(node.material.color) : primitiveEntry.color,
|
|
7497
|
+
...(texture ? { texture } : {}),
|
|
7498
|
+
...(metallicRoughnessTexture ? { metallicRoughnessTexture } : {}),
|
|
7499
|
+
...(occlusionTexture ? { occlusionTexture } : {}),
|
|
7500
|
+
...(emissiveTexture ? { emissiveTexture } : {}),
|
|
7501
|
+
...(primitiveEntry.metallic !== undefined ? { metallic: primitiveEntry.metallic } : {}),
|
|
7502
|
+
...(primitiveEntry.roughness !== undefined ? { roughness: primitiveEntry.roughness } : {}),
|
|
7503
|
+
...(primitiveEntry.emissive ? { emissive: primitiveEntry.emissive } : {}),
|
|
7504
|
+
...(primitiveModelMatrix ? { modelMatrix: primitiveModelMatrix } : {}),
|
|
6123
7505
|
...(primitiveEntry.indices ? { indexType: primitiveEntry.indices instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT } : {})
|
|
6124
7506
|
};
|
|
6125
7507
|
})
|
|
6126
7508
|
};
|
|
6127
7509
|
}
|
|
7510
|
+
function createGltfPrimitiveModelMatrixResolver(modelData, animation, primitiveEntry) {
|
|
7511
|
+
const staticMatrix = primitiveEntry.staticWorldMatrix;
|
|
7512
|
+
const nodeIndex = primitiveEntry.nodeIndex;
|
|
7513
|
+
if (nodeIndex === undefined)
|
|
7514
|
+
return staticMatrix ? () => staticMatrix : undefined;
|
|
7515
|
+
const clip = animation?.clip ? findGltfAnimationClip(modelData, animation.clip) : undefined;
|
|
7516
|
+
if (!clip)
|
|
7517
|
+
return staticMatrix ? () => staticMatrix : undefined;
|
|
7518
|
+
return (time) => resolveGltfAnimatedNodeMatrix(modelData, clip, animation, nodeIndex, time) ?? staticMatrix ?? identity4();
|
|
7519
|
+
}
|
|
7520
|
+
function findGltfAnimationClip(modelData, requestedClip) {
|
|
7521
|
+
const requested = requestedClip.trim().toLowerCase();
|
|
7522
|
+
if (!requested)
|
|
7523
|
+
return undefined;
|
|
7524
|
+
return modelData.animations.find((clip) => clip.name === requestedClip)
|
|
7525
|
+
?? modelData.animations.find((clip) => clip.name.toLowerCase() === requested);
|
|
7526
|
+
}
|
|
7527
|
+
function resolveGltfAnimatedNodeMatrix(modelData, clip, animation, nodeIndex, time) {
|
|
7528
|
+
if (nodeIndex < 0 || nodeIndex >= modelData.nodes.length)
|
|
7529
|
+
return undefined;
|
|
7530
|
+
const seconds = resolveGltfAnimationSeconds(animation, clip, time);
|
|
7531
|
+
const poses = modelData.nodes.map((node) => ({
|
|
7532
|
+
translation: [...node.baseTranslation],
|
|
7533
|
+
rotation: [...node.baseRotation],
|
|
7534
|
+
scale: [...node.baseScale],
|
|
7535
|
+
changed: false
|
|
7536
|
+
}));
|
|
7537
|
+
for (const channel of clip.channels) {
|
|
7538
|
+
const pose = poses[channel.nodeIndex];
|
|
7539
|
+
if (!pose)
|
|
7540
|
+
continue;
|
|
7541
|
+
pose.changed = true;
|
|
7542
|
+
if (channel.path === "rotation")
|
|
7543
|
+
pose.rotation = sampleGltfQuaternionChannel(channel, seconds);
|
|
7544
|
+
else if (channel.path === "scale")
|
|
7545
|
+
pose.scale = sampleGltfVec3Channel(channel, seconds);
|
|
7546
|
+
else
|
|
7547
|
+
pose.translation = sampleGltfVec3Channel(channel, seconds);
|
|
7548
|
+
}
|
|
7549
|
+
const localMatrices = modelData.nodes.map((node, index) => {
|
|
7550
|
+
const pose = poses[index];
|
|
7551
|
+
return pose?.changed ? gltfTrsMatrix(pose.translation, pose.rotation, pose.scale) : node.baseMatrix;
|
|
7552
|
+
});
|
|
7553
|
+
const worldMatrices = new Array(modelData.nodes.length);
|
|
7554
|
+
const visited = new Set();
|
|
7555
|
+
const visit = (currentIndex, parentMatrix) => {
|
|
7556
|
+
if (visited.has(currentIndex))
|
|
7557
|
+
return;
|
|
7558
|
+
const currentNode = modelData.nodes[currentIndex];
|
|
7559
|
+
const localMatrix = localMatrices[currentIndex];
|
|
7560
|
+
if (!currentNode || !localMatrix)
|
|
7561
|
+
return;
|
|
7562
|
+
visited.add(currentIndex);
|
|
7563
|
+
const worldMatrix = multiply4(parentMatrix, localMatrix);
|
|
7564
|
+
worldMatrices[currentIndex] = worldMatrix;
|
|
7565
|
+
for (const childIndex of currentNode.children)
|
|
7566
|
+
visit(childIndex, worldMatrix);
|
|
7567
|
+
};
|
|
7568
|
+
const roots = modelData.sceneRoots.length > 0 ? modelData.sceneRoots : modelData.nodes.map((_, index) => index);
|
|
7569
|
+
for (const rootIndex of roots)
|
|
7570
|
+
visit(rootIndex, identity4());
|
|
7571
|
+
for (let index = 0; index < modelData.nodes.length; index += 1) {
|
|
7572
|
+
if (!visited.has(index))
|
|
7573
|
+
visit(index, identity4());
|
|
7574
|
+
}
|
|
7575
|
+
return worldMatrices[nodeIndex];
|
|
7576
|
+
}
|
|
7577
|
+
function resolveGltfAnimationSeconds(animation, clip, time) {
|
|
7578
|
+
const speed = Math.max(0.05, animation?.speed ?? 1);
|
|
7579
|
+
const startTime = animation?.startTime ?? 0;
|
|
7580
|
+
const phase = Math.max(0, animation?.captureTime ?? 0);
|
|
7581
|
+
const rawSeconds = Math.max(0, time / 1000 - startTime) * speed;
|
|
7582
|
+
const seconds = phase + rawSeconds;
|
|
7583
|
+
const duration = Math.max(0, animation?.duration ?? clip.duration);
|
|
7584
|
+
if (duration <= 0)
|
|
7585
|
+
return seconds;
|
|
7586
|
+
return animation?.loop === false ? Math.min(seconds, duration) : seconds % duration;
|
|
7587
|
+
}
|
|
7588
|
+
function createTexture2D(gl, image) {
|
|
7589
|
+
const texture = gl.createTexture();
|
|
7590
|
+
if (!texture)
|
|
7591
|
+
throw new AuraRuntimeError("backend-fallback", "Aura3D WebGL2 texture allocation failed. Suggested fix: reduce texture count or reload the page.");
|
|
7592
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
7593
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
7594
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
7595
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
7596
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
7597
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
7598
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
|
7599
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
|
7600
|
+
const anisotropic = gl.getExtension("EXT_texture_filter_anisotropic")
|
|
7601
|
+
?? gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic")
|
|
7602
|
+
?? gl.getExtension("MOZ_EXT_texture_filter_anisotropic");
|
|
7603
|
+
if (anisotropic) {
|
|
7604
|
+
const maxAnisotropy = Number(gl.getParameter(anisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT)) || 1;
|
|
7605
|
+
gl.texParameterf(gl.TEXTURE_2D, anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(8, maxAnisotropy));
|
|
7606
|
+
}
|
|
7607
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
7608
|
+
return texture;
|
|
7609
|
+
}
|
|
6128
7610
|
function createWebGLPrimitiveModel(gl, node) {
|
|
6129
7611
|
const primitive = node.primitive === "sphere"
|
|
6130
7612
|
? createSphereGeometry()
|
|
@@ -6148,7 +7630,10 @@ function createWebGLPrimitiveModel(gl, node) {
|
|
|
6148
7630
|
index: createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, primitive.indices),
|
|
6149
7631
|
count: primitive.indices.length,
|
|
6150
7632
|
mode: gl.TRIANGLES,
|
|
6151
|
-
indexType: gl.UNSIGNED_SHORT
|
|
7633
|
+
indexType: gl.UNSIGNED_SHORT,
|
|
7634
|
+
metallic: clamp01(node.material?.metallic ?? node.material?.metalness ?? 0),
|
|
7635
|
+
roughness: clamp01(node.material?.roughness ?? 0.72),
|
|
7636
|
+
emissive: node.material?.emissive ? colorToRgb(node.material.emissive) : [0, 0, 0]
|
|
6152
7637
|
}]
|
|
6153
7638
|
};
|
|
6154
7639
|
}
|
|
@@ -6200,7 +7685,8 @@ function createWebGLParticleModel(gl, effect) {
|
|
|
6200
7685
|
? 90
|
|
6201
7686
|
: 240
|
|
6202
7687
|
: 120;
|
|
6203
|
-
|
|
7688
|
+
// Fountain cap matches prefabs.particleFountain's 2400 maximum so rendered counts never undercut the scene JSON.
|
|
7689
|
+
const maxCount = isFountain ? 2400 : 1800;
|
|
6204
7690
|
const count = Math.max(minCount, Math.min(maxCount, requestedCount));
|
|
6205
7691
|
const radius = effect.radius ?? 1.15;
|
|
6206
7692
|
const height = effect.height ?? 2.4;
|
|
@@ -6236,53 +7722,66 @@ function createWebGLParticleModel(gl, effect) {
|
|
|
6236
7722
|
? ["#fff7ad", "#fef08a", "#fb923c", "#60a5fa", "#38bdf8", "#fb7185"]
|
|
6237
7723
|
: [effect.color ?? "#7dfcff", "#ffd166", "#60a5fa"]);
|
|
6238
7724
|
const sizeCurve = effect.sizeOverLife ?? [0.35, 1, 0.58];
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
? 0.
|
|
6250
|
-
:
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
:
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
const
|
|
6260
|
-
const
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
7725
|
+
const writeParticleVertices = (seconds) => {
|
|
7726
|
+
const emitter = effect.emitter ?? "swirl";
|
|
7727
|
+
for (let index = 0; index < count; index += 1) {
|
|
7728
|
+
writeParticlePosition(center, 0, seconds, emitter, radius, height, index, turbulence, gravity, groundCollision, fountainLayer);
|
|
7729
|
+
const life = getParticleLife(index, seconds, emitter);
|
|
7730
|
+
const sizeLife = life < 0.5
|
|
7731
|
+
? (sizeCurve[0] ?? 0.35) + ((sizeCurve[1] ?? 1) - (sizeCurve[0] ?? 0.35)) * (life / 0.5)
|
|
7732
|
+
: (sizeCurve[1] ?? 1) + ((sizeCurve[2] ?? 0.58) - (sizeCurve[1] ?? 1)) * ((life - 0.5) / 0.5);
|
|
7733
|
+
const baseSize = isFountain
|
|
7734
|
+
? fountainLayer === "mist"
|
|
7735
|
+
? 0.012
|
|
7736
|
+
: fountainLayer === "splash"
|
|
7737
|
+
? 0.024
|
|
7738
|
+
: 0.032
|
|
7739
|
+
: materialMode === "dust" || materialMode === "smoke"
|
|
7740
|
+
? 0.011
|
|
7741
|
+
: materialMode === "star" || materialMode === "spark"
|
|
7742
|
+
? 0.016
|
|
7743
|
+
: 0.022;
|
|
7744
|
+
const size = baseSize * Math.max(0.38, sizeLife) * seededRange(index, 353, 0.72, 1.14);
|
|
7745
|
+
const particleColor = colorToRgb(lifetimeRamp[index % lifetimeRamp.length] ?? effect.color ?? "#7dfcff");
|
|
7746
|
+
const vertexBase = index * 6;
|
|
7747
|
+
const positionBase = vertexBase * 3;
|
|
7748
|
+
for (let vertex = 0; vertex < localVertices.length; vertex += 1) {
|
|
7749
|
+
const local = localVertices[vertex];
|
|
7750
|
+
const offset = positionBase + vertex * 3;
|
|
7751
|
+
positions[offset] = center[0] + local[0] * size;
|
|
7752
|
+
positions[offset + 1] = center[1] + local[1] * size;
|
|
7753
|
+
positions[offset + 2] = center[2] + local[2] * size;
|
|
7754
|
+
normals[offset] = 0.45;
|
|
7755
|
+
normals[offset + 1] = 0.82;
|
|
7756
|
+
normals[offset + 2] = 0.36;
|
|
7757
|
+
colors[offset] = particleColor[0];
|
|
7758
|
+
colors[offset + 1] = particleColor[1];
|
|
7759
|
+
colors[offset + 2] = particleColor[2];
|
|
7760
|
+
}
|
|
7761
|
+
const indexBase = index * 24;
|
|
7762
|
+
for (let tri = 0; tri < localTriangles.length; tri += 1) {
|
|
7763
|
+
const local = localTriangles[tri];
|
|
7764
|
+
indices[indexBase + tri * 3] = vertexBase + local[0];
|
|
7765
|
+
indices[indexBase + tri * 3 + 1] = vertexBase + local[1];
|
|
7766
|
+
indices[indexBase + tri * 3 + 2] = vertexBase + local[2];
|
|
7767
|
+
}
|
|
6277
7768
|
}
|
|
6278
|
-
}
|
|
7769
|
+
};
|
|
7770
|
+
writeParticleVertices(0);
|
|
7771
|
+
const position = createBuffer(gl, gl.ARRAY_BUFFER, positions);
|
|
6279
7772
|
return {
|
|
7773
|
+
node: effect,
|
|
6280
7774
|
bounds: { min: [-radius * 2, 0, -radius * 2], max: [radius * 2, height, radius * 2] },
|
|
6281
7775
|
color: [1, 1, 1],
|
|
6282
7776
|
normalizeToUnit: false,
|
|
6283
|
-
|
|
7777
|
+
update(time) {
|
|
7778
|
+
const seconds = time * Math.max(0.05, effect.speed ?? 1);
|
|
7779
|
+
writeParticleVertices(seconds);
|
|
7780
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, position);
|
|
7781
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, positions);
|
|
7782
|
+
},
|
|
6284
7783
|
primitives: [{
|
|
6285
|
-
position
|
|
7784
|
+
position,
|
|
6286
7785
|
normal: createBuffer(gl, gl.ARRAY_BUFFER, normals),
|
|
6287
7786
|
vertexColor: createBuffer(gl, gl.ARRAY_BUFFER, colors),
|
|
6288
7787
|
index: createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices),
|
|
@@ -6306,16 +7805,19 @@ precision highp float;
|
|
|
6306
7805
|
in vec3 a_position;
|
|
6307
7806
|
in vec3 a_normal;
|
|
6308
7807
|
in vec3 a_color;
|
|
7808
|
+
in vec2 a_uv;
|
|
6309
7809
|
uniform mat4 u_model;
|
|
6310
7810
|
uniform mat4 u_viewProjection;
|
|
6311
7811
|
out vec3 v_normal;
|
|
6312
7812
|
out vec3 v_world;
|
|
6313
7813
|
out vec3 v_color;
|
|
7814
|
+
out vec2 v_uv;
|
|
6314
7815
|
void main() {
|
|
6315
7816
|
vec4 world = u_model * vec4(a_position, 1.0);
|
|
6316
7817
|
v_world = world.xyz;
|
|
6317
7818
|
v_normal = normalize(mat3(u_model) * a_normal);
|
|
6318
7819
|
v_color = a_color;
|
|
7820
|
+
v_uv = a_uv;
|
|
6319
7821
|
gl_Position = u_viewProjection * world;
|
|
6320
7822
|
}`);
|
|
6321
7823
|
const fragment = compileShader(gl, gl.FRAGMENT_SHADER, `#version 300 es
|
|
@@ -6323,16 +7825,48 @@ precision highp float;
|
|
|
6323
7825
|
in vec3 v_normal;
|
|
6324
7826
|
in vec3 v_world;
|
|
6325
7827
|
in vec3 v_color;
|
|
7828
|
+
in vec2 v_uv;
|
|
6326
7829
|
uniform vec3 u_color;
|
|
6327
7830
|
uniform vec3 u_lightDirection;
|
|
7831
|
+
uniform sampler2D u_baseColorTexture;
|
|
7832
|
+
uniform int u_useTexture;
|
|
7833
|
+
uniform sampler2D u_metallicRoughnessTexture;
|
|
7834
|
+
uniform int u_useMetallicRoughnessTexture;
|
|
7835
|
+
uniform sampler2D u_occlusionTexture;
|
|
7836
|
+
uniform int u_useOcclusionTexture;
|
|
7837
|
+
uniform sampler2D u_emissiveTexture;
|
|
7838
|
+
uniform int u_useEmissiveTexture;
|
|
7839
|
+
uniform float u_metallic;
|
|
7840
|
+
uniform float u_roughness;
|
|
7841
|
+
uniform vec3 u_emissive;
|
|
6328
7842
|
out vec4 outColor;
|
|
6329
7843
|
void main() {
|
|
6330
7844
|
vec3 normal = normalize(v_normal);
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
vec3
|
|
6334
|
-
|
|
6335
|
-
|
|
7845
|
+
vec3 lightDirection = normalize(u_lightDirection);
|
|
7846
|
+
vec3 viewDirection = normalize(vec3(0.0, 0.34, 1.0));
|
|
7847
|
+
vec3 halfVector = normalize(lightDirection + viewDirection);
|
|
7848
|
+
float key = max(dot(normal, lightDirection), 0.0);
|
|
7849
|
+
float wrap = max(dot(normal, normalize(vec3(-0.36, 0.52, -0.28))), 0.0);
|
|
7850
|
+
float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 4.0);
|
|
7851
|
+
float rim = pow(1.0 - max(dot(normal, viewDirection), 0.0), 2.0);
|
|
7852
|
+
vec4 texel = u_useTexture == 1 ? texture(u_baseColorTexture, v_uv) : vec4(1.0);
|
|
7853
|
+
vec4 mrTexel = u_useMetallicRoughnessTexture == 1 ? texture(u_metallicRoughnessTexture, v_uv) : vec4(1.0);
|
|
7854
|
+
vec3 emissiveTexel = u_useEmissiveTexture == 1 ? texture(u_emissiveTexture, v_uv).rgb : vec3(1.0);
|
|
7855
|
+
float ao = u_useOcclusionTexture == 1 ? mix(0.45, 1.0, texture(u_occlusionTexture, v_uv).r) : 1.0;
|
|
7856
|
+
float roughness = clamp(u_roughness * mrTexel.g, 0.045, 1.0);
|
|
7857
|
+
float metallic = clamp(u_metallic * mrTexel.b, 0.0, 1.0);
|
|
7858
|
+
vec3 base = u_color * v_color * texel.rgb;
|
|
7859
|
+
vec3 dielectricSpecular = vec3(0.045);
|
|
7860
|
+
vec3 specularColor = mix(dielectricSpecular, base, metallic);
|
|
7861
|
+
float specularPower = mix(96.0, 10.0, roughness);
|
|
7862
|
+
float specular = pow(max(dot(normal, halfVector), 0.0), specularPower) * mix(0.95, 0.18, roughness);
|
|
7863
|
+
vec3 diffuse = base * (1.0 - metallic) * (0.22 + key * 0.78 + wrap * 0.12);
|
|
7864
|
+
vec3 environment = mix(vec3(0.10, 0.15, 0.18), vec3(0.74, 0.86, 1.0), normal.y * 0.5 + 0.5);
|
|
7865
|
+
vec3 reflection = environment * specularColor * (fresnel * 0.62 + (1.0 - roughness) * 0.22);
|
|
7866
|
+
vec3 emissive = u_emissive * emissiveTexel;
|
|
7867
|
+
vec3 color = (diffuse + specularColor * specular + reflection) * ao + emissive + vec3(0.35, 0.65, 1.0) * rim * 0.075;
|
|
7868
|
+
color = color / (color + vec3(1.0));
|
|
7869
|
+
outColor = vec4(pow(color, vec3(1.0 / 2.2)), texel.a);
|
|
6336
7870
|
}`);
|
|
6337
7871
|
const program = gl.createProgram();
|
|
6338
7872
|
if (!program)
|
|
@@ -6349,14 +7883,26 @@ void main() {
|
|
|
6349
7883
|
model: requiredUniform(gl, program, "u_model"),
|
|
6350
7884
|
viewProjection: requiredUniform(gl, program, "u_viewProjection"),
|
|
6351
7885
|
color: requiredUniform(gl, program, "u_color"),
|
|
6352
|
-
lightDirection: requiredUniform(gl, program, "u_lightDirection")
|
|
7886
|
+
lightDirection: requiredUniform(gl, program, "u_lightDirection"),
|
|
7887
|
+
baseColorTexture: requiredUniform(gl, program, "u_baseColorTexture"),
|
|
7888
|
+
useTexture: requiredUniform(gl, program, "u_useTexture"),
|
|
7889
|
+
metallicRoughnessTexture: requiredUniform(gl, program, "u_metallicRoughnessTexture"),
|
|
7890
|
+
useMetallicRoughnessTexture: requiredUniform(gl, program, "u_useMetallicRoughnessTexture"),
|
|
7891
|
+
occlusionTexture: requiredUniform(gl, program, "u_occlusionTexture"),
|
|
7892
|
+
useOcclusionTexture: requiredUniform(gl, program, "u_useOcclusionTexture"),
|
|
7893
|
+
emissiveTexture: requiredUniform(gl, program, "u_emissiveTexture"),
|
|
7894
|
+
useEmissiveTexture: requiredUniform(gl, program, "u_useEmissiveTexture"),
|
|
7895
|
+
metallic: requiredUniform(gl, program, "u_metallic"),
|
|
7896
|
+
roughness: requiredUniform(gl, program, "u_roughness"),
|
|
7897
|
+
emissive: requiredUniform(gl, program, "u_emissive")
|
|
6353
7898
|
};
|
|
6354
7899
|
return {
|
|
6355
7900
|
program,
|
|
6356
7901
|
attributes: {
|
|
6357
7902
|
position: gl.getAttribLocation(program, "a_position"),
|
|
6358
7903
|
normal: gl.getAttribLocation(program, "a_normal"),
|
|
6359
|
-
color: gl.getAttribLocation(program, "a_color")
|
|
7904
|
+
color: gl.getAttribLocation(program, "a_color"),
|
|
7905
|
+
uv: gl.getAttribLocation(program, "a_uv")
|
|
6360
7906
|
},
|
|
6361
7907
|
uniforms
|
|
6362
7908
|
};
|
|
@@ -6398,7 +7944,7 @@ async function loadGltfForWebGL(url) {
|
|
|
6398
7944
|
const bytes = await response.arrayBuffer();
|
|
6399
7945
|
const loaded = isGlb(bytes) ? parseGlb(bytes) : { json: JSON.parse(new TextDecoder().decode(bytes)), buffers: [] };
|
|
6400
7946
|
const buffers = loaded.buffers.length > 0 ? loaded.buffers : await loadExternalGltfBuffers(loaded.json, absoluteUrl);
|
|
6401
|
-
return createGltfModel(loaded.json, buffers);
|
|
7947
|
+
return await createGltfModel(loaded.json, buffers, absoluteUrl);
|
|
6402
7948
|
}
|
|
6403
7949
|
function isGlb(bytes) {
|
|
6404
7950
|
return new DataView(bytes).getUint32(0, true) === 0x46546c67;
|
|
@@ -6445,10 +7991,14 @@ function dataUriToArrayBuffer(uri) {
|
|
|
6445
7991
|
bytes[index] = binary.charCodeAt(index);
|
|
6446
7992
|
return bytes.buffer;
|
|
6447
7993
|
}
|
|
6448
|
-
function createGltfModel(json, buffers) {
|
|
7994
|
+
async function createGltfModel(json, buffers, modelUrl) {
|
|
6449
7995
|
const primitives = [];
|
|
7996
|
+
const textures = await createGltfTextureSources(json, buffers, modelUrl);
|
|
7997
|
+
const nodes = createGltfRuntimeNodes(json);
|
|
7998
|
+
const animations = createGltfAnimationClips(json, buffers);
|
|
7999
|
+
const sceneRoots = resolveGltfSceneRoots(json);
|
|
6450
8000
|
let bounds;
|
|
6451
|
-
const pushMesh = (meshIndex, matrix) => {
|
|
8001
|
+
const pushMesh = (meshIndex, matrix, nodeIndex) => {
|
|
6452
8002
|
const mesh = json.meshes?.[meshIndex];
|
|
6453
8003
|
if (!mesh)
|
|
6454
8004
|
return;
|
|
@@ -6460,17 +8010,29 @@ function createGltfModel(json, buffers) {
|
|
|
6460
8010
|
const sourceNormals = primitive.attributes?.NORMAL === undefined
|
|
6461
8011
|
? createDefaultNormals(sourcePositions.length / 3)
|
|
6462
8012
|
: readAccessor(json, buffers, primitive.attributes.NORMAL, 3);
|
|
6463
|
-
const
|
|
6464
|
-
|
|
8013
|
+
const sourceUvs = primitive.attributes?.TEXCOORD_0 === undefined
|
|
8014
|
+
? undefined
|
|
8015
|
+
: readAccessor(json, buffers, primitive.attributes.TEXCOORD_0, 2);
|
|
6465
8016
|
const indices = primitive.indices === undefined ? undefined : readIndices(json, buffers, primitive.indices);
|
|
6466
|
-
const
|
|
8017
|
+
const materialInfo = materialRenderInfo(json, primitive.material, sourceUvs);
|
|
8018
|
+
const primitiveBounds = boundsFromPositions(transformPositions(sourcePositions, matrix));
|
|
6467
8019
|
bounds = bounds ? mergeBounds(bounds, primitiveBounds) : primitiveBounds;
|
|
6468
8020
|
primitives.push({
|
|
6469
|
-
positions,
|
|
6470
|
-
normals,
|
|
8021
|
+
positions: sourcePositions,
|
|
8022
|
+
normals: sourceNormals,
|
|
8023
|
+
...(sourceUvs ? { uvs: sourceUvs } : {}),
|
|
6471
8024
|
...(indices ? { indices } : {}),
|
|
6472
8025
|
mode: primitive.mode ?? 4,
|
|
6473
|
-
color:
|
|
8026
|
+
color: materialInfo.color,
|
|
8027
|
+
...(materialInfo.textureIndex !== undefined ? { textureIndex: materialInfo.textureIndex } : {}),
|
|
8028
|
+
...(materialInfo.metallicRoughnessTextureIndex !== undefined ? { metallicRoughnessTextureIndex: materialInfo.metallicRoughnessTextureIndex } : {}),
|
|
8029
|
+
...(materialInfo.occlusionTextureIndex !== undefined ? { occlusionTextureIndex: materialInfo.occlusionTextureIndex } : {}),
|
|
8030
|
+
...(materialInfo.emissiveTextureIndex !== undefined ? { emissiveTextureIndex: materialInfo.emissiveTextureIndex } : {}),
|
|
8031
|
+
metallic: materialInfo.metallic,
|
|
8032
|
+
roughness: materialInfo.roughness,
|
|
8033
|
+
emissive: materialInfo.emissive,
|
|
8034
|
+
...(nodeIndex !== undefined ? { nodeIndex } : {}),
|
|
8035
|
+
staticWorldMatrix: matrix
|
|
6474
8036
|
});
|
|
6475
8037
|
}
|
|
6476
8038
|
};
|
|
@@ -6485,22 +8047,16 @@ function createGltfModel(json, buffers) {
|
|
|
6485
8047
|
const nextStack = new Set(stack);
|
|
6486
8048
|
nextStack.add(nodeIndex);
|
|
6487
8049
|
if (node.mesh !== undefined)
|
|
6488
|
-
pushMesh(node.mesh, worldMatrix);
|
|
8050
|
+
pushMesh(node.mesh, worldMatrix, nodeIndex);
|
|
6489
8051
|
for (const childIndex of node.children ?? [])
|
|
6490
8052
|
visitNode(childIndex, worldMatrix, nextStack);
|
|
6491
8053
|
};
|
|
6492
|
-
|
|
6493
|
-
if (json.nodes?.length && sceneRoots?.length) {
|
|
8054
|
+
if (json.nodes?.length && sceneRoots.length) {
|
|
6494
8055
|
for (const nodeIndex of sceneRoots)
|
|
6495
8056
|
visitNode(nodeIndex, identity4(), new Set());
|
|
6496
8057
|
}
|
|
6497
8058
|
else if (json.nodes?.length) {
|
|
6498
|
-
const
|
|
6499
|
-
for (const node of json.nodes)
|
|
6500
|
-
for (const childIndex of node.children ?? [])
|
|
6501
|
-
childNodes.add(childIndex);
|
|
6502
|
-
const roots = json.nodes.map((_, nodeIndex) => nodeIndex).filter((nodeIndex) => !childNodes.has(nodeIndex));
|
|
6503
|
-
for (const nodeIndex of roots.length > 0 ? roots : json.nodes.map((_, nodeIndex) => nodeIndex))
|
|
8059
|
+
for (const nodeIndex of json.nodes.map((_, nodeIndex) => nodeIndex))
|
|
6504
8060
|
visitNode(nodeIndex, identity4(), new Set());
|
|
6505
8061
|
}
|
|
6506
8062
|
else {
|
|
@@ -6510,19 +8066,163 @@ function createGltfModel(json, buffers) {
|
|
|
6510
8066
|
if (primitives.length === 0) {
|
|
6511
8067
|
throw new AuraRuntimeError("failed-glb-load", "Aura3D found no mesh primitives with POSITION data in the model. Suggested fix: export a visible mesh to GLB/glTF.");
|
|
6512
8068
|
}
|
|
6513
|
-
return { primitives, bounds: bounds ?? { min: [-1, -1, -1], max: [1, 1, 1] } };
|
|
8069
|
+
return { primitives, textures, nodes, animations, sceneRoots, bounds: bounds ?? { min: [-1, -1, -1], max: [1, 1, 1] } };
|
|
6514
8070
|
}
|
|
6515
|
-
function
|
|
6516
|
-
|
|
8071
|
+
function resolveGltfSceneRoots(json) {
|
|
8072
|
+
const nodeCount = json.nodes?.length ?? 0;
|
|
8073
|
+
const sceneRoots = json.scenes?.[json.scene ?? 0]?.nodes?.filter((nodeIndex) => nodeIndex >= 0 && nodeIndex < nodeCount) ?? [];
|
|
8074
|
+
if (sceneRoots.length > 0)
|
|
8075
|
+
return sceneRoots;
|
|
8076
|
+
if (nodeCount === 0)
|
|
8077
|
+
return [];
|
|
8078
|
+
const childNodes = new Set();
|
|
8079
|
+
for (const node of json.nodes ?? []) {
|
|
8080
|
+
for (const childIndex of node.children ?? [])
|
|
8081
|
+
childNodes.add(childIndex);
|
|
8082
|
+
}
|
|
8083
|
+
const roots = (json.nodes ?? []).map((_, nodeIndex) => nodeIndex).filter((nodeIndex) => !childNodes.has(nodeIndex));
|
|
8084
|
+
return roots.length > 0 ? roots : (json.nodes ?? []).map((_, nodeIndex) => nodeIndex);
|
|
8085
|
+
}
|
|
8086
|
+
function createGltfRuntimeNodes(json) {
|
|
8087
|
+
return (json.nodes ?? []).map((node) => {
|
|
8088
|
+
const translate = node.translation ?? [0, 0, 0];
|
|
8089
|
+
const rotate = node.rotation ?? [0, 0, 0, 1];
|
|
8090
|
+
const scale = node.scale ?? [1, 1, 1];
|
|
8091
|
+
return {
|
|
8092
|
+
children: node.children ?? [],
|
|
8093
|
+
baseTranslation: [translate[0] ?? 0, translate[1] ?? 0, translate[2] ?? 0],
|
|
8094
|
+
baseRotation: normalizeQuaternion([rotate[0] ?? 0, rotate[1] ?? 0, rotate[2] ?? 0, rotate[3] ?? 1]),
|
|
8095
|
+
baseScale: [scale[0] ?? 1, scale[1] ?? 1, scale[2] ?? 1],
|
|
8096
|
+
baseMatrix: gltfNodeMatrix(node)
|
|
8097
|
+
};
|
|
8098
|
+
});
|
|
8099
|
+
}
|
|
8100
|
+
function createGltfAnimationClips(json, buffers) {
|
|
8101
|
+
return (json.animations ?? []).map((animation, animationIndex) => {
|
|
8102
|
+
const channels = [];
|
|
8103
|
+
let duration = 0;
|
|
8104
|
+
for (const channel of animation.channels ?? []) {
|
|
8105
|
+
const sampler = animation.samplers?.[channel.sampler];
|
|
8106
|
+
const nodeIndex = channel.target.node;
|
|
8107
|
+
const path = parseGltfAnimationPath(channel.target.path);
|
|
8108
|
+
if (!sampler || nodeIndex === undefined || !path)
|
|
8109
|
+
continue;
|
|
8110
|
+
const times = readAccessor(json, buffers, sampler.input, 1);
|
|
8111
|
+
const values = readAccessor(json, buffers, sampler.output, path === "rotation" ? 4 : 3);
|
|
8112
|
+
if (times.length === 0 || values.length === 0)
|
|
8113
|
+
continue;
|
|
8114
|
+
duration = Math.max(duration, times[times.length - 1] ?? 0);
|
|
8115
|
+
channels.push({
|
|
8116
|
+
nodeIndex,
|
|
8117
|
+
path,
|
|
8118
|
+
times,
|
|
8119
|
+
values,
|
|
8120
|
+
interpolation: sampler.interpolation ?? "LINEAR"
|
|
8121
|
+
});
|
|
8122
|
+
}
|
|
8123
|
+
return {
|
|
8124
|
+
name: animation.name ?? `animation-${animationIndex + 1}`,
|
|
8125
|
+
duration,
|
|
8126
|
+
channels
|
|
8127
|
+
};
|
|
8128
|
+
}).filter((clip) => clip.channels.length > 0);
|
|
8129
|
+
}
|
|
8130
|
+
function parseGltfAnimationPath(path) {
|
|
8131
|
+
return path === "translation" || path === "rotation" || path === "scale" ? path : undefined;
|
|
8132
|
+
}
|
|
8133
|
+
async function createGltfTextureSources(json, buffers, modelUrl) {
|
|
8134
|
+
const images = new Map();
|
|
8135
|
+
const loadImage = (imageIndex) => {
|
|
8136
|
+
const existing = images.get(imageIndex);
|
|
8137
|
+
if (existing)
|
|
8138
|
+
return existing;
|
|
8139
|
+
const promise = loadGltfImage(json, buffers, imageIndex, modelUrl).catch(() => undefined);
|
|
8140
|
+
images.set(imageIndex, promise);
|
|
8141
|
+
return promise;
|
|
8142
|
+
};
|
|
8143
|
+
return await Promise.all((json.textures ?? []).map(async (texture) => {
|
|
8144
|
+
if (texture.source === undefined)
|
|
8145
|
+
return undefined;
|
|
8146
|
+
const image = await loadImage(texture.source);
|
|
8147
|
+
return image ? { image } : undefined;
|
|
8148
|
+
}));
|
|
8149
|
+
}
|
|
8150
|
+
async function loadGltfImage(json, buffers, imageIndex, modelUrl) {
|
|
8151
|
+
const image = json.images?.[imageIndex];
|
|
8152
|
+
if (!image)
|
|
8153
|
+
return undefined;
|
|
8154
|
+
let blob;
|
|
8155
|
+
if (image.bufferView !== undefined) {
|
|
8156
|
+
const view = json.bufferViews?.[image.bufferView];
|
|
8157
|
+
if (!view)
|
|
8158
|
+
return undefined;
|
|
8159
|
+
const buffer = buffers[view.buffer];
|
|
8160
|
+
if (!buffer)
|
|
8161
|
+
return undefined;
|
|
8162
|
+
const bytes = new Uint8Array(buffer, view.byteOffset ?? 0, view.byteLength);
|
|
8163
|
+
blob = new Blob([bytes], { type: image.mimeType ?? "application/octet-stream" });
|
|
8164
|
+
}
|
|
8165
|
+
else if (image.uri?.startsWith("data:")) {
|
|
8166
|
+
const response = await fetch(image.uri);
|
|
8167
|
+
blob = await response.blob();
|
|
8168
|
+
}
|
|
8169
|
+
else if (image.uri) {
|
|
8170
|
+
const response = await fetch(new URL(image.uri, modelUrl).href);
|
|
8171
|
+
if (!response.ok)
|
|
8172
|
+
return undefined;
|
|
8173
|
+
blob = await response.blob();
|
|
8174
|
+
}
|
|
8175
|
+
else {
|
|
6517
8176
|
return undefined;
|
|
8177
|
+
}
|
|
8178
|
+
if (typeof createImageBitmap === "function")
|
|
8179
|
+
return await createImageBitmap(blob);
|
|
8180
|
+
return await blobToHtmlImage(blob);
|
|
8181
|
+
}
|
|
8182
|
+
async function blobToHtmlImage(blob) {
|
|
8183
|
+
const url = URL.createObjectURL(blob);
|
|
8184
|
+
try {
|
|
8185
|
+
const image = new Image();
|
|
8186
|
+
image.decoding = "async";
|
|
8187
|
+
image.src = url;
|
|
8188
|
+
if (typeof image.decode === "function")
|
|
8189
|
+
await image.decode();
|
|
8190
|
+
else
|
|
8191
|
+
await new Promise((resolve, reject) => {
|
|
8192
|
+
image.onload = () => resolve();
|
|
8193
|
+
image.onerror = () => reject(new Error("Aura3D failed to decode glTF image."));
|
|
8194
|
+
});
|
|
8195
|
+
return image;
|
|
8196
|
+
}
|
|
8197
|
+
finally {
|
|
8198
|
+
URL.revokeObjectURL(url);
|
|
8199
|
+
}
|
|
8200
|
+
}
|
|
8201
|
+
function materialRenderInfo(json, materialIndex, uvs) {
|
|
8202
|
+
if (materialIndex === undefined) {
|
|
8203
|
+
return { metallic: 0, roughness: 0.72, emissive: [0, 0, 0] };
|
|
8204
|
+
}
|
|
6518
8205
|
const materialEntry = json.materials?.[materialIndex];
|
|
6519
|
-
const
|
|
6520
|
-
|
|
6521
|
-
return [clamp01(base[0]), clamp01(base[1]), clamp01(base[2])];
|
|
8206
|
+
const pbr = materialEntry?.pbrMetallicRoughness;
|
|
8207
|
+
const base = pbr?.baseColorFactor;
|
|
6522
8208
|
const emissive = materialEntry?.emissiveFactor;
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
8209
|
+
return {
|
|
8210
|
+
...(base && base.length >= 3 ? { color: [clamp01(base[0]), clamp01(base[1]), clamp01(base[2])] } : {}),
|
|
8211
|
+
...textureRef(pbr?.baseColorTexture, uvs, "textureIndex"),
|
|
8212
|
+
...textureRef(pbr?.metallicRoughnessTexture, uvs, "metallicRoughnessTextureIndex"),
|
|
8213
|
+
...textureRef(materialEntry?.occlusionTexture, uvs, "occlusionTextureIndex"),
|
|
8214
|
+
...textureRef(materialEntry?.emissiveTexture, uvs, "emissiveTextureIndex"),
|
|
8215
|
+
metallic: clamp01(pbr?.metallicFactor ?? 0),
|
|
8216
|
+
roughness: clamp01(pbr?.roughnessFactor ?? 0.72),
|
|
8217
|
+
emissive: emissive && emissive.length >= 3
|
|
8218
|
+
? [clamp01(emissive[0]), clamp01(emissive[1]), clamp01(emissive[2])]
|
|
8219
|
+
: [0, 0, 0]
|
|
8220
|
+
};
|
|
8221
|
+
}
|
|
8222
|
+
function textureRef(texture, uvs, key) {
|
|
8223
|
+
if (!texture || !uvs || texture.texCoord !== undefined && texture.texCoord !== 0)
|
|
8224
|
+
return {};
|
|
8225
|
+
return { [key]: texture.index };
|
|
6526
8226
|
}
|
|
6527
8227
|
function createPlaneGeometry() {
|
|
6528
8228
|
return {
|
|
@@ -6767,6 +8467,106 @@ function gltfNodeMatrix(node) {
|
|
|
6767
8467
|
const scale = node.scale ?? [1, 1, 1];
|
|
6768
8468
|
return multiply4(translation(translate[0] ?? 0, translate[1] ?? 0, translate[2] ?? 0), multiply4(rotationQuaternion(rotate), scaling(scale[0] ?? 1, scale[1] ?? 1, scale[2] ?? 1)));
|
|
6769
8469
|
}
|
|
8470
|
+
function gltfTrsMatrix(translate, rotate, scale) {
|
|
8471
|
+
return multiply4(translation(translate[0], translate[1], translate[2]), multiply4(rotationQuaternion(rotate), scaling(scale[0], scale[1], scale[2])));
|
|
8472
|
+
}
|
|
8473
|
+
function sampleGltfVec3Channel(channel, seconds) {
|
|
8474
|
+
const [left, right, t] = gltfKeyframePair(channel.times, seconds);
|
|
8475
|
+
const a = readGltfChannelValue(channel, left, 3);
|
|
8476
|
+
if (left === right || channel.interpolation === "STEP")
|
|
8477
|
+
return [a[0], a[1], a[2]];
|
|
8478
|
+
const b = readGltfChannelValue(channel, right, 3);
|
|
8479
|
+
return [
|
|
8480
|
+
a[0] + (b[0] - a[0]) * t,
|
|
8481
|
+
a[1] + (b[1] - a[1]) * t,
|
|
8482
|
+
a[2] + (b[2] - a[2]) * t
|
|
8483
|
+
];
|
|
8484
|
+
}
|
|
8485
|
+
function sampleGltfQuaternionChannel(channel, seconds) {
|
|
8486
|
+
const [left, right, t] = gltfKeyframePair(channel.times, seconds);
|
|
8487
|
+
const a = normalizeQuaternion(readGltfChannelValue(channel, left, 4));
|
|
8488
|
+
if (left === right || channel.interpolation === "STEP")
|
|
8489
|
+
return a;
|
|
8490
|
+
const b = normalizeQuaternion(readGltfChannelValue(channel, right, 4));
|
|
8491
|
+
return slerpQuaternion(a, b, t);
|
|
8492
|
+
}
|
|
8493
|
+
function gltfKeyframePair(times, seconds) {
|
|
8494
|
+
if (times.length <= 1)
|
|
8495
|
+
return [0, 0, 0];
|
|
8496
|
+
if (seconds <= (times[0] ?? 0))
|
|
8497
|
+
return [0, 0, 0];
|
|
8498
|
+
const lastIndex = times.length - 1;
|
|
8499
|
+
if (seconds >= (times[lastIndex] ?? seconds))
|
|
8500
|
+
return [lastIndex, lastIndex, 0];
|
|
8501
|
+
for (let index = 0; index < lastIndex; index += 1) {
|
|
8502
|
+
const start = times[index] ?? 0;
|
|
8503
|
+
const end = times[index + 1] ?? start;
|
|
8504
|
+
if (seconds >= start && seconds <= end) {
|
|
8505
|
+
const duration = Math.max(0.000001, end - start);
|
|
8506
|
+
return [index, index + 1, Math.min(1, Math.max(0, (seconds - start) / duration))];
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
return [lastIndex, lastIndex, 0];
|
|
8510
|
+
}
|
|
8511
|
+
function readGltfChannelValue(channel, keyframeIndex, components) {
|
|
8512
|
+
const cubic = channel.interpolation === "CUBICSPLINE";
|
|
8513
|
+
const stride = components * (cubic ? 3 : 1);
|
|
8514
|
+
const offset = keyframeIndex * stride + (cubic ? components : 0);
|
|
8515
|
+
if (components === 4) {
|
|
8516
|
+
return [
|
|
8517
|
+
channel.values[offset] ?? 0,
|
|
8518
|
+
channel.values[offset + 1] ?? 0,
|
|
8519
|
+
channel.values[offset + 2] ?? 0,
|
|
8520
|
+
channel.values[offset + 3] ?? 1
|
|
8521
|
+
];
|
|
8522
|
+
}
|
|
8523
|
+
return [
|
|
8524
|
+
channel.values[offset] ?? 0,
|
|
8525
|
+
channel.values[offset + 1] ?? 0,
|
|
8526
|
+
channel.values[offset + 2] ?? 0
|
|
8527
|
+
];
|
|
8528
|
+
}
|
|
8529
|
+
function normalizeQuaternion(rotation) {
|
|
8530
|
+
const length = Math.hypot(rotation[0] ?? 0, rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 1) || 1;
|
|
8531
|
+
return [
|
|
8532
|
+
(rotation[0] ?? 0) / length,
|
|
8533
|
+
(rotation[1] ?? 0) / length,
|
|
8534
|
+
(rotation[2] ?? 0) / length,
|
|
8535
|
+
(rotation[3] ?? 1) / length
|
|
8536
|
+
];
|
|
8537
|
+
}
|
|
8538
|
+
function slerpQuaternion(a, b, t) {
|
|
8539
|
+
let bx = b[0];
|
|
8540
|
+
let by = b[1];
|
|
8541
|
+
let bz = b[2];
|
|
8542
|
+
let bw = b[3];
|
|
8543
|
+
let dot = a[0] * bx + a[1] * by + a[2] * bz + a[3] * bw;
|
|
8544
|
+
if (dot < 0) {
|
|
8545
|
+
dot = -dot;
|
|
8546
|
+
bx = -bx;
|
|
8547
|
+
by = -by;
|
|
8548
|
+
bz = -bz;
|
|
8549
|
+
bw = -bw;
|
|
8550
|
+
}
|
|
8551
|
+
if (dot > 0.9995) {
|
|
8552
|
+
return normalizeQuaternion([
|
|
8553
|
+
a[0] + (bx - a[0]) * t,
|
|
8554
|
+
a[1] + (by - a[1]) * t,
|
|
8555
|
+
a[2] + (bz - a[2]) * t,
|
|
8556
|
+
a[3] + (bw - a[3]) * t
|
|
8557
|
+
]);
|
|
8558
|
+
}
|
|
8559
|
+
const theta = Math.acos(Math.min(1, Math.max(-1, dot)));
|
|
8560
|
+
const sinTheta = Math.sin(theta) || 1;
|
|
8561
|
+
const wa = Math.sin((1 - t) * theta) / sinTheta;
|
|
8562
|
+
const wb = Math.sin(t * theta) / sinTheta;
|
|
8563
|
+
return normalizeQuaternion([
|
|
8564
|
+
a[0] * wa + bx * wb,
|
|
8565
|
+
a[1] * wa + by * wb,
|
|
8566
|
+
a[2] * wa + bz * wb,
|
|
8567
|
+
a[3] * wa + bw * wb
|
|
8568
|
+
]);
|
|
8569
|
+
}
|
|
6770
8570
|
function rotationQuaternion(rotation) {
|
|
6771
8571
|
const length = Math.hypot(rotation[0] ?? 0, rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 1) || 1;
|
|
6772
8572
|
const x = (rotation[0] ?? 0) / length;
|
|
@@ -6836,10 +8636,10 @@ function mergeBounds(a, b) {
|
|
|
6836
8636
|
max: [Math.max(a.max[0], b.max[0]), Math.max(a.max[1], b.max[1]), Math.max(a.max[2], b.max[2])]
|
|
6837
8637
|
};
|
|
6838
8638
|
}
|
|
6839
|
-
function createViewProjection(snapshot, aspect, time) {
|
|
8639
|
+
function createViewProjection(snapshot, aspect, time, runtimeNodes) {
|
|
6840
8640
|
const cameraSpec = snapshot.camera;
|
|
6841
|
-
const target = resolveCameraTarget(snapshot, cameraSpec);
|
|
6842
|
-
const eye = resolveCameraEye(snapshot, cameraSpec, time);
|
|
8641
|
+
const target = resolveCameraTarget(snapshot, cameraSpec, runtimeNodes);
|
|
8642
|
+
const eye = resolveCameraEye(snapshot, cameraSpec, time, runtimeNodes);
|
|
6843
8643
|
const view = lookAt(eye, target, [0, 1, 0]);
|
|
6844
8644
|
const projection = perspective(((cameraSpec.fov ?? 45) * Math.PI) / 180, aspect, 0.05, 100);
|
|
6845
8645
|
return multiply4(projection, view);
|
|
@@ -6850,7 +8650,7 @@ function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
|
|
|
6850
8650
|
Math.max(0.001, bounds.max[1] - bounds.min[1]),
|
|
6851
8651
|
Math.max(0.001, bounds.max[2] - bounds.min[2])
|
|
6852
8652
|
];
|
|
6853
|
-
const fitScale =
|
|
8653
|
+
const fitScale = resolveModelFitScale(node, extent, normalizeToUnit);
|
|
6854
8654
|
const centerX = (bounds.min[0] + bounds.max[0]) / 2;
|
|
6855
8655
|
const centerZ = (bounds.min[2] + bounds.max[2]) / 2;
|
|
6856
8656
|
const baseSize = node?.kind === "primitive" ? primitiveSize(node) : [1, 1, 1];
|
|
@@ -6859,6 +8659,23 @@ function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
|
|
|
6859
8659
|
const rotation = animatedRotation(node, time);
|
|
6860
8660
|
return multiply4(translation(position[0], position[1], position[2]), multiply4(rotationXYZ(rotation), multiply4(scaling(nodeScale[0] * baseSize[0] * fitScale, nodeScale[1] * baseSize[1] * fitScale, nodeScale[2] * baseSize[2] * fitScale), normalizeToUnit ? translation(-centerX, -bounds.min[1], -centerZ) : identity4())));
|
|
6861
8661
|
}
|
|
8662
|
+
function resolveModelFitScale(node, extent, normalizeToUnit) {
|
|
8663
|
+
if (node?.kind === "model") {
|
|
8664
|
+
if (isPositiveFinite(node.targetHeight))
|
|
8665
|
+
return node.targetHeight / extent[1];
|
|
8666
|
+
if (isPositiveFinite(node.targetLength))
|
|
8667
|
+
return node.targetLength / Math.max(extent[0], extent[2]);
|
|
8668
|
+
if (isPositiveFinite(node.targetMaxDimension))
|
|
8669
|
+
return node.targetMaxDimension / Math.max(extent[0], extent[1], extent[2]);
|
|
8670
|
+
}
|
|
8671
|
+
return normalizeToUnit ? AURA_NORMALIZED_MODEL_MAX_DIMENSION / Math.max(extent[0], extent[1], extent[2]) : 1;
|
|
8672
|
+
}
|
|
8673
|
+
function isPositiveFinite(value) {
|
|
8674
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
8675
|
+
}
|
|
8676
|
+
function shouldNormalizeModelNode(node) {
|
|
8677
|
+
return node?.scaleMode !== "world";
|
|
8678
|
+
}
|
|
6862
8679
|
function animatedPosition(node, time) {
|
|
6863
8680
|
const basePosition = node?.position ?? [0, 0, 0];
|
|
6864
8681
|
if (!node?.animation)
|
|
@@ -6876,6 +8693,8 @@ function animatedRotation(node, time) {
|
|
|
6876
8693
|
const baseRotation = node?.rotation ?? [0, 0, 0];
|
|
6877
8694
|
if (!node?.animation)
|
|
6878
8695
|
return baseRotation;
|
|
8696
|
+
if (node.kind === "model" && !isModelTransformAnimationClip(node.animation.clip))
|
|
8697
|
+
return baseRotation;
|
|
6879
8698
|
const speed = Math.max(0.05, node.animation.speed ?? 1);
|
|
6880
8699
|
const seconds = resolveAnimationSeconds(node.animation, time);
|
|
6881
8700
|
if (node.animation.clip === "turntable") {
|
|
@@ -6891,6 +8710,9 @@ function animatedRotation(node, time) {
|
|
|
6891
8710
|
return baseRotation;
|
|
6892
8711
|
return [baseRotation[0], baseRotation[1] + seconds * speed, baseRotation[2]];
|
|
6893
8712
|
}
|
|
8713
|
+
function isModelTransformAnimationClip(clip) {
|
|
8714
|
+
return clip === "turntable" || clip === "float" || clip === "orbit";
|
|
8715
|
+
}
|
|
6894
8716
|
function primitiveSize(node) {
|
|
6895
8717
|
if (typeof node.size === "number")
|
|
6896
8718
|
return [node.size, node.size, node.size];
|
|
@@ -7173,7 +8995,7 @@ function validateMaterialTexture(materialSpec) {
|
|
|
7173
8995
|
throw new AuraRuntimeError("unsupported-texture", `Aura3D texture asset "${texture.id}" uses unsupported format "${texture.format}". Suggested fix: use png, jpg, jpeg, webp, or ktx2 textures.`);
|
|
7174
8996
|
}
|
|
7175
8997
|
}
|
|
7176
|
-
function createInitialDiagnostics(snapshot) {
|
|
8998
|
+
function createInitialDiagnostics(snapshot, rendererOptions) {
|
|
7177
8999
|
return {
|
|
7178
9000
|
backend: "headless",
|
|
7179
9001
|
fps: 0,
|
|
@@ -7181,7 +9003,7 @@ function createInitialDiagnostics(snapshot) {
|
|
|
7181
9003
|
renderSize: [0, 0],
|
|
7182
9004
|
assets: [],
|
|
7183
9005
|
evidence: collectAuraSceneEvidence(snapshot),
|
|
7184
|
-
renderer: createRendererDiagnosticReport(snapshot),
|
|
9006
|
+
renderer: createRendererDiagnosticReport(snapshot, undefined, rendererOptions),
|
|
7185
9007
|
warnings: snapshot.nodes.length === 0 ? ["Scene contains no renderable nodes. Add model(assets.assetId) or primitives.box()."] : [],
|
|
7186
9008
|
errors: []
|
|
7187
9009
|
};
|
|
@@ -7208,7 +9030,7 @@ function collectGeneratedCodeWarnings(snapshot) {
|
|
|
7208
9030
|
warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
|
|
7209
9031
|
}
|
|
7210
9032
|
for (const node of snapshot.nodes) {
|
|
7211
|
-
if (node.kind === "model" && node.asset.
|
|
9033
|
+
if (node.kind === "model" && createAssetProvenance(node.asset).source === "unsafe-url") {
|
|
7212
9034
|
warnings.push(`Model uses unsafeModelUrl("${node.asset.url}"). Suggested fix: run assets add and use typed assets.`);
|
|
7213
9035
|
}
|
|
7214
9036
|
}
|
|
@@ -7408,7 +9230,8 @@ function drawEffect(context, width, height, node, time) {
|
|
|
7408
9230
|
}
|
|
7409
9231
|
if (node.effect === "particles") {
|
|
7410
9232
|
const isFountain = node.emitter === "fountain";
|
|
7411
|
-
|
|
9233
|
+
// Fountain cap matches prefabs.particleFountain's 2400 maximum so rendered counts never undercut the scene JSON.
|
|
9234
|
+
const count = Math.max(120, Math.min(isFountain ? 2400 : 1600, node.particleCount ?? 900));
|
|
7412
9235
|
const radius = Math.max(0.1, node.radius ?? 1.15);
|
|
7413
9236
|
const height3d = Math.max(0.2, node.height ?? 2.4);
|
|
7414
9237
|
const color = String(node.color ?? "#7dfcff");
|