@aura3d/engine 1.3.3 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -15
- 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 +1 -0
- package/dist/assets/GLTFRenderResources.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 +222 -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 +3 -1
- package/dist/create-aura3d/index.js.map +1 -1
- package/dist/create-aura3d/showcase-spec-artifacts.d.ts +105 -0
- package/dist/create-aura3d/showcase-spec-artifacts.d.ts.map +1 -0
- package/dist/create-aura3d/showcase-spec-artifacts.js +143 -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 +881 -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 +690 -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 +274 -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 +319 -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/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 +182 -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/PublicGameGeometry.d.ts +117 -0
- package/dist/engine/agent-api/PublicGameGeometry.d.ts.map +1 -0
- package/dist/engine/agent-api/PublicGameGeometry.js +118 -0
- package/dist/engine/agent-api/PublicGameGeometry.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 +266 -5
- package/dist/engine/agent-api/index.d.ts.map +1 -1
- package/dist/engine/agent-api/index.js +2445 -113
- 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/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/rendering/ForwardPass.js +7 -1
- 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/package.json +30 -32
- 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,17 @@ 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
|
+
import { certifyPublicPlatformerGeometry, certifyPublicRacingGeometry } from "./PublicGameGeometry.js";
|
|
58
|
+
export { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
|
|
59
|
+
export { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
|
|
60
|
+
export { certifyPublicPlatformerGeometry, certifyPublicRacingGeometry } from "./PublicGameGeometry.js";
|
|
51
61
|
import { createPromptAnimationEpisodePlan, createPromptAnimationStoryBible, definePromptAnimationStoryboard } from "./PromptAnimationContract.js";
|
|
52
62
|
import { applyShotPlaybackFrame, createShotPlaybackPlan, createShotTimeline, installShotPlayback, sampleShotPlaybackPlan } from "./ShotTimeline.js";
|
|
53
63
|
import { captionCueAtTime, createCaptionTimingProof, deriveCaptionTrackFromDialogue } from "./DialoguePerformance.js";
|
|
@@ -157,6 +167,11 @@ export function model(asset, options = {}) {
|
|
|
157
167
|
castShadow: options.castShadow ?? true,
|
|
158
168
|
receiveShadow: options.receiveShadow ?? true,
|
|
159
169
|
visible: options.visible ?? true,
|
|
170
|
+
role: options.role,
|
|
171
|
+
scaleMode: options.scaleMode,
|
|
172
|
+
targetHeight: options.targetHeight,
|
|
173
|
+
targetMaxDimension: options.targetMaxDimension,
|
|
174
|
+
targetLength: options.targetLength,
|
|
160
175
|
physics: options.physics
|
|
161
176
|
});
|
|
162
177
|
}
|
|
@@ -554,8 +569,205 @@ export const material = {
|
|
|
554
569
|
fabric: material.fabric()
|
|
555
570
|
}),
|
|
556
571
|
inspector: (name, spec) => createMaterialInspector(name, spec),
|
|
557
|
-
visualQA: (nodes) => validateMaterialVisualQA(nodes)
|
|
572
|
+
visualQA: (nodes) => validateMaterialVisualQA(nodes),
|
|
573
|
+
capabilityDiagnostics: (input) => createMaterialCapabilityDiagnostics(input)
|
|
558
574
|
};
|
|
575
|
+
const materialCapabilityCatalog = [
|
|
576
|
+
{
|
|
577
|
+
id: "base-color",
|
|
578
|
+
label: "Base color",
|
|
579
|
+
rootSafeApi: "supported",
|
|
580
|
+
productionRuntime: "supported",
|
|
581
|
+
evidence: "AuraMaterialSpec.color and imported GLB baseColorFactor are consumed by the root renderer.",
|
|
582
|
+
claimRule: "Can be claimed with a route screenshot."
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: "base-color-texture",
|
|
586
|
+
label: "Base-color texture",
|
|
587
|
+
rootSafeApi: "partial",
|
|
588
|
+
productionRuntime: "supported",
|
|
589
|
+
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.",
|
|
590
|
+
claimRule: "Can describe typed texture metadata and rendered textured assets; do not claim full texture-material parity without controlled root pixels."
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
id: "metallic-roughness",
|
|
594
|
+
label: "Metallic/roughness",
|
|
595
|
+
rootSafeApi: "partial",
|
|
596
|
+
productionRuntime: "supported",
|
|
597
|
+
evidence: "Root material specs expose values, but production-style material response needs route-specific pixel proof.",
|
|
598
|
+
claimRule: "Do not claim full PBR parity without browser evidence."
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: "normal-map",
|
|
602
|
+
label: "Normal maps",
|
|
603
|
+
rootSafeApi: "partial",
|
|
604
|
+
productionRuntime: "supported",
|
|
605
|
+
evidence: "Root specs carry normal/procedural-normal intent; production shader parity is not guaranteed.",
|
|
606
|
+
claimRule: "Claim only with material-specific screenshot evidence."
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: "emissive",
|
|
610
|
+
label: "Emissive",
|
|
611
|
+
rootSafeApi: "partial",
|
|
612
|
+
productionRuntime: "supported",
|
|
613
|
+
evidence: "Root specs and effects expose emissive color; pixel-backed bloom is reported separately.",
|
|
614
|
+
claimRule: "Distinguish emissive material from postprocess bloom."
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
id: "alpha",
|
|
618
|
+
label: "Alpha/opacity",
|
|
619
|
+
rootSafeApi: "partial",
|
|
620
|
+
productionRuntime: "supported",
|
|
621
|
+
evidence: "Opacity is accepted by the public spec, but sorting and blending must be visually verified.",
|
|
622
|
+
claimRule: "Verify alpha behavior in screenshots before claiming."
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: "double-sided",
|
|
626
|
+
label: "Double-sided materials",
|
|
627
|
+
rootSafeApi: "metadata-only",
|
|
628
|
+
productionRuntime: "supported",
|
|
629
|
+
evidence: "Root diagnostics may preserve metadata, but public material specs do not expose a full double-sided render contract.",
|
|
630
|
+
claimRule: "Claim only when tested route shows backface behavior."
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
id: "clearcoat",
|
|
634
|
+
label: "Clearcoat",
|
|
635
|
+
rootSafeApi: "partial",
|
|
636
|
+
productionRuntime: "supported",
|
|
637
|
+
evidence: "Root specs and inspectors expose clearcoat values; physically accurate layered highlights need pixel proof.",
|
|
638
|
+
claimRule: "Do not call clearcoat physically accurate without renderer evidence."
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
id: "sheen",
|
|
642
|
+
label: "Sheen",
|
|
643
|
+
rootSafeApi: "partial",
|
|
644
|
+
productionRuntime: "supported",
|
|
645
|
+
evidence: "Root specs expose sheen values for inspection and future renderer binding.",
|
|
646
|
+
claimRule: "Treat as partial unless screenshot-proven."
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
id: "transmission",
|
|
650
|
+
label: "Transmission/glass",
|
|
651
|
+
rootSafeApi: "partial",
|
|
652
|
+
productionRuntime: "supported",
|
|
653
|
+
evidence: "Root specs expose glass intent, opacity, IOR, and thickness; real refraction/volume is not proven in the root path.",
|
|
654
|
+
claimRule: "Do not claim real transmission/refraction through root createAuraApp without pixels."
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
id: "variants",
|
|
658
|
+
label: "Material variants",
|
|
659
|
+
rootSafeApi: "metadata-only",
|
|
660
|
+
productionRuntime: "supported",
|
|
661
|
+
evidence: "Variant metadata and route logic can be inspected; renderer-level variant switching needs route tests.",
|
|
662
|
+
claimRule: "Claim selected variant behavior only when tested."
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
id: "hdr-ibl",
|
|
666
|
+
label: "HDR/IBL",
|
|
667
|
+
rootSafeApi: "partial",
|
|
668
|
+
productionRuntime: "supported",
|
|
669
|
+
evidence: "Root environment nodes request lighting intent; environment prefiltering is not proven in the root path.",
|
|
670
|
+
claimRule: "Root HDR/IBL claims require diagnostics and browser pixels."
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
id: "shadow-maps",
|
|
674
|
+
label: "Shadow maps",
|
|
675
|
+
rootSafeApi: "partial",
|
|
676
|
+
productionRuntime: "supported",
|
|
677
|
+
evidence: "Root path reports contact-shadow cues; production shadow-map sampling is not proven by spec alone.",
|
|
678
|
+
claimRule: "Production-shadow claims require pixel proof."
|
|
679
|
+
}
|
|
680
|
+
];
|
|
681
|
+
function createMaterialCapabilityDiagnostics(input) {
|
|
682
|
+
const specs = extractMaterialCapabilitySpecs(input);
|
|
683
|
+
const requested = new Set();
|
|
684
|
+
if (specs.length > 0)
|
|
685
|
+
requested.add("base-color");
|
|
686
|
+
for (const spec of specs) {
|
|
687
|
+
if (spec.texture)
|
|
688
|
+
requested.add("base-color-texture");
|
|
689
|
+
if (spec.metallic !== undefined || spec.metalness !== undefined || spec.roughness !== undefined || spec.roughnessMap || spec.metalnessMap)
|
|
690
|
+
requested.add("metallic-roughness");
|
|
691
|
+
if (spec.normal)
|
|
692
|
+
requested.add("normal-map");
|
|
693
|
+
if (spec.emissive || spec.emissiveIntensity !== undefined)
|
|
694
|
+
requested.add("emissive");
|
|
695
|
+
if (spec.opacity !== undefined && spec.opacity < 1)
|
|
696
|
+
requested.add("alpha");
|
|
697
|
+
if (spec.clearcoat !== undefined || spec.clearcoatRoughness !== undefined)
|
|
698
|
+
requested.add("clearcoat");
|
|
699
|
+
if (spec.sheen !== undefined || spec.sheenColor !== undefined || spec.sheenRoughness !== undefined)
|
|
700
|
+
requested.add("sheen");
|
|
701
|
+
if (spec.transmission !== undefined || spec.thickness !== undefined || spec.ior !== undefined || spec.attenuationColor !== undefined || spec.attenuationDistance !== undefined)
|
|
702
|
+
requested.add("transmission");
|
|
703
|
+
if (spec.envMapIntensity !== undefined)
|
|
704
|
+
requested.add("hdr-ibl");
|
|
705
|
+
}
|
|
706
|
+
const features = materialCapabilityCatalog.map((feature) => ({ ...feature, requested: requested.has(feature.id) }));
|
|
707
|
+
const unsupportedRequestedFeatures = features
|
|
708
|
+
.filter((feature) => feature.requested && (feature.rootSafeApi === "unsupported" || feature.rootSafeApi === "internal" || feature.rootSafeApi === "metadata-only"))
|
|
709
|
+
.map((feature) => feature.id);
|
|
710
|
+
const partialRequestedFeatures = features
|
|
711
|
+
.filter((feature) => feature.requested && feature.rootSafeApi === "partial")
|
|
712
|
+
.map((feature) => feature.id);
|
|
713
|
+
const warnings = [];
|
|
714
|
+
if (partialRequestedFeatures.length > 0) {
|
|
715
|
+
warnings.push(`Partial root material features requested: ${partialRequestedFeatures.join(", ")}. Capture route pixels before claiming production material quality.`);
|
|
716
|
+
}
|
|
717
|
+
if (unsupportedRequestedFeatures.length > 0) {
|
|
718
|
+
warnings.push(`Metadata/internal material features requested: ${unsupportedRequestedFeatures.join(", ")}. Do not claim rendered support from root createAuraApp alone.`);
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
kind: "aura-material-capability-diagnostics",
|
|
722
|
+
rendererPath: "root-createAuraApp",
|
|
723
|
+
requestedFeatures: [...requested].sort(),
|
|
724
|
+
unsupportedRequestedFeatures,
|
|
725
|
+
partialRequestedFeatures,
|
|
726
|
+
features,
|
|
727
|
+
warnings,
|
|
728
|
+
claimBoundary: "Material diagnostics describe public root createAuraApp support. They are not a substitute for browser screenshot evidence."
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function extractMaterialCapabilitySpecs(input) {
|
|
732
|
+
if (!input)
|
|
733
|
+
return [];
|
|
734
|
+
if (Array.isArray(input)) {
|
|
735
|
+
if (input.every(isAuraMaterialSpecLike))
|
|
736
|
+
return input;
|
|
737
|
+
return groups.flatten(input)
|
|
738
|
+
.filter((node) => (node.kind === "primitive" || node.kind === "model") && Boolean(node.material))
|
|
739
|
+
.map((node) => node.material);
|
|
740
|
+
}
|
|
741
|
+
if (isAuraMaterialSpecLike(input))
|
|
742
|
+
return [input];
|
|
743
|
+
const snapshot = normalizeSceneSnapshot(input);
|
|
744
|
+
return groups.flatten(snapshot.nodes)
|
|
745
|
+
.filter((node) => (node.kind === "primitive" || node.kind === "model") && Boolean(node.material))
|
|
746
|
+
.map((node) => node.material);
|
|
747
|
+
}
|
|
748
|
+
function isAuraMaterialSpecLike(value) {
|
|
749
|
+
if (!value || typeof value !== "object")
|
|
750
|
+
return false;
|
|
751
|
+
const candidate = value;
|
|
752
|
+
if (candidate.kind !== undefined || candidate.nodes !== undefined)
|
|
753
|
+
return false;
|
|
754
|
+
return Boolean(candidate.color !== undefined ||
|
|
755
|
+
candidate.roughness !== undefined ||
|
|
756
|
+
candidate.metallic !== undefined ||
|
|
757
|
+
candidate.metalness !== undefined ||
|
|
758
|
+
candidate.emissive !== undefined ||
|
|
759
|
+
candidate.opacity !== undefined ||
|
|
760
|
+
candidate.transmission !== undefined ||
|
|
761
|
+
candidate.clearcoat !== undefined ||
|
|
762
|
+
candidate.sheen !== undefined ||
|
|
763
|
+
candidate.texture !== undefined ||
|
|
764
|
+
candidate.normal !== undefined ||
|
|
765
|
+
candidate.roughnessMap !== undefined ||
|
|
766
|
+
candidate.metalnessMap !== undefined ||
|
|
767
|
+
candidate.envMapIntensity !== undefined ||
|
|
768
|
+
candidate.shader !== undefined ||
|
|
769
|
+
candidate.name !== undefined);
|
|
770
|
+
}
|
|
559
771
|
function createMaterialInspector(name, spec) {
|
|
560
772
|
const metalness = spec.metalness ?? spec.metallic ?? 0;
|
|
561
773
|
const parameters = [
|
|
@@ -807,6 +1019,7 @@ export const camera = {
|
|
|
807
1019
|
distance: options.distance ?? 5,
|
|
808
1020
|
position: options.position,
|
|
809
1021
|
target: options.target ?? [0, 1, 0],
|
|
1022
|
+
offset: options.offset,
|
|
810
1023
|
fov: options.fov ?? 50,
|
|
811
1024
|
easing: options.easing,
|
|
812
1025
|
captureTime: options.captureTime,
|
|
@@ -844,6 +1057,37 @@ export const camera = {
|
|
|
844
1057
|
const distance = extent * (options.padding ?? 2.15);
|
|
845
1058
|
return camera.orbit({ target: center, distance, fov: options.fov ?? 42 });
|
|
846
1059
|
},
|
|
1060
|
+
frameAsset: (asset, options = {}) => {
|
|
1061
|
+
const bounds = boundsFromAsset(asset);
|
|
1062
|
+
const size = boundsSize(bounds);
|
|
1063
|
+
const renderedScale = resolveFrameAssetRenderScale(bounds, options);
|
|
1064
|
+
const renderedSize = [
|
|
1065
|
+
Math.max(0.001, size[0] * renderedScale),
|
|
1066
|
+
Math.max(0.001, size[1] * renderedScale),
|
|
1067
|
+
Math.max(0.001, size[2] * renderedScale)
|
|
1068
|
+
];
|
|
1069
|
+
const position = options.position ?? [0, options.floorY ?? 0, 0];
|
|
1070
|
+
const target = options.target ?? [
|
|
1071
|
+
position[0],
|
|
1072
|
+
position[1] + renderedSize[1] * 0.28,
|
|
1073
|
+
position[2]
|
|
1074
|
+
];
|
|
1075
|
+
const fov = options.fov ?? 36;
|
|
1076
|
+
const fovRadians = Math.max(1, Math.min(120, fov)) * Math.PI / 180;
|
|
1077
|
+
const radius = Math.hypot(renderedSize[0], renderedSize[1], renderedSize[2]) / 2;
|
|
1078
|
+
const verticalFitDistance = renderedSize[1] / (2 * Math.tan(fovRadians / 2));
|
|
1079
|
+
const horizontalFitDistance = Math.max(renderedSize[0], renderedSize[2]) / (2 * Math.tan(fovRadians / 2));
|
|
1080
|
+
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));
|
|
1081
|
+
const azimuth = options.azimuth ?? 0.62;
|
|
1082
|
+
const elevation = options.elevation ?? 0.28;
|
|
1083
|
+
const horizontal = Math.max(0.001, Math.cos(elevation));
|
|
1084
|
+
const eye = [
|
|
1085
|
+
target[0] + Math.sin(azimuth) * horizontal * distance,
|
|
1086
|
+
target[1] + Math.sin(elevation) * distance,
|
|
1087
|
+
target[2] + Math.cos(azimuth) * horizontal * distance
|
|
1088
|
+
];
|
|
1089
|
+
return camera.orbit({ target, position: eye, distance, fov });
|
|
1090
|
+
},
|
|
847
1091
|
physics: () => camera.orbit({ target: [0, 0.58, -0.35], distance: 5.8, fov: 43 }),
|
|
848
1092
|
charts: () => camera.orbit({ target: [0, 0.78, 0], distance: 6.4, fov: 40 }),
|
|
849
1093
|
materials: () => camera.perspective({ position: [0, 2.08, 7.35], target: [0, 0.9, -0.72], fov: 40 }),
|
|
@@ -854,6 +1098,19 @@ export const camera = {
|
|
|
854
1098
|
miniGolf: () => camera.follow({ targetNode: "white physics golf ball", distance: 4.2, fov: 48 }),
|
|
855
1099
|
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 })
|
|
856
1100
|
};
|
|
1101
|
+
function resolveFrameAssetRenderScale(bounds, options) {
|
|
1102
|
+
const size = boundsSize(bounds);
|
|
1103
|
+
const height = Math.max(0.001, boundsHeight(bounds));
|
|
1104
|
+
const horizontalLength = Math.max(0.001, size[0], size[2]);
|
|
1105
|
+
const maxDimension = Math.max(0.001, boundsMaxDimension(bounds));
|
|
1106
|
+
if (isPositiveFinite(options.targetHeight))
|
|
1107
|
+
return options.targetHeight / height;
|
|
1108
|
+
if (isPositiveFinite(options.targetLength))
|
|
1109
|
+
return options.targetLength / horizontalLength;
|
|
1110
|
+
if (isPositiveFinite(options.targetMaxDimension))
|
|
1111
|
+
return options.targetMaxDimension / maxDimension;
|
|
1112
|
+
return AURA_NORMALIZED_MODEL_MAX_DIMENSION / maxDimension;
|
|
1113
|
+
}
|
|
857
1114
|
export const timeline = {
|
|
858
1115
|
loop: (options = {}) => ({
|
|
859
1116
|
mode: "loop",
|
|
@@ -1284,17 +1541,120 @@ const rendererQualityPresets = {
|
|
|
1284
1541
|
evidence: "prioritizes benchmark screenshots with stronger edge quality for labels, axes, and neon rings"
|
|
1285
1542
|
}
|
|
1286
1543
|
};
|
|
1544
|
+
const rendererQualityProfiles = {
|
|
1545
|
+
"safe-basic": {
|
|
1546
|
+
kind: "aura-renderer-quality-profile",
|
|
1547
|
+
id: "safe-basic",
|
|
1548
|
+
label: "Safe Basic",
|
|
1549
|
+
rendererMode: "safe-basic",
|
|
1550
|
+
status: "supported",
|
|
1551
|
+
antialiasing: "msaa",
|
|
1552
|
+
pixelRatio: 1,
|
|
1553
|
+
preserveDrawingBuffer: true,
|
|
1554
|
+
maxRecommendedDrawCalls: 180,
|
|
1555
|
+
requestedFeatures: ["typed models", "primitives", "basic particles", "base-color textures", "runtime nodes"],
|
|
1556
|
+
supportedInRoot: ["typed models", "primitives", "basic particles", "base-color textures", "runtime nodes"],
|
|
1557
|
+
blockedInRoot: ["production PBR parity", "skinned animation mixer", "native WebGPU compute", "postprocess pass chain"],
|
|
1558
|
+
claimBoundary: "Root createAuraApp safe path. Use this for public examples unless a browser test proves a stronger profile."
|
|
1559
|
+
},
|
|
1560
|
+
production: {
|
|
1561
|
+
kind: "aura-renderer-quality-profile",
|
|
1562
|
+
id: "production",
|
|
1563
|
+
label: "Production Typed GLB",
|
|
1564
|
+
rendererMode: "production",
|
|
1565
|
+
status: "supported",
|
|
1566
|
+
antialiasing: "msaa-plus-high-dpi",
|
|
1567
|
+
pixelRatio: 1.5,
|
|
1568
|
+
preserveDrawingBuffer: true,
|
|
1569
|
+
maxRecommendedDrawCalls: 260,
|
|
1570
|
+
requestedFeatures: ["typed GLB actors", "PBR material pipeline", "environment lighting", "shadow maps", "postprocess"],
|
|
1571
|
+
supportedInRoot: ["typed GLB actor bridge", "imported GLB render source", "PBR material pipeline metadata", "runtime diagnostics"],
|
|
1572
|
+
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"],
|
|
1573
|
+
claimBoundary: "Root createAuraApp can route typed GLB manifest assets through the production runtime. Claim only the specific features proven by route diagnostics and screenshots."
|
|
1574
|
+
},
|
|
1575
|
+
cinematic: {
|
|
1576
|
+
kind: "aura-renderer-quality-profile",
|
|
1577
|
+
id: "cinematic",
|
|
1578
|
+
label: "Cinematic Request",
|
|
1579
|
+
rendererMode: "production",
|
|
1580
|
+
status: "fallback-only",
|
|
1581
|
+
antialiasing: "msaa-plus-high-dpi",
|
|
1582
|
+
pixelRatio: 1.5,
|
|
1583
|
+
preserveDrawingBuffer: true,
|
|
1584
|
+
maxRecommendedDrawCalls: 320,
|
|
1585
|
+
requestedFeatures: ["cinematic lighting", "environment lighting", "postprocess", "motion proof", "high-DPI screenshots"],
|
|
1586
|
+
supportedInRoot: ["high-DPI canvas sizing", "scene diagnostics", "basic particles", "camera/timeline animation"],
|
|
1587
|
+
blockedInRoot: ["renderer-owned bloom pass", "depth-aware postprocess", "production shadow maps", "skinned animation mixer"],
|
|
1588
|
+
claimBoundary: "Cinematic is an explicit request profile, not proof of production cinematic rendering."
|
|
1589
|
+
},
|
|
1590
|
+
"experimental-webgpu": {
|
|
1591
|
+
kind: "aura-renderer-quality-profile",
|
|
1592
|
+
id: "experimental-webgpu",
|
|
1593
|
+
label: "Experimental WebGPU Request",
|
|
1594
|
+
rendererMode: "production",
|
|
1595
|
+
status: "experimental",
|
|
1596
|
+
antialiasing: "msaa",
|
|
1597
|
+
pixelRatio: 1,
|
|
1598
|
+
preserveDrawingBuffer: true,
|
|
1599
|
+
maxRecommendedDrawCalls: 220,
|
|
1600
|
+
requestedFeatures: ["WebGPU adapter", "compute dispatch", "native WebGPU rendering"],
|
|
1601
|
+
supportedInRoot: ["capability diagnostics", "fallback route evidence"],
|
|
1602
|
+
blockedInRoot: ["native WebGPU claim without adapter/backend/dispatch/render evidence"],
|
|
1603
|
+
claimBoundary: "May only claim native WebGPU when route diagnostics and browser screenshots prove adapter, backend, dispatch, and pixels."
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
function resolveRendererQualityProfile(id) {
|
|
1607
|
+
return rendererQualityProfiles[id ?? "safe-basic"] ?? rendererQualityProfiles["safe-basic"];
|
|
1608
|
+
}
|
|
1609
|
+
function normalizeCreateAppRendererOptions(options) {
|
|
1610
|
+
const profile = resolveRendererQualityProfile(options?.qualityProfile);
|
|
1611
|
+
const mode = options?.mode ?? profile.rendererMode;
|
|
1612
|
+
return {
|
|
1613
|
+
mode,
|
|
1614
|
+
fallback: options?.fallback ?? "safe-basic",
|
|
1615
|
+
qualityProfile: profile.id,
|
|
1616
|
+
profile
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
function analyzeProductionBridgeEligibility(nodes) {
|
|
1620
|
+
const modelNodes = nodes.filter(isRenderableModelNode);
|
|
1621
|
+
const typedModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "typed-aura-assets-manifest").length;
|
|
1622
|
+
const unsafeModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "unsafe-url").length;
|
|
1623
|
+
const inlineModelCount = modelNodes.filter((node) => createAssetProvenance(node.asset).source === "inline-definition").length;
|
|
1624
|
+
const primitiveCount = nodes.filter((node) => node.kind === "primitive").length;
|
|
1625
|
+
const effectCount = nodes.filter((node) => node.kind === "effect").length;
|
|
1626
|
+
const reasons = [];
|
|
1627
|
+
if (typedModelCount < 1)
|
|
1628
|
+
reasons.push("production bridge requires at least one typed GLB from generated aura-assets");
|
|
1629
|
+
if (unsafeModelCount > 0)
|
|
1630
|
+
reasons.push("unsafeModelUrl and remote/raw model URLs are blocked from the production bridge");
|
|
1631
|
+
if (inlineModelCount > 0)
|
|
1632
|
+
reasons.push("inline model definitions without manifest hashes are blocked from the production bridge");
|
|
1633
|
+
return {
|
|
1634
|
+
eligible: reasons.length === 0,
|
|
1635
|
+
typedModelCount,
|
|
1636
|
+
unsafeModelCount,
|
|
1637
|
+
inlineModelCount,
|
|
1638
|
+
primitiveCount,
|
|
1639
|
+
effectCount,
|
|
1640
|
+
reasons
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1287
1643
|
export const renderer = {
|
|
1288
1644
|
colorManagementPreset: () => rendererColorManagementPreset,
|
|
1289
1645
|
exposurePresets: () => sceneExposurePresets,
|
|
1290
1646
|
exposureFor: (category) => sceneExposurePresets[category],
|
|
1291
1647
|
qualityPresets: () => rendererQualityPresets,
|
|
1648
|
+
qualityProfiles: () => rendererQualityProfiles,
|
|
1649
|
+
qualityProfile: (id) => resolveRendererQualityProfile(id),
|
|
1292
1650
|
screenshotQuality: () => rendererQualityPresets.screenshot,
|
|
1293
|
-
diagnostics: (sceneValue) => createRendererDiagnosticReport(flattenSceneSnapshot(normalizeSceneSnapshot(sceneValue)))
|
|
1651
|
+
diagnostics: (sceneValue, options) => createRendererDiagnosticReport(flattenSceneSnapshot(normalizeSceneSnapshot(sceneValue)), undefined, options)
|
|
1294
1652
|
};
|
|
1295
|
-
function createRendererDiagnosticReport(snapshot, runtime) {
|
|
1653
|
+
function createRendererDiagnosticReport(snapshot, runtime, rendererOptions) {
|
|
1296
1654
|
const flattened = groups.flatten(snapshot.nodes);
|
|
1297
1655
|
const names = flattened.map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
|
|
1656
|
+
const rendererSelection = normalizeCreateAppRendererOptions(rendererOptions);
|
|
1657
|
+
const productionEligibility = analyzeProductionBridgeEligibility(flattened);
|
|
1298
1658
|
const sceneCategory = resolveRendererSceneCategory(snapshot, names);
|
|
1299
1659
|
const exposure = sceneExposurePresets[sceneCategory];
|
|
1300
1660
|
const bloom = flattened.find((node) => node.kind === "effect" && node.effect === "bloom");
|
|
@@ -1303,9 +1663,12 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1303
1663
|
const contactShadows = names.filter((name) => name.includes("contact shadow") || name.includes("footprint") || name.includes("glow pool")).length;
|
|
1304
1664
|
const ambientOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "ambient-occlusion");
|
|
1305
1665
|
const contactOcclusion = flattened.some((node) => node.kind === "effect" && node.effect === "contact-occlusion") || contactShadows > 0;
|
|
1306
|
-
const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion;
|
|
1307
|
-
const requestedPasses = requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
|
|
1308
1666
|
const runtimePostprocess = runtime?.postprocess;
|
|
1667
|
+
const runtimePasses = runtimePostprocess?.actualPasses ?? [];
|
|
1668
|
+
const postprocessRequested = Boolean(bloom) || ambientOcclusion || contactOcclusion || runtimePasses.length > 0;
|
|
1669
|
+
const requestedPasses = runtimePasses.length > 0
|
|
1670
|
+
? runtimePasses
|
|
1671
|
+
: requestedRendererPostProcessPasses(Boolean(bloom), ambientOcclusion, contactOcclusion);
|
|
1309
1672
|
const runtimeStatus = !postprocessRequested
|
|
1310
1673
|
? "disabled"
|
|
1311
1674
|
: runtime?.mounted
|
|
@@ -1324,6 +1687,18 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1324
1687
|
warnings.push("renderer diagnostics are a scene plan only; call createAuraApp(...).diagnostics() after the first render for pixel-backed pass status");
|
|
1325
1688
|
if (postprocessRequested && runtime?.mounted && !runtimePostprocess?.pixelBacked)
|
|
1326
1689
|
warnings.push("postprocess was requested but the runtime composer did not initialize a pixel-backed pass");
|
|
1690
|
+
if (rendererSelection.mode === "production" && !runtime?.mounted && productionEligibility.eligible) {
|
|
1691
|
+
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.`);
|
|
1692
|
+
}
|
|
1693
|
+
if (rendererSelection.mode === "production" && !productionEligibility.eligible) {
|
|
1694
|
+
warnings.push(`Renderer profile "${rendererSelection.profile.id}" will use safe-basic fallback for this scene: ${productionEligibility.reasons.join("; ")}.`);
|
|
1695
|
+
}
|
|
1696
|
+
if (rendererSelection.profile.status === "fallback-only") {
|
|
1697
|
+
warnings.push(`Renderer profile "${rendererSelection.profile.id}" is a request profile; public claims require route-specific runtime diagnostics and screenshot proof.`);
|
|
1698
|
+
}
|
|
1699
|
+
if (rendererSelection.profile.id === "experimental-webgpu") {
|
|
1700
|
+
warnings.push("Experimental WebGPU profile cannot claim native WebGPU without adapter, backend, dispatch, render, and pixel evidence.");
|
|
1701
|
+
}
|
|
1327
1702
|
warnings.push(...(runtime?.warnings ?? []));
|
|
1328
1703
|
const environmentStatus = runtime?.environment ?? {
|
|
1329
1704
|
enabled: Boolean(environment),
|
|
@@ -1343,6 +1718,9 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1343
1718
|
return {
|
|
1344
1719
|
kind: "aura-renderer-diagnostics",
|
|
1345
1720
|
colorManagement: rendererColorManagementPreset,
|
|
1721
|
+
rendererMode: rendererSelection.mode,
|
|
1722
|
+
fallbackMode: rendererSelection.fallback,
|
|
1723
|
+
qualityProfile: rendererSelection.profile,
|
|
1346
1724
|
sceneCategory,
|
|
1347
1725
|
exposure,
|
|
1348
1726
|
toneMapping: "aces-filmic",
|
|
@@ -1406,6 +1784,7 @@ function createRendererDiagnosticReport(snapshot, runtime) {
|
|
|
1406
1784
|
},
|
|
1407
1785
|
antialiasing: rendererQualityPresets.screenshot.antialiasing,
|
|
1408
1786
|
screenshotQuality: rendererQualityPresets.screenshot,
|
|
1787
|
+
materialCapabilities: createMaterialCapabilityDiagnostics(snapshot),
|
|
1409
1788
|
warnings
|
|
1410
1789
|
};
|
|
1411
1790
|
}
|
|
@@ -1439,7 +1818,18 @@ function resolveRendererSceneCategory(snapshot, names) {
|
|
|
1439
1818
|
return "physics";
|
|
1440
1819
|
if (hasName("chart") || hasName("data bar") || hasName("axis"))
|
|
1441
1820
|
return "chart";
|
|
1442
|
-
if (hasName("
|
|
1821
|
+
if (hasName("game") ||
|
|
1822
|
+
hasName("golf") ||
|
|
1823
|
+
hasName("score") ||
|
|
1824
|
+
hasName("cup") ||
|
|
1825
|
+
hasName("race") ||
|
|
1826
|
+
hasName("car") ||
|
|
1827
|
+
hasName("circuit") ||
|
|
1828
|
+
hasName("runner") ||
|
|
1829
|
+
hasName("platform") ||
|
|
1830
|
+
hasName("checkpoint") ||
|
|
1831
|
+
hasName("arcade") ||
|
|
1832
|
+
hasName("blockfall"))
|
|
1443
1833
|
return "game";
|
|
1444
1834
|
const background = colorToClearColor(snapshot.background);
|
|
1445
1835
|
const luminance = background[0] * 0.2126 + background[1] * 0.7152 + background[2] * 0.0722;
|
|
@@ -3238,6 +3628,821 @@ export function createAuraGameRuntime(options = {}) {
|
|
|
3238
3628
|
bodies: []
|
|
3239
3629
|
};
|
|
3240
3630
|
}
|
|
3631
|
+
export function createGameRacingRoadMeshNodes(options) {
|
|
3632
|
+
const mode = options.mode ?? "standalone";
|
|
3633
|
+
const routePoints = racingPresentationRoutePoints(options.sceneBinding, options.route, options.roadY);
|
|
3634
|
+
if (routePoints.length < 2)
|
|
3635
|
+
return [];
|
|
3636
|
+
const isCircuitStage = mode === "game-circuit";
|
|
3637
|
+
const roadWidth = racingPresentationRoadWidth(options.sceneBinding, options.route, mode);
|
|
3638
|
+
const markingVisibility = options.markingVisibility ?? "full";
|
|
3639
|
+
const nodes = [];
|
|
3640
|
+
const includeTerrain = options.includeTerrain ?? (mode === "standalone" || isCircuitStage);
|
|
3641
|
+
if (includeTerrain && mode !== "asset-overlay") {
|
|
3642
|
+
const terrainPaddingScale = options.terrainPaddingScale ?? (isCircuitStage ? 4.5 : 3.2);
|
|
3643
|
+
const bounds = boundsForPresentationPoints(routePoints, roadWidth * terrainPaddingScale);
|
|
3644
|
+
nodes.push(primitives.box({
|
|
3645
|
+
name: "scene-bound racing terrain pad",
|
|
3646
|
+
material: material.pbr({ color: options.terrainColor ?? (isCircuitStage ? "#1f342e" : "#263b35"), roughness: 0.92, metallic: 0 })
|
|
3647
|
+
})
|
|
3648
|
+
.position(bounds.centerX, -0.032, bounds.centerZ)
|
|
3649
|
+
.scale([bounds.width, 0.035, bounds.depth])
|
|
3650
|
+
.toJSON());
|
|
3651
|
+
if (isCircuitStage) {
|
|
3652
|
+
nodes.push(primitives.box({
|
|
3653
|
+
name: "scene-bound racing pit lane apron",
|
|
3654
|
+
material: material.pbr({ color: options.pitLaneColor ?? "#121c1e", roughness: 0.86, metallic: 0 })
|
|
3655
|
+
})
|
|
3656
|
+
.position(roundGamePresentation(bounds.centerX - bounds.width * 0.12), 0.002, roundGamePresentation(bounds.centerZ + bounds.depth * 0.23))
|
|
3657
|
+
.rotate(0, -0.16, 0)
|
|
3658
|
+
.scale([Math.max(0.8, bounds.width * 0.42), 0.026, roadWidth * 0.58])
|
|
3659
|
+
.toJSON());
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
for (let index = 0; index < routePoints.length - 1; index += 1) {
|
|
3663
|
+
const start = routePoints[index];
|
|
3664
|
+
const end = routePoints[index + 1];
|
|
3665
|
+
if (!start || !end)
|
|
3666
|
+
continue;
|
|
3667
|
+
const segment = segmentPresentation(start, end);
|
|
3668
|
+
if (segment.length <= 0.02)
|
|
3669
|
+
continue;
|
|
3670
|
+
if (mode === "asset-overlay") {
|
|
3671
|
+
nodes.push(primitives.box({
|
|
3672
|
+
name: `asset-bound racing topology line ${index + 1}`,
|
|
3673
|
+
material: material.emissive({
|
|
3674
|
+
color: options.laneColor ?? "#d8f6ff",
|
|
3675
|
+
emissive: options.laneColor ?? "#8df4ff",
|
|
3676
|
+
emissiveIntensity: 0.48,
|
|
3677
|
+
roughness: 0.42
|
|
3678
|
+
})
|
|
3679
|
+
})
|
|
3680
|
+
.position(segment.midX, segment.midY + 0.038, segment.midZ)
|
|
3681
|
+
.rotate(0, -segment.angle, 0)
|
|
3682
|
+
.scale([Math.max(0.06, segment.length * 0.68), 0.008, 0.014])
|
|
3683
|
+
.toJSON());
|
|
3684
|
+
continue;
|
|
3685
|
+
}
|
|
3686
|
+
const sideX = -Math.sin(segment.angle);
|
|
3687
|
+
const sideZ = Math.cos(segment.angle);
|
|
3688
|
+
const y = segment.midY;
|
|
3689
|
+
nodes.push(primitives.box({
|
|
3690
|
+
name: `scene-bound racing road segment ${index + 1}`,
|
|
3691
|
+
material: material.pbr({ color: options.roadColor ?? (isCircuitStage ? "#11191d" : "#343b3f"), roughness: 0.82, metallic: 0 })
|
|
3692
|
+
})
|
|
3693
|
+
.position(segment.midX, y, segment.midZ)
|
|
3694
|
+
.rotate(0, -segment.angle, 0)
|
|
3695
|
+
.scale([segment.length + roadWidth * 0.44, 0.028, roadWidth])
|
|
3696
|
+
.toJSON());
|
|
3697
|
+
if (markingVisibility === "none")
|
|
3698
|
+
continue;
|
|
3699
|
+
const isSubtleMark = markingVisibility === "subtle";
|
|
3700
|
+
if (!isSubtleMark || index % 3 === 0) {
|
|
3701
|
+
nodes.push(primitives.box({
|
|
3702
|
+
name: `scene-bound racing center stripe ${index + 1}`,
|
|
3703
|
+
material: material.emissive({
|
|
3704
|
+
color: options.laneColor ?? "#d8f6ff",
|
|
3705
|
+
emissive: options.laneColor ?? "#8df4ff",
|
|
3706
|
+
emissiveIntensity: isSubtleMark ? 0.34 : 0.7,
|
|
3707
|
+
roughness: 0.4
|
|
3708
|
+
})
|
|
3709
|
+
})
|
|
3710
|
+
.position(segment.midX, y + 0.022, segment.midZ)
|
|
3711
|
+
.rotate(0, -segment.angle, 0)
|
|
3712
|
+
.scale([Math.max(0.08, segment.length * (isCircuitStage ? 0.28 : 0.72)), 0.009, 0.01])
|
|
3713
|
+
.toJSON());
|
|
3714
|
+
}
|
|
3715
|
+
if (!isSubtleMark || index % 4 === 0) {
|
|
3716
|
+
nodes.push(primitives.box({
|
|
3717
|
+
name: `scene-bound racing left curb ${index + 1}`,
|
|
3718
|
+
material: material.pbr({ color: index % 2 === 0 ? "#d4ded8" : options.curbColor ?? "#df3550", roughness: 0.66 })
|
|
3719
|
+
})
|
|
3720
|
+
.position(roundGamePresentation(segment.midX + sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ + sideZ * roadWidth * 0.54))
|
|
3721
|
+
.rotate(0, -segment.angle, 0)
|
|
3722
|
+
.scale([Math.max(0.08, segment.length * 0.38), 0.012, 0.016])
|
|
3723
|
+
.toJSON(), primitives.box({
|
|
3724
|
+
name: `scene-bound racing right curb ${index + 1}`,
|
|
3725
|
+
material: material.pbr({ color: index % 2 === 0 ? options.curbColor ?? "#df3550" : "#d4ded8", roughness: 0.66 })
|
|
3726
|
+
})
|
|
3727
|
+
.position(roundGamePresentation(segment.midX - sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ - sideZ * roadWidth * 0.54))
|
|
3728
|
+
.rotate(0, -segment.angle, 0)
|
|
3729
|
+
.scale([Math.max(0.08, segment.length * 0.38), 0.012, 0.016])
|
|
3730
|
+
.toJSON());
|
|
3731
|
+
}
|
|
3732
|
+
if (isCircuitStage && (!isSubtleMark || index % 2 === 0)) {
|
|
3733
|
+
const railLength = Math.max(0.1, segment.length * (isSubtleMark ? 0.5 : 0.64));
|
|
3734
|
+
nodes.push(primitives.box({
|
|
3735
|
+
name: `scene-bound racing left guardrail ${index + 1}`,
|
|
3736
|
+
material: material.pbr({ color: "#141d20", roughness: 0.74, metallic: 0 })
|
|
3737
|
+
})
|
|
3738
|
+
.position(roundGamePresentation(segment.midX + sideX * roadWidth * 0.62), y + 0.034, roundGamePresentation(segment.midZ + sideZ * roadWidth * 0.62))
|
|
3739
|
+
.rotate(0, -segment.angle, 0)
|
|
3740
|
+
.scale([railLength, 0.018, 0.014])
|
|
3741
|
+
.toJSON(), primitives.box({
|
|
3742
|
+
name: `scene-bound racing right guardrail ${index + 1}`,
|
|
3743
|
+
material: material.pbr({ color: "#141d20", roughness: 0.74, metallic: 0 })
|
|
3744
|
+
})
|
|
3745
|
+
.position(roundGamePresentation(segment.midX - sideX * roadWidth * 0.62), y + 0.034, roundGamePresentation(segment.midZ - sideZ * roadWidth * 0.62))
|
|
3746
|
+
.rotate(0, -segment.angle, 0)
|
|
3747
|
+
.scale([railLength, 0.018, 0.014])
|
|
3748
|
+
.toJSON());
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return nodes;
|
|
3752
|
+
}
|
|
3753
|
+
export function createGameRacingCheckpointGateNodes(options) {
|
|
3754
|
+
const mode = options.mode ?? "scene-bound";
|
|
3755
|
+
const sample = racingPresentationSceneSample(options.sceneBinding, options.route, options.progress, options.roadY);
|
|
3756
|
+
if (!sample)
|
|
3757
|
+
return [];
|
|
3758
|
+
const roadWidth = options.roadWidth ?? racingPresentationRoadWidth(options.sceneBinding, options.route, "game-circuit");
|
|
3759
|
+
const label = options.index === undefined ? "racing checkpoint gate" : `racing checkpoint gate ${options.index + 1}`;
|
|
3760
|
+
const sideX = -Math.sin(sample.angle);
|
|
3761
|
+
const sideZ = Math.cos(sample.angle);
|
|
3762
|
+
const crossAngle = -sample.angle + Math.PI / 2;
|
|
3763
|
+
const prefix = mode === "asset-bound" ? "asset-bound" : "scene-bound";
|
|
3764
|
+
if (mode === "asset-bound") {
|
|
3765
|
+
return [
|
|
3766
|
+
primitives.box({
|
|
3767
|
+
name: `${prefix} ${label}`,
|
|
3768
|
+
material: material.emissive({ color: options.gateColor ?? "#f8f1bf", emissive: options.accentColor ?? "#f6e27a", emissiveIntensity: 0.38, roughness: 0.46 })
|
|
3769
|
+
})
|
|
3770
|
+
.position(sample.point[0], sample.point[1] + 0.045, sample.point[2])
|
|
3771
|
+
.rotate(0, crossAngle, 0)
|
|
3772
|
+
.scale([roadWidth * 0.9, 0.01, 0.026])
|
|
3773
|
+
.toJSON()
|
|
3774
|
+
];
|
|
3775
|
+
}
|
|
3776
|
+
const postOffset = roadWidth * 0.72;
|
|
3777
|
+
const bannerY = sample.point[1] + 0.096;
|
|
3778
|
+
const lightY = sample.point[1] + 0.072;
|
|
3779
|
+
const leftX = roundGamePresentation(sample.point[0] + sideX * postOffset);
|
|
3780
|
+
const leftZ = roundGamePresentation(sample.point[2] + sideZ * postOffset);
|
|
3781
|
+
const rightX = roundGamePresentation(sample.point[0] - sideX * postOffset);
|
|
3782
|
+
const rightZ = roundGamePresentation(sample.point[2] - sideZ * postOffset);
|
|
3783
|
+
const bannerX = roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.76);
|
|
3784
|
+
const bannerZ = roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.76);
|
|
3785
|
+
return [
|
|
3786
|
+
primitives.box({
|
|
3787
|
+
name: `${prefix} ${label} left post`,
|
|
3788
|
+
material: material.metal({ color: options.gateColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3789
|
+
})
|
|
3790
|
+
.position(leftX, sample.point[1] + 0.041, leftZ)
|
|
3791
|
+
.scale([0.028, 0.082, 0.028])
|
|
3792
|
+
.toJSON(),
|
|
3793
|
+
primitives.box({
|
|
3794
|
+
name: `${prefix} ${label} right post`,
|
|
3795
|
+
material: material.metal({ color: options.gateColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3796
|
+
})
|
|
3797
|
+
.position(rightX, sample.point[1] + 0.041, rightZ)
|
|
3798
|
+
.scale([0.028, 0.082, 0.028])
|
|
3799
|
+
.toJSON(),
|
|
3800
|
+
primitives.box({
|
|
3801
|
+
name: `${prefix} ${label} overhead banner`,
|
|
3802
|
+
material: material.emissive({
|
|
3803
|
+
color: options.accentColor ?? "#b66d33",
|
|
3804
|
+
emissive: options.accentColor ?? "#d88f4a",
|
|
3805
|
+
emissiveIntensity: 0.16,
|
|
3806
|
+
roughness: 0.42
|
|
3807
|
+
})
|
|
3808
|
+
})
|
|
3809
|
+
.position(bannerX, bannerY, bannerZ)
|
|
3810
|
+
.rotate(0, crossAngle, 0)
|
|
3811
|
+
.scale([roadWidth * 0.26, 0.024, 0.024])
|
|
3812
|
+
.toJSON(),
|
|
3813
|
+
primitives.sphere({
|
|
3814
|
+
name: `${prefix} ${label} left signal light`,
|
|
3815
|
+
material: material.emissive({ color: options.lightColor ?? "#7ee8c4", emissive: options.lightColor ?? "#7ee8c4", emissiveIntensity: 0.64, roughness: 0.32 })
|
|
3816
|
+
})
|
|
3817
|
+
.position(roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.2), lightY, roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.2))
|
|
3818
|
+
.scale(0.018)
|
|
3819
|
+
.toJSON(),
|
|
3820
|
+
primitives.sphere({
|
|
3821
|
+
name: `${prefix} ${label} right signal light`,
|
|
3822
|
+
material: material.emissive({ color: options.lightColor ?? "#7ee8c4", emissive: options.lightColor ?? "#7ee8c4", emissiveIntensity: 0.64, roughness: 0.32 })
|
|
3823
|
+
})
|
|
3824
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.2), lightY, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.2))
|
|
3825
|
+
.scale(0.018)
|
|
3826
|
+
.toJSON(),
|
|
3827
|
+
primitives.cylinder({
|
|
3828
|
+
name: `${prefix} ${label} left cone marker`,
|
|
3829
|
+
material: material.emissive({ color: options.accentColor ?? "#c78344", emissive: options.accentColor ?? "#c78344", emissiveIntensity: 0.26, roughness: 0.52 })
|
|
3830
|
+
})
|
|
3831
|
+
.position(roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.78), sample.point[1] + 0.034, roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.78))
|
|
3832
|
+
.scale([0.038, 0.068, 0.038])
|
|
3833
|
+
.toJSON(),
|
|
3834
|
+
primitives.cylinder({
|
|
3835
|
+
name: `${prefix} ${label} right cone marker`,
|
|
3836
|
+
material: material.emissive({ color: options.accentColor ?? "#c78344", emissive: options.accentColor ?? "#c78344", emissiveIntensity: 0.26, roughness: 0.52 })
|
|
3837
|
+
})
|
|
3838
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.78), sample.point[1] + 0.034, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.78))
|
|
3839
|
+
.scale([0.038, 0.068, 0.038])
|
|
3840
|
+
.toJSON()
|
|
3841
|
+
];
|
|
3842
|
+
}
|
|
3843
|
+
export function createGameRacingStartFinishNodes(options) {
|
|
3844
|
+
const mode = options.mode ?? "scene-bound";
|
|
3845
|
+
const sample = racingPresentationSceneSample(options.sceneBinding, options.route, 0, options.roadY);
|
|
3846
|
+
if (!sample)
|
|
3847
|
+
return [];
|
|
3848
|
+
const roadWidth = options.roadWidth ?? racingPresentationRoadWidth(options.sceneBinding, options.route, "game-circuit");
|
|
3849
|
+
const crossAngle = -sample.angle + Math.PI / 2;
|
|
3850
|
+
const prefix = mode === "asset-bound" ? "asset-bound" : "scene-bound";
|
|
3851
|
+
if (mode === "asset-bound") {
|
|
3852
|
+
return [
|
|
3853
|
+
primitives.box({
|
|
3854
|
+
name: `${prefix} racing start finish band`,
|
|
3855
|
+
material: material.emissive({ color: "#ffffff", emissive: "#ffffff", emissiveIntensity: 0.36, roughness: 0.3 })
|
|
3856
|
+
})
|
|
3857
|
+
.position(sample.point[0], sample.point[1] + 0.052, sample.point[2])
|
|
3858
|
+
.rotate(0, crossAngle, 0)
|
|
3859
|
+
.scale([roadWidth * 1.05, 0.01, 0.035])
|
|
3860
|
+
.toJSON()
|
|
3861
|
+
];
|
|
3862
|
+
}
|
|
3863
|
+
const nodes = [];
|
|
3864
|
+
const sideX = -Math.sin(sample.angle);
|
|
3865
|
+
const sideZ = Math.cos(sample.angle);
|
|
3866
|
+
const forwardX = Math.cos(sample.angle);
|
|
3867
|
+
const forwardZ = Math.sin(sample.angle);
|
|
3868
|
+
nodes.push(primitives.box({
|
|
3869
|
+
name: "scene-bound racing start grid vehicle pad",
|
|
3870
|
+
material: material.pbr({ color: options.checkerColorB ?? "#0d1416", roughness: 0.78, metallic: 0 })
|
|
3871
|
+
})
|
|
3872
|
+
.position(roundGamePresentation(sample.point[0] - forwardX * roadWidth * 0.1), sample.point[1] + 0.028, roundGamePresentation(sample.point[2] - forwardZ * roadWidth * 0.1))
|
|
3873
|
+
.rotate(0, crossAngle, 0)
|
|
3874
|
+
.scale([roadWidth * 1.08, 0.01, roadWidth * 0.78])
|
|
3875
|
+
.toJSON(), primitives.box({
|
|
3876
|
+
name: "scene-bound racing start finish light strip",
|
|
3877
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.48, roughness: 0.38 })
|
|
3878
|
+
})
|
|
3879
|
+
.position(sample.point[0], sample.point[1] + 0.047, sample.point[2])
|
|
3880
|
+
.rotate(0, crossAngle, 0)
|
|
3881
|
+
.scale([roadWidth * 0.86, 0.012, 0.028])
|
|
3882
|
+
.toJSON());
|
|
3883
|
+
const tileCount = 10;
|
|
3884
|
+
for (let tile = 0; tile < tileCount; tile += 1) {
|
|
3885
|
+
const laneOffset = ((tile + 0.5) / tileCount - 0.5) * roadWidth * 1.18;
|
|
3886
|
+
nodes.push(primitives.box({
|
|
3887
|
+
name: `scene-bound racing start finish checker ${tile + 1}`,
|
|
3888
|
+
material: material.pbr({ color: tile % 2 === 0 ? options.checkerColorA ?? "#d4ded8" : options.checkerColorB ?? "#0d1416", roughness: 0.52, metallic: 0 })
|
|
3889
|
+
})
|
|
3890
|
+
.position(roundGamePresentation(sample.point[0] + sideX * laneOffset), sample.point[1] + 0.04, roundGamePresentation(sample.point[2] + sideZ * laneOffset))
|
|
3891
|
+
.rotate(0, crossAngle, 0)
|
|
3892
|
+
.scale([roadWidth / tileCount, 0.014, 0.07])
|
|
3893
|
+
.toJSON());
|
|
3894
|
+
}
|
|
3895
|
+
const postOffset = roadWidth * 0.68;
|
|
3896
|
+
const leftX = roundGamePresentation(sample.point[0] + sideX * postOffset);
|
|
3897
|
+
const leftZ = roundGamePresentation(sample.point[2] + sideZ * postOffset);
|
|
3898
|
+
const rightX = roundGamePresentation(sample.point[0] - sideX * postOffset);
|
|
3899
|
+
const rightZ = roundGamePresentation(sample.point[2] - sideZ * postOffset);
|
|
3900
|
+
const bannerX = roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.84);
|
|
3901
|
+
const bannerZ = roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.84);
|
|
3902
|
+
nodes.push(primitives.box({
|
|
3903
|
+
name: "scene-bound racing launch grid bright marker",
|
|
3904
|
+
material: material.emissive({ color: options.checkerColorA ?? "#718980", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.16, roughness: 0.44 })
|
|
3905
|
+
})
|
|
3906
|
+
.position(roundGamePresentation(sample.point[0] - forwardX * roadWidth * 0.28), sample.point[1] + 0.038, roundGamePresentation(sample.point[2] - forwardZ * roadWidth * 0.28))
|
|
3907
|
+
.rotate(0, crossAngle, 0)
|
|
3908
|
+
.scale([roadWidth * 0.44, 0.012, 0.05])
|
|
3909
|
+
.toJSON(), primitives.box({
|
|
3910
|
+
name: "scene-bound racing launch grid dark marker",
|
|
3911
|
+
material: material.pbr({ color: options.checkerColorB ?? "#0d1416", roughness: 0.52, metallic: 0 })
|
|
3912
|
+
})
|
|
3913
|
+
.position(roundGamePresentation(sample.point[0] - forwardX * roadWidth * 0.42), sample.point[1] + 0.039, roundGamePresentation(sample.point[2] - forwardZ * roadWidth * 0.42))
|
|
3914
|
+
.rotate(0, crossAngle, 0)
|
|
3915
|
+
.scale([roadWidth * 0.44, 0.012, 0.05])
|
|
3916
|
+
.toJSON(), primitives.sphere({
|
|
3917
|
+
name: "scene-bound racing start left lane signal",
|
|
3918
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.72, roughness: 0.32 })
|
|
3919
|
+
})
|
|
3920
|
+
.position(roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.34 - forwardX * roadWidth * 0.2), sample.point[1] + 0.06, roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.34 - forwardZ * roadWidth * 0.2))
|
|
3921
|
+
.scale(0.036)
|
|
3922
|
+
.toJSON(), primitives.sphere({
|
|
3923
|
+
name: "scene-bound racing start right lane signal",
|
|
3924
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.72, roughness: 0.32 })
|
|
3925
|
+
})
|
|
3926
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.34 - forwardX * roadWidth * 0.2), sample.point[1] + 0.06, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.34 - forwardZ * roadWidth * 0.2))
|
|
3927
|
+
.scale(0.036)
|
|
3928
|
+
.toJSON(), primitives.box({
|
|
3929
|
+
name: "scene-bound racing start gantry left post",
|
|
3930
|
+
material: material.metal({ color: options.gantryColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3931
|
+
})
|
|
3932
|
+
.position(leftX, sample.point[1] + 0.064, leftZ)
|
|
3933
|
+
.scale([0.032, 0.128, 0.032])
|
|
3934
|
+
.toJSON(), primitives.box({
|
|
3935
|
+
name: "scene-bound racing start gantry right post",
|
|
3936
|
+
material: material.metal({ color: options.gantryColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3937
|
+
})
|
|
3938
|
+
.position(rightX, sample.point[1] + 0.064, rightZ)
|
|
3939
|
+
.scale([0.032, 0.128, 0.032])
|
|
3940
|
+
.toJSON(), primitives.box({
|
|
3941
|
+
name: "scene-bound racing start finish gantry banner",
|
|
3942
|
+
material: material.emissive({ color: options.gantryColor ?? "#2c4243", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.12, roughness: 0.42 })
|
|
3943
|
+
})
|
|
3944
|
+
.position(bannerX, sample.point[1] + 0.138, bannerZ)
|
|
3945
|
+
.rotate(0, crossAngle, 0)
|
|
3946
|
+
.scale([roadWidth * 0.34, 0.034, 0.034])
|
|
3947
|
+
.toJSON(), primitives.sphere({
|
|
3948
|
+
name: "scene-bound racing start green light",
|
|
3949
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.72, roughness: 0.32 })
|
|
3950
|
+
})
|
|
3951
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.84), sample.point[1] + 0.156, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.84))
|
|
3952
|
+
.scale(0.023)
|
|
3953
|
+
.toJSON());
|
|
3954
|
+
return nodes;
|
|
3955
|
+
}
|
|
3956
|
+
export function createGamePublicRacingPresentationNodes(options) {
|
|
3957
|
+
const mode = "game-circuit";
|
|
3958
|
+
const roadWidth = racingPresentationRoadWidth(options.sceneBinding, options.route, mode);
|
|
3959
|
+
const nodes = [
|
|
3960
|
+
...createGameRacingRoadMeshNodes({ ...options, mode, includeTerrain: options.includeTerrain ?? true })
|
|
3961
|
+
];
|
|
3962
|
+
for (const [index, progress] of (options.route.checkpoints ?? []).entries()) {
|
|
3963
|
+
nodes.push(...createGameRacingCheckpointGateNodes({
|
|
3964
|
+
sceneBinding: options.sceneBinding,
|
|
3965
|
+
route: options.route,
|
|
3966
|
+
progress,
|
|
3967
|
+
index,
|
|
3968
|
+
roadY: options.roadY,
|
|
3969
|
+
roadWidth,
|
|
3970
|
+
gateColor: options.checkpointColor,
|
|
3971
|
+
accentColor: options.checkpointAccentColor,
|
|
3972
|
+
lightColor: options.startLightColor
|
|
3973
|
+
}));
|
|
3974
|
+
}
|
|
3975
|
+
nodes.push(...createGameRacingStartFinishNodes({
|
|
3976
|
+
sceneBinding: options.sceneBinding,
|
|
3977
|
+
route: options.route,
|
|
3978
|
+
roadY: options.roadY,
|
|
3979
|
+
roadWidth,
|
|
3980
|
+
lightColor: options.startLightColor
|
|
3981
|
+
}));
|
|
3982
|
+
return nodes;
|
|
3983
|
+
}
|
|
3984
|
+
export function createGameRacingCameraRig(options) {
|
|
3985
|
+
return createGameRacingPresentationCamera(options);
|
|
3986
|
+
}
|
|
3987
|
+
export function certifyPublicRacingPresentation(input) {
|
|
3988
|
+
const base = certifyPublicRacingGeometry(input);
|
|
3989
|
+
const blockers = [...base.blockers];
|
|
3990
|
+
const presentation = input.presentation;
|
|
3991
|
+
if (!presentation) {
|
|
3992
|
+
blockers.push("racing:presentation-missing");
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
if ((presentation.roadMeshNodes ?? 0) < 8)
|
|
3996
|
+
blockers.push(`racing:presentation-road-mesh-too-small:${presentation.roadMeshNodes ?? 0}`);
|
|
3997
|
+
if ((presentation.checkpointGateNodes ?? 0) < input.checkpoints.length) {
|
|
3998
|
+
blockers.push(`racing:presentation-checkpoint-gates-too-few:${presentation.checkpointGateNodes ?? 0}`);
|
|
3999
|
+
}
|
|
4000
|
+
if ((presentation.startFinishNodes ?? 0) < 4)
|
|
4001
|
+
blockers.push(`racing:presentation-start-finish-too-small:${presentation.startFinishNodes ?? 0}`);
|
|
4002
|
+
if (presentation.cameraMode !== "follow" && presentation.cameraMode !== "perspective") {
|
|
4003
|
+
blockers.push(`racing:presentation-camera-not-racing:${presentation.cameraMode ?? "missing"}`);
|
|
4004
|
+
}
|
|
4005
|
+
if ((presentation.debugMarkerCount ?? 0) > 0)
|
|
4006
|
+
blockers.push(`racing:presentation-debug-markers:${presentation.debugMarkerCount}`);
|
|
4007
|
+
}
|
|
4008
|
+
return {
|
|
4009
|
+
...base,
|
|
4010
|
+
publicReady: blockers.length === 0,
|
|
4011
|
+
blockers
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
export function createGameRacingPresentationTrackNodes(options) {
|
|
4015
|
+
const mode = options.mode ?? "standalone";
|
|
4016
|
+
const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
|
|
4017
|
+
if (mode === "asset-overlay" && guideVisibility === "public")
|
|
4018
|
+
return [];
|
|
4019
|
+
const roadWidth = racingPresentationRoadWidth(options.sceneBinding, options.route, mode);
|
|
4020
|
+
const nodes = [...createGameRacingRoadMeshNodes(options)];
|
|
4021
|
+
if (mode === "asset-overlay") {
|
|
4022
|
+
for (const [index, progress] of (options.route.checkpoints ?? []).entries()) {
|
|
4023
|
+
nodes.push(...createGameRacingCheckpointGateNodes({
|
|
4024
|
+
sceneBinding: options.sceneBinding,
|
|
4025
|
+
route: options.route,
|
|
4026
|
+
progress,
|
|
4027
|
+
index,
|
|
4028
|
+
mode: "asset-bound",
|
|
4029
|
+
roadY: options.roadY,
|
|
4030
|
+
roadWidth,
|
|
4031
|
+
gateColor: options.laneColor,
|
|
4032
|
+
accentColor: options.laneColor
|
|
4033
|
+
}));
|
|
4034
|
+
}
|
|
4035
|
+
nodes.push(...createGameRacingStartFinishNodes({
|
|
4036
|
+
sceneBinding: options.sceneBinding,
|
|
4037
|
+
route: options.route,
|
|
4038
|
+
mode: "asset-bound",
|
|
4039
|
+
roadY: options.roadY,
|
|
4040
|
+
roadWidth
|
|
4041
|
+
}));
|
|
4042
|
+
return nodes;
|
|
4043
|
+
}
|
|
4044
|
+
if (guideVisibility === "evidence" || guideVisibility === "full") {
|
|
4045
|
+
for (const [index, progress] of (options.route.checkpoints ?? []).entries()) {
|
|
4046
|
+
nodes.push(...createGameRacingCheckpointGateNodes({
|
|
4047
|
+
sceneBinding: options.sceneBinding,
|
|
4048
|
+
route: options.route,
|
|
4049
|
+
progress,
|
|
4050
|
+
index,
|
|
4051
|
+
roadY: options.roadY,
|
|
4052
|
+
roadWidth,
|
|
4053
|
+
gateColor: options.laneColor,
|
|
4054
|
+
accentColor: options.laneColor
|
|
4055
|
+
}));
|
|
4056
|
+
}
|
|
4057
|
+
nodes.push(...createGameRacingStartFinishNodes({
|
|
4058
|
+
sceneBinding: options.sceneBinding,
|
|
4059
|
+
route: options.route,
|
|
4060
|
+
roadY: options.roadY,
|
|
4061
|
+
roadWidth,
|
|
4062
|
+
lightColor: options.laneColor
|
|
4063
|
+
}));
|
|
4064
|
+
}
|
|
4065
|
+
return nodes;
|
|
4066
|
+
}
|
|
4067
|
+
export function createGamePublicPlatformerPresentationNodes(options) {
|
|
4068
|
+
return createGamePlatformerPresentationSurfaceNodes({
|
|
4069
|
+
...options,
|
|
4070
|
+
mode: "game-level",
|
|
4071
|
+
guideVisibility: "public"
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4074
|
+
export function createGamePlatformerGroundMeshNodes(options) {
|
|
4075
|
+
return createSinglePlatformerSurfaceMeshNodes("ground", options);
|
|
4076
|
+
}
|
|
4077
|
+
export function createGamePlatformerPlatformMeshNodes(options) {
|
|
4078
|
+
return createSinglePlatformerSurfaceMeshNodes("platform", options);
|
|
4079
|
+
}
|
|
4080
|
+
export function createGamePlatformerHazardNodes(options) {
|
|
4081
|
+
const mode = options.mode ?? "game-level";
|
|
4082
|
+
const isGameLevel = mode === "game-level";
|
|
4083
|
+
const rect = options.sceneBinding.surfaceToSceneRect(options.hazard);
|
|
4084
|
+
return [
|
|
4085
|
+
primitives.box({
|
|
4086
|
+
name: `scene-bound platformer hazard ${options.hazard.id}`,
|
|
4087
|
+
material: material.emissive({
|
|
4088
|
+
color: options.color ?? "#ff6b6b",
|
|
4089
|
+
emissive: options.color ?? "#ff6b6b",
|
|
4090
|
+
emissiveIntensity: isGameLevel ? 0.7 : 0.54,
|
|
4091
|
+
roughness: 0.36
|
|
4092
|
+
})
|
|
4093
|
+
})
|
|
4094
|
+
.position(rect.center[0], rect.center[1], rect.center[2] + (isGameLevel ? 0.39 : 0.31))
|
|
4095
|
+
.scale([Math.max(rect.size[0], 0.045), Math.max(rect.size[1], 0.045), 0.055])
|
|
4096
|
+
.toJSON()
|
|
4097
|
+
];
|
|
4098
|
+
}
|
|
4099
|
+
export function createGamePlatformerCheckpointNodes(options) {
|
|
4100
|
+
const point = options.sceneBinding.toScenePoint(options.checkpoint, 0.02);
|
|
4101
|
+
return [
|
|
4102
|
+
primitives.box({
|
|
4103
|
+
name: `scene-bound platformer checkpoint ${options.checkpoint.id}`,
|
|
4104
|
+
material: material.emissive({
|
|
4105
|
+
color: options.color ?? "#d7f9ff",
|
|
4106
|
+
emissive: options.color ?? "#a6f4ff",
|
|
4107
|
+
emissiveIntensity: 0.48,
|
|
4108
|
+
roughness: 0.4
|
|
4109
|
+
})
|
|
4110
|
+
})
|
|
4111
|
+
.position(point[0], point[1], point[2] + 0.29)
|
|
4112
|
+
.scale([0.026, 0.22, 0.034])
|
|
4113
|
+
.toJSON()
|
|
4114
|
+
];
|
|
4115
|
+
}
|
|
4116
|
+
export function createGamePlatformerFinishNodes(options) {
|
|
4117
|
+
const point = options.sceneBinding.toScenePoint(options.finish, 0.04);
|
|
4118
|
+
return [
|
|
4119
|
+
primitives.box({
|
|
4120
|
+
name: `scene-bound platformer finish marker${options.finish.id ? ` ${options.finish.id}` : ""}`,
|
|
4121
|
+
material: material.emissive({
|
|
4122
|
+
color: options.color ?? "#b6ffbd",
|
|
4123
|
+
emissive: options.color ?? "#83f58f",
|
|
4124
|
+
emissiveIntensity: 0.6,
|
|
4125
|
+
roughness: 0.42
|
|
4126
|
+
})
|
|
4127
|
+
})
|
|
4128
|
+
.position(point[0], point[1], point[2] + 0.34)
|
|
4129
|
+
.scale([0.06, 0.34, 0.05])
|
|
4130
|
+
.toJSON()
|
|
4131
|
+
];
|
|
4132
|
+
}
|
|
4133
|
+
export function createGamePlatformerCameraRig(options) {
|
|
4134
|
+
return createGamePlatformerPresentationCamera(options);
|
|
4135
|
+
}
|
|
4136
|
+
export function certifyPublicPlatformerPresentation(input) {
|
|
4137
|
+
const base = certifyPublicPlatformerGeometry(input);
|
|
4138
|
+
const blockers = [...base.blockers];
|
|
4139
|
+
const presentation = input.presentation;
|
|
4140
|
+
if (!presentation) {
|
|
4141
|
+
blockers.push("platformer:presentation-missing");
|
|
4142
|
+
}
|
|
4143
|
+
else {
|
|
4144
|
+
if ((presentation.groundMeshNodes ?? 0) < 2)
|
|
4145
|
+
blockers.push(`platformer:presentation-ground-mesh-too-small:${presentation.groundMeshNodes ?? 0}`);
|
|
4146
|
+
if ((presentation.platformMeshNodes ?? 0) < 6)
|
|
4147
|
+
blockers.push(`platformer:presentation-platform-mesh-too-small:${presentation.platformMeshNodes ?? 0}`);
|
|
4148
|
+
if ((presentation.hazardNodes ?? 0) < input.hazards.length) {
|
|
4149
|
+
blockers.push(`platformer:presentation-hazards-too-few:${presentation.hazardNodes ?? 0}`);
|
|
4150
|
+
}
|
|
4151
|
+
if ((presentation.checkpointNodes ?? 0) < input.checkpoints.length) {
|
|
4152
|
+
blockers.push(`platformer:presentation-checkpoints-too-few:${presentation.checkpointNodes ?? 0}`);
|
|
4153
|
+
}
|
|
4154
|
+
if ((presentation.finishNodes ?? 0) < 1)
|
|
4155
|
+
blockers.push(`platformer:presentation-finish-missing:${presentation.finishNodes ?? 0}`);
|
|
4156
|
+
if (presentation.cameraMode !== "follow" && presentation.cameraMode !== "perspective") {
|
|
4157
|
+
blockers.push(`platformer:presentation-camera-not-side-scroller:${presentation.cameraMode ?? "missing"}`);
|
|
4158
|
+
}
|
|
4159
|
+
if ((presentation.debugMarkerCount ?? 0) > 0)
|
|
4160
|
+
blockers.push(`platformer:presentation-debug-markers:${presentation.debugMarkerCount}`);
|
|
4161
|
+
if (presentation.characterGrounded !== true)
|
|
4162
|
+
blockers.push("platformer:presentation-character-not-grounded");
|
|
4163
|
+
}
|
|
4164
|
+
return {
|
|
4165
|
+
...base,
|
|
4166
|
+
publicReady: blockers.length === 0,
|
|
4167
|
+
blockers
|
|
4168
|
+
};
|
|
4169
|
+
}
|
|
4170
|
+
export function createGamePlatformerPresentationSurfaceNodes(options) {
|
|
4171
|
+
const mode = options.mode ?? "standalone";
|
|
4172
|
+
const platforms = options.level.platforms ?? [];
|
|
4173
|
+
const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
|
|
4174
|
+
if (mode === "asset-overlay" && guideVisibility === "public")
|
|
4175
|
+
return [];
|
|
4176
|
+
const isGameLevel = mode === "game-level";
|
|
4177
|
+
const nodes = [];
|
|
4178
|
+
if ((mode === "standalone" || mode === "game-level") && platforms.length > 0 && options.includeBackdrop !== false) {
|
|
4179
|
+
const rects = platforms.map((surface) => options.sceneBinding.surfaceToSceneRect(surface));
|
|
4180
|
+
const bounds = boundsForPresentationRects(rects, isGameLevel ? 0.78 : 0.42);
|
|
4181
|
+
nodes.push(primitives.box({
|
|
4182
|
+
name: "scene-bound platformer backdrop",
|
|
4183
|
+
material: material.pbr({ color: isGameLevel ? "#263d48" : "#314d59", roughness: 0.94, metallic: 0, opacity: isGameLevel ? 0.2 : 0.28 })
|
|
4184
|
+
})
|
|
4185
|
+
.position(bounds.centerX, bounds.centerY + (isGameLevel ? 0.18 : 0.08), bounds.backZ)
|
|
4186
|
+
.scale([bounds.width, Math.max(0.2, bounds.height * (isGameLevel ? 2.25 : 1.8)), 0.035])
|
|
4187
|
+
.toJSON());
|
|
4188
|
+
}
|
|
4189
|
+
for (const surface of platforms) {
|
|
4190
|
+
const rect = options.sceneBinding.surfaceToSceneRect(surface);
|
|
4191
|
+
const width = Math.max(rect.size[0], 0.08);
|
|
4192
|
+
const height = Math.max(rect.size[1], 0.055);
|
|
4193
|
+
if (mode === "asset-overlay") {
|
|
4194
|
+
nodes.push(primitives.box({
|
|
4195
|
+
name: `asset-bound platformer contact strip ${surface.id}`,
|
|
4196
|
+
material: material.emissive({
|
|
4197
|
+
color: options.platformTrimColor ?? "#c9f7ff",
|
|
4198
|
+
emissive: options.platformTrimColor ?? "#a8f4ff",
|
|
4199
|
+
emissiveIntensity: 0.28,
|
|
4200
|
+
roughness: 0.44
|
|
4201
|
+
})
|
|
4202
|
+
})
|
|
4203
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.026), rect.center[2] + 0.24)
|
|
4204
|
+
.scale([width * 0.78, 0.012, 0.024])
|
|
4205
|
+
.toJSON());
|
|
4206
|
+
continue;
|
|
4207
|
+
}
|
|
4208
|
+
nodes.push(primitives.box({
|
|
4209
|
+
name: `scene-bound platformer surface ${surface.id}`,
|
|
4210
|
+
material: material.pbr({ color: options.platformColor ?? (isGameLevel ? "#526972" : "#718994"), roughness: 0.72, metallic: 0 })
|
|
4211
|
+
})
|
|
4212
|
+
.position(rect.center[0], rect.center[1], rect.center[2])
|
|
4213
|
+
.scale([width, height, isGameLevel ? 0.64 : 0.5])
|
|
4214
|
+
.toJSON(), primitives.box({
|
|
4215
|
+
name: `scene-bound platformer surface trim ${surface.id}`,
|
|
4216
|
+
material: material.emissive({
|
|
4217
|
+
color: options.platformTrimColor ?? "#c9f7ff",
|
|
4218
|
+
emissive: options.platformTrimColor ?? "#a8f4ff",
|
|
4219
|
+
emissiveIntensity: 0.36,
|
|
4220
|
+
roughness: 0.38
|
|
4221
|
+
})
|
|
4222
|
+
})
|
|
4223
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.012), rect.center[2] + (isGameLevel ? 0.334 : 0.262))
|
|
4224
|
+
.scale([width * 0.92, 0.016, 0.035])
|
|
4225
|
+
.toJSON());
|
|
4226
|
+
}
|
|
4227
|
+
for (const hazard of options.level.hazards ?? []) {
|
|
4228
|
+
const rect = options.sceneBinding.surfaceToSceneRect(hazard);
|
|
4229
|
+
nodes.push(primitives.box({
|
|
4230
|
+
name: `scene-bound platformer hazard ${hazard.id}`,
|
|
4231
|
+
material: material.emissive({
|
|
4232
|
+
color: options.hazardColor ?? "#ff6b6b",
|
|
4233
|
+
emissive: options.hazardColor ?? "#ff6b6b",
|
|
4234
|
+
emissiveIntensity: 0.7,
|
|
4235
|
+
roughness: 0.36
|
|
4236
|
+
})
|
|
4237
|
+
})
|
|
4238
|
+
.position(rect.center[0], rect.center[1], rect.center[2] + (isGameLevel ? 0.39 : 0.31))
|
|
4239
|
+
.scale([Math.max(rect.size[0], 0.045), Math.max(rect.size[1], 0.045), 0.055])
|
|
4240
|
+
.toJSON());
|
|
4241
|
+
}
|
|
4242
|
+
for (const collectible of options.level.collectibles ?? []) {
|
|
4243
|
+
const point = options.sceneBinding.toScenePoint(collectible, 0.04);
|
|
4244
|
+
nodes.push(primitives.torus({
|
|
4245
|
+
name: `scene-bound platformer collectible ${collectible.id}`,
|
|
4246
|
+
material: material.emissive({
|
|
4247
|
+
color: options.collectibleColor ?? "#fff3a3",
|
|
4248
|
+
emissive: options.collectibleColor ?? "#f6dc67",
|
|
4249
|
+
emissiveIntensity: 0.62,
|
|
4250
|
+
roughness: 0.4
|
|
4251
|
+
})
|
|
4252
|
+
})
|
|
4253
|
+
.position(point[0], point[1], point[2] + (isGameLevel ? 0.26 : 0.16))
|
|
4254
|
+
.rotate(Math.PI / 2, 0, 0)
|
|
4255
|
+
.scale([0.045, 0.045, 0.012])
|
|
4256
|
+
.toJSON());
|
|
4257
|
+
}
|
|
4258
|
+
for (const checkpoint of options.level.checkpoints ?? []) {
|
|
4259
|
+
const point = options.sceneBinding.toScenePoint(checkpoint, 0.02);
|
|
4260
|
+
nodes.push(primitives.box({
|
|
4261
|
+
name: `scene-bound platformer checkpoint ${checkpoint.id}`,
|
|
4262
|
+
material: material.emissive({ color: "#d7f9ff", emissive: "#a6f4ff", emissiveIntensity: 0.48, roughness: 0.4 })
|
|
4263
|
+
})
|
|
4264
|
+
.position(point[0], point[1], point[2] + (isGameLevel ? 0.29 : 0.19))
|
|
4265
|
+
.scale([0.026, 0.22, 0.034])
|
|
4266
|
+
.toJSON());
|
|
4267
|
+
}
|
|
4268
|
+
if (options.level.finish) {
|
|
4269
|
+
const finish = options.sceneBinding.toScenePoint(options.level.finish, 0.04);
|
|
4270
|
+
nodes.push(primitives.box({
|
|
4271
|
+
name: "scene-bound platformer finish marker",
|
|
4272
|
+
material: material.emissive({
|
|
4273
|
+
color: options.finishColor ?? "#b6ffbd",
|
|
4274
|
+
emissive: options.finishColor ?? "#83f58f",
|
|
4275
|
+
emissiveIntensity: 0.6,
|
|
4276
|
+
roughness: 0.42
|
|
4277
|
+
})
|
|
4278
|
+
})
|
|
4279
|
+
.position(finish[0], finish[1], finish[2] + (isGameLevel ? 0.34 : 0.22))
|
|
4280
|
+
.scale([0.06, 0.34, 0.05])
|
|
4281
|
+
.toJSON());
|
|
4282
|
+
}
|
|
4283
|
+
return nodes;
|
|
4284
|
+
}
|
|
4285
|
+
function createSinglePlatformerSurfaceMeshNodes(surfaceRole, options) {
|
|
4286
|
+
const mode = options.mode ?? "game-level";
|
|
4287
|
+
const isGameLevel = mode === "game-level";
|
|
4288
|
+
const rect = options.sceneBinding.surfaceToSceneRect(options.surface);
|
|
4289
|
+
const width = Math.max(rect.size[0], 0.08);
|
|
4290
|
+
const height = Math.max(rect.size[1], 0.055);
|
|
4291
|
+
if (mode === "asset-bound") {
|
|
4292
|
+
return [
|
|
4293
|
+
primitives.box({
|
|
4294
|
+
name: `asset-bound platformer ${surfaceRole} contact strip ${options.surface.id}`,
|
|
4295
|
+
material: material.emissive({
|
|
4296
|
+
color: options.trimColor ?? "#c9f7ff",
|
|
4297
|
+
emissive: options.trimColor ?? "#a8f4ff",
|
|
4298
|
+
emissiveIntensity: 0.28,
|
|
4299
|
+
roughness: 0.44
|
|
4300
|
+
})
|
|
4301
|
+
})
|
|
4302
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.026), rect.center[2] + 0.24)
|
|
4303
|
+
.scale([width * 0.78, 0.012, 0.024])
|
|
4304
|
+
.toJSON()
|
|
4305
|
+
];
|
|
4306
|
+
}
|
|
4307
|
+
return [
|
|
4308
|
+
primitives.box({
|
|
4309
|
+
name: `scene-bound platformer ${surfaceRole} mesh ${options.surface.id}`,
|
|
4310
|
+
material: material.pbr({
|
|
4311
|
+
color: options.color ?? (isGameLevel ? "#526972" : "#718994"),
|
|
4312
|
+
roughness: 0.72,
|
|
4313
|
+
metallic: 0
|
|
4314
|
+
})
|
|
4315
|
+
})
|
|
4316
|
+
.position(rect.center[0], rect.center[1], rect.center[2])
|
|
4317
|
+
.scale([width, height, isGameLevel ? 0.64 : 0.5])
|
|
4318
|
+
.toJSON(),
|
|
4319
|
+
primitives.box({
|
|
4320
|
+
name: `scene-bound platformer ${surfaceRole} trim ${options.surface.id}`,
|
|
4321
|
+
material: material.emissive({
|
|
4322
|
+
color: options.trimColor ?? "#c9f7ff",
|
|
4323
|
+
emissive: options.trimColor ?? "#a8f4ff",
|
|
4324
|
+
emissiveIntensity: 0.36,
|
|
4325
|
+
roughness: 0.38
|
|
4326
|
+
})
|
|
4327
|
+
})
|
|
4328
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.012), rect.center[2] + (isGameLevel ? 0.334 : 0.262))
|
|
4329
|
+
.scale([width * 0.92, 0.016, 0.035])
|
|
4330
|
+
.toJSON()
|
|
4331
|
+
];
|
|
4332
|
+
}
|
|
4333
|
+
function boundsForPresentationPoints(points, padding) {
|
|
4334
|
+
const xs = points.map((point) => point[0]);
|
|
4335
|
+
const zs = points.map((point) => point[2]);
|
|
4336
|
+
const minX = Math.min(...xs);
|
|
4337
|
+
const maxX = Math.max(...xs);
|
|
4338
|
+
const minZ = Math.min(...zs);
|
|
4339
|
+
const maxZ = Math.max(...zs);
|
|
4340
|
+
return {
|
|
4341
|
+
centerX: roundGamePresentation((minX + maxX) / 2),
|
|
4342
|
+
centerZ: roundGamePresentation((minZ + maxZ) / 2),
|
|
4343
|
+
width: roundGamePresentation(Math.max(1.2, maxX - minX + padding)),
|
|
4344
|
+
depth: roundGamePresentation(Math.max(1.2, maxZ - minZ + padding))
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
function boundsForPresentationRects(rects, padding) {
|
|
4348
|
+
const minX = Math.min(...rects.map((rect) => rect.center[0] - rect.size[0] / 2));
|
|
4349
|
+
const maxX = Math.max(...rects.map((rect) => rect.center[0] + rect.size[0] / 2));
|
|
4350
|
+
const minY = Math.min(...rects.map((rect) => rect.center[1] - rect.size[1] / 2));
|
|
4351
|
+
const maxY = Math.max(...rects.map((rect) => rect.center[1] + rect.size[1] / 2));
|
|
4352
|
+
const z = Math.min(...rects.map((rect) => rect.center[2]));
|
|
4353
|
+
return {
|
|
4354
|
+
centerX: roundGamePresentation((minX + maxX) / 2),
|
|
4355
|
+
centerY: roundGamePresentation((minY + maxY) / 2),
|
|
4356
|
+
backZ: roundGamePresentation(z - 0.22),
|
|
4357
|
+
width: roundGamePresentation(Math.max(1, maxX - minX + padding)),
|
|
4358
|
+
height: roundGamePresentation(Math.max(0.6, maxY - minY + padding))
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
function segmentPresentation(start, end) {
|
|
4362
|
+
const dx = end[0] - start[0];
|
|
4363
|
+
const dz = end[2] - start[2];
|
|
4364
|
+
return {
|
|
4365
|
+
midX: roundGamePresentation((start[0] + end[0]) / 2),
|
|
4366
|
+
midY: roundGamePresentation((start[1] + end[1]) / 2),
|
|
4367
|
+
midZ: roundGamePresentation((start[2] + end[2]) / 2),
|
|
4368
|
+
length: roundGamePresentation(Math.hypot(dx, dz)),
|
|
4369
|
+
angle: Math.atan2(dz, dx)
|
|
4370
|
+
};
|
|
4371
|
+
}
|
|
4372
|
+
function racingPresentationRoutePoints(sceneBinding, route, roadY) {
|
|
4373
|
+
return route.points.map((point) => sceneBinding.toScenePoint(point, roadY ?? 0.012));
|
|
4374
|
+
}
|
|
4375
|
+
function racingPresentationRoadWidth(sceneBinding, route, mode) {
|
|
4376
|
+
const isCircuitStage = mode === "game-circuit";
|
|
4377
|
+
return Math.max((route.width ?? 0.18) * sceneBinding.transform.scale * (isCircuitStage ? 1.55 : 1.5), isCircuitStage ? 0.34 : 0.18);
|
|
4378
|
+
}
|
|
4379
|
+
function racingPresentationSceneSample(sceneBinding, route, progress, roadY) {
|
|
4380
|
+
const sample = racingPresentationRouteSample(route, progress);
|
|
4381
|
+
if (!sample)
|
|
4382
|
+
return undefined;
|
|
4383
|
+
return {
|
|
4384
|
+
point: sceneBinding.toScenePoint(sample.point, roadY ?? 0.012),
|
|
4385
|
+
angle: sample.angle
|
|
4386
|
+
};
|
|
4387
|
+
}
|
|
4388
|
+
function racingPresentationRouteSample(route, progress) {
|
|
4389
|
+
const points = route.points;
|
|
4390
|
+
if (points.length < 2)
|
|
4391
|
+
return undefined;
|
|
4392
|
+
const clamped = Number.isFinite(progress) ? Math.min(1, Math.max(0, progress)) : 0;
|
|
4393
|
+
let totalLength = 0;
|
|
4394
|
+
const segmentLengths = [];
|
|
4395
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
4396
|
+
const start = points[index];
|
|
4397
|
+
const end = points[index + 1];
|
|
4398
|
+
if (!start || !end)
|
|
4399
|
+
continue;
|
|
4400
|
+
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
4401
|
+
segmentLengths[index] = length;
|
|
4402
|
+
totalLength += length;
|
|
4403
|
+
}
|
|
4404
|
+
if (totalLength <= 0) {
|
|
4405
|
+
const start = points[0];
|
|
4406
|
+
const end = points[1] ?? start;
|
|
4407
|
+
if (!start || !end)
|
|
4408
|
+
return undefined;
|
|
4409
|
+
return {
|
|
4410
|
+
point: start,
|
|
4411
|
+
angle: Math.atan2(end.y - start.y, end.x - start.x)
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4414
|
+
const targetLength = clamped * totalLength;
|
|
4415
|
+
let traversed = 0;
|
|
4416
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
4417
|
+
const start = points[index];
|
|
4418
|
+
const end = points[index + 1];
|
|
4419
|
+
const length = segmentLengths[index] ?? 0;
|
|
4420
|
+
if (!start || !end || length <= 0)
|
|
4421
|
+
continue;
|
|
4422
|
+
if (targetLength <= traversed + length || index === points.length - 2) {
|
|
4423
|
+
const localT = Math.min(1, Math.max(0, (targetLength - traversed) / length));
|
|
4424
|
+
return {
|
|
4425
|
+
point: {
|
|
4426
|
+
x: start.x + (end.x - start.x) * localT,
|
|
4427
|
+
y: start.y + (end.y - start.y) * localT
|
|
4428
|
+
},
|
|
4429
|
+
angle: Math.atan2(end.y - start.y, end.x - start.x)
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
traversed += length;
|
|
4433
|
+
}
|
|
4434
|
+
const last = points[points.length - 1];
|
|
4435
|
+
const previous = points[points.length - 2] ?? last;
|
|
4436
|
+
if (!last || !previous)
|
|
4437
|
+
return undefined;
|
|
4438
|
+
return {
|
|
4439
|
+
point: last,
|
|
4440
|
+
angle: Math.atan2(last.y - previous.y, last.x - previous.x)
|
|
4441
|
+
};
|
|
4442
|
+
}
|
|
4443
|
+
function roundGamePresentation(value) {
|
|
4444
|
+
return Math.round(value * 1000) / 1000;
|
|
4445
|
+
}
|
|
3241
4446
|
export const game = {
|
|
3242
4447
|
createRuntime: createAuraGameRuntime,
|
|
3243
4448
|
rules: gameRules,
|
|
@@ -3258,8 +4463,39 @@ export const game = {
|
|
|
3258
4463
|
simulation: createGameSimulation,
|
|
3259
4464
|
runSimulation: runGameSimulation,
|
|
3260
4465
|
inspector: createGameInspector,
|
|
4466
|
+
locomotion: createGameLocomotionKit,
|
|
4467
|
+
assetBoundPlatformerLevel: createGameAssetBoundPlatformerLevel,
|
|
4468
|
+
platformerSceneBinding: createGamePlatformerSceneBinding,
|
|
4469
|
+
platformerPresentationCamera: createGamePlatformerPresentationCamera,
|
|
4470
|
+
platformerPresentationSurfaces: createGamePlatformerPresentationSurfaceNodes,
|
|
4471
|
+
publicPlatformerPresentation: createGamePublicPlatformerPresentationNodes,
|
|
4472
|
+
platformerGroundMesh: createGamePlatformerGroundMeshNodes,
|
|
4473
|
+
platformerPlatformMesh: createGamePlatformerPlatformMeshNodes,
|
|
4474
|
+
platformerHazard: createGamePlatformerHazardNodes,
|
|
4475
|
+
platformerCheckpoint: createGamePlatformerCheckpointNodes,
|
|
4476
|
+
platformerFinish: createGamePlatformerFinishNodes,
|
|
4477
|
+
platformerCameraRig: createGamePlatformerCameraRig,
|
|
4478
|
+
certifyPlatformerGeometry: certifyPublicPlatformerGeometry,
|
|
4479
|
+
certifyPlatformerPresentation: certifyPublicPlatformerPresentation,
|
|
4480
|
+
platformer: createGamePlatformerKit,
|
|
4481
|
+
assetBoundRacingRoute: createGameAssetBoundRacingRoute,
|
|
4482
|
+
racingSceneBinding: createGameRacingSceneBinding,
|
|
4483
|
+
racingPresentationCamera: createGameRacingPresentationCamera,
|
|
4484
|
+
racingCameraRig: createGameRacingCameraRig,
|
|
4485
|
+
racingRoadMesh: createGameRacingRoadMeshNodes,
|
|
4486
|
+
racingCheckpointGate: createGameRacingCheckpointGateNodes,
|
|
4487
|
+
racingStartFinish: createGameRacingStartFinishNodes,
|
|
4488
|
+
racingPresentationTrack: createGameRacingPresentationTrackNodes,
|
|
4489
|
+
publicRacingPresentation: createGamePublicRacingPresentationNodes,
|
|
4490
|
+
certifyRacingGeometry: certifyPublicRacingGeometry,
|
|
4491
|
+
certifyRacingPresentation: certifyPublicRacingPresentation,
|
|
4492
|
+
racing: createGameRacingKit,
|
|
4493
|
+
fallingBlocks: createGameFallingBlocksKit,
|
|
4494
|
+
fallingBlockPieces: GAME_FALLING_BLOCK_PIECES,
|
|
4495
|
+
eventLog: createGameEventLog,
|
|
3261
4496
|
touchControls: createGameTouchControlLayout,
|
|
3262
4497
|
kinematicBody: createGameKinematicBody,
|
|
4498
|
+
collisionWorld: createGameCollisionWorld,
|
|
3263
4499
|
jumpAssist: createGameJumpAssist,
|
|
3264
4500
|
collider: {
|
|
3265
4501
|
box: createGameBoxCollider,
|
|
@@ -3292,6 +4528,12 @@ export const game = {
|
|
|
3292
4528
|
timer: createGameHudTimerBinding,
|
|
3293
4529
|
combo: createGameHudComboBinding,
|
|
3294
4530
|
round: createGameHudRoundBinding,
|
|
4531
|
+
score: createGameHudScoreBinding,
|
|
4532
|
+
lives: createGameHudLivesBinding,
|
|
4533
|
+
objective: createGameHudObjectiveBinding,
|
|
4534
|
+
checkpoint: createGameHudCheckpointBinding,
|
|
4535
|
+
value: createGameHudValueBinding,
|
|
4536
|
+
eventLog: createGameHudEventLogBinding,
|
|
3295
4537
|
debugToggle: createGameHudDebugToggleBinding,
|
|
3296
4538
|
bindings: createGameHudBindings,
|
|
3297
4539
|
snapshot: createGameHudSnapshot
|
|
@@ -5047,6 +6289,8 @@ export function createRuntimeNodeImportedAssetEvidence(input) {
|
|
|
5047
6289
|
}
|
|
5048
6290
|
} : {}),
|
|
5049
6291
|
morphTargets,
|
|
6292
|
+
activeMorphTargets: { ...(input.activeMorphTargets ?? {}) },
|
|
6293
|
+
missingMorphTargets: [...(input.missingMorphTargets ?? [])],
|
|
5050
6294
|
...(input.bounds ? { bounds: isRuntimeNodeEvidenceBounds(input.bounds) ? input.bounds : calculateRuntimeNodeBounds(input.bounds) } : {}),
|
|
5051
6295
|
renderItemCount: input.renderItemCount ?? 0,
|
|
5052
6296
|
skinnedRenderItemCount: input.skinnedRenderItemCount ?? 0,
|
|
@@ -5297,6 +6541,8 @@ function cloneRuntimeImportedAssetEvidence(evidence) {
|
|
|
5297
6541
|
clips: [...evidence.clips],
|
|
5298
6542
|
skinningPalette: evidence.skinningPalette ? { ...evidence.skinningPalette } : undefined,
|
|
5299
6543
|
morphTargets: [...evidence.morphTargets],
|
|
6544
|
+
activeMorphTargets: { ...evidence.activeMorphTargets },
|
|
6545
|
+
missingMorphTargets: [...evidence.missingMorphTargets],
|
|
5300
6546
|
bounds: evidence.bounds
|
|
5301
6547
|
? {
|
|
5302
6548
|
...evidence.bounds,
|
|
@@ -5320,10 +6566,11 @@ function sanitizeRuntimeMorphWeight(weight) {
|
|
|
5320
6566
|
export function createAuraApp(target, options) {
|
|
5321
6567
|
let snapshot = normalizeSceneSnapshot(options.scene);
|
|
5322
6568
|
let renderSnapshot = flattenSceneSnapshot(snapshot);
|
|
5323
|
-
const
|
|
6569
|
+
const rendererSelection = normalizeCreateAppRendererOptions(options.renderer);
|
|
6570
|
+
const diagnosticsState = createInitialDiagnostics(renderSnapshot, options.renderer);
|
|
5324
6571
|
const canvas = resolveCanvas(target);
|
|
5325
6572
|
if (canvas) {
|
|
5326
|
-
configureCanvas(canvas, options.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
|
|
6573
|
+
configureCanvas(canvas, options.pixelRatio ?? rendererSelection.profile.pixelRatio ?? devicePixelRatioSafe(), options.resize ?? true);
|
|
5327
6574
|
}
|
|
5328
6575
|
const overlay = canvas && shouldRenderOverlay(options.diagnostics, snapshot) ? createDiagnosticsOverlay(canvas, diagnosticsState) : undefined;
|
|
5329
6576
|
let disposed = false;
|
|
@@ -5360,7 +6607,7 @@ export function createAuraApp(target, options) {
|
|
|
5360
6607
|
};
|
|
5361
6608
|
const shouldUseProductionRendererForCurrentScene = () => Boolean(canvas && renderSnapshot.nodes.some(isWebGLRenderableNode) && typeof window !== "undefined");
|
|
5362
6609
|
const resetDiagnosticsForCurrentScene = (backend) => {
|
|
5363
|
-
const fresh = createInitialDiagnostics(renderSnapshot);
|
|
6610
|
+
const fresh = createInitialDiagnostics(renderSnapshot, options.renderer);
|
|
5364
6611
|
diagnosticsState.backend = backend;
|
|
5365
6612
|
diagnosticsState.fps = fresh.fps;
|
|
5366
6613
|
diagnosticsState.drawCalls = fresh.drawCalls;
|
|
@@ -5409,7 +6656,7 @@ export function createAuraApp(target, options) {
|
|
|
5409
6656
|
overlay?.update();
|
|
5410
6657
|
const revision = ++mountRevision;
|
|
5411
6658
|
if (shouldUseProductionRenderer && canvas) {
|
|
5412
|
-
void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused)
|
|
6659
|
+
void startProductionRender(canvas, renderSnapshot, diagnosticsState, options, overlay, runRuntimeFrame, () => runtimePaused, runtimeNodes)
|
|
5413
6660
|
.then((controller) => {
|
|
5414
6661
|
if (disposed || revision !== mountRevision) {
|
|
5415
6662
|
controller.dispose();
|
|
@@ -5737,12 +6984,12 @@ function isRenderableModelNode(node) {
|
|
|
5737
6984
|
function isWebGLRenderableNode(node) {
|
|
5738
6985
|
return isRenderableModelNode(node) || node.kind === "primitive";
|
|
5739
6986
|
}
|
|
5740
|
-
async function startProductionRender(canvas, snapshot, diagnosticsState, options, overlay, beforeRender, isPaused = () => false) {
|
|
6987
|
+
async function startProductionRender(canvas, snapshot, diagnosticsState, options, overlay, beforeRender, isPaused = () => false, runtimeNodes) {
|
|
5741
6988
|
const renderableNode = snapshot.nodes.find(isWebGLRenderableNode);
|
|
5742
6989
|
if (!renderableNode) {
|
|
5743
6990
|
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().");
|
|
5744
6991
|
}
|
|
5745
|
-
const renderer = await createProductionSceneRenderer(canvas, snapshot);
|
|
6992
|
+
const renderer = await createProductionSceneRenderer(canvas, snapshot, options.renderer, runtimeNodes);
|
|
5746
6993
|
diagnosticsState.renderer = renderer.diagnostics;
|
|
5747
6994
|
const continuousRender = shouldContinuouslyRender(snapshot);
|
|
5748
6995
|
let disposed = false;
|
|
@@ -5756,7 +7003,7 @@ async function startProductionRender(canvas, snapshot, diagnosticsState, options
|
|
|
5756
7003
|
if (!isPaused())
|
|
5757
7004
|
beforeRender?.(delta / 1000, "raf");
|
|
5758
7005
|
const drawCalls = renderer.render(time);
|
|
5759
|
-
diagnosticsState.backend =
|
|
7006
|
+
diagnosticsState.backend = renderer.backend;
|
|
5760
7007
|
diagnosticsState.fps = diagnosticsState.fps || 60;
|
|
5761
7008
|
diagnosticsState.drawCalls = drawCalls;
|
|
5762
7009
|
diagnosticsState.renderSize = [canvas.width, canvas.height];
|
|
@@ -5795,8 +7042,494 @@ function shouldContinuouslyRender(snapshot) {
|
|
|
5795
7042
|
return node.effect === "particles" || node.effect === "rain";
|
|
5796
7043
|
});
|
|
5797
7044
|
}
|
|
5798
|
-
async function createProductionSceneRenderer(canvas, snapshot) {
|
|
5799
|
-
|
|
7045
|
+
async function createProductionSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes) {
|
|
7046
|
+
const rendererSelection = normalizeCreateAppRendererOptions(rendererOptions);
|
|
7047
|
+
if (rendererSelection.mode !== "production") {
|
|
7048
|
+
return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [], runtimeNodes);
|
|
7049
|
+
}
|
|
7050
|
+
const flattened = groups.flatten(snapshot.nodes);
|
|
7051
|
+
const eligibility = analyzeProductionBridgeEligibility(flattened);
|
|
7052
|
+
if (!eligibility.eligible) {
|
|
7053
|
+
return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [
|
|
7054
|
+
`Production bridge skipped: ${eligibility.reasons.join("; ")}.`
|
|
7055
|
+
], runtimeNodes);
|
|
7056
|
+
}
|
|
7057
|
+
try {
|
|
7058
|
+
return await createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes);
|
|
7059
|
+
}
|
|
7060
|
+
catch (error) {
|
|
7061
|
+
return await createWebGLSceneRenderer(canvas, snapshot, rendererOptions, [
|
|
7062
|
+
`Production bridge failed and safe-basic fallback rendered instead: ${productionRenderErrorMessage(error)}`
|
|
7063
|
+
], runtimeNodes);
|
|
7064
|
+
}
|
|
7065
|
+
}
|
|
7066
|
+
const PRODUCTION_RUNTIME_POSTPROCESS = {
|
|
7067
|
+
targetFormat: "rgba8",
|
|
7068
|
+
toneMapping: {
|
|
7069
|
+
exposure: 1.14,
|
|
7070
|
+
whitePoint: 1.18,
|
|
7071
|
+
operator: "filmic",
|
|
7072
|
+
inputColorSpace: "linear",
|
|
7073
|
+
outputColorSpace: "srgb"
|
|
7074
|
+
},
|
|
7075
|
+
colorGrade: {
|
|
7076
|
+
contrast: 1.1,
|
|
7077
|
+
saturation: 1.06,
|
|
7078
|
+
vibrance: 0.12,
|
|
7079
|
+
vignette: 0.08,
|
|
7080
|
+
sharpening: 0.24
|
|
7081
|
+
},
|
|
7082
|
+
fxaa: {
|
|
7083
|
+
edgeThreshold: 0.08,
|
|
7084
|
+
subpixelBlend: 0.55
|
|
7085
|
+
}
|
|
7086
|
+
};
|
|
7087
|
+
const PRODUCTION_RUNTIME_SHADOWS = {
|
|
7088
|
+
enabled: true,
|
|
7089
|
+
size: 1024,
|
|
7090
|
+
bias: 0.0014,
|
|
7091
|
+
strength: 0.28,
|
|
7092
|
+
pcfRadius: 1.2,
|
|
7093
|
+
pcfSamples: 9,
|
|
7094
|
+
filter: "pcf",
|
|
7095
|
+
label: "aura3d-root-production-shadow-map"
|
|
7096
|
+
};
|
|
7097
|
+
let cachedProductionRuntimeLights;
|
|
7098
|
+
function createProductionRuntimeEnvironment(snapshot) {
|
|
7099
|
+
const names = groups.flatten(snapshot.nodes).map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
|
|
7100
|
+
const category = resolveRendererSceneCategory(snapshot, names);
|
|
7101
|
+
const preset = category === "city-day" ? "daylight"
|
|
7102
|
+
: category === "city-night" ? "evening"
|
|
7103
|
+
: category === "game" ? "gameplay"
|
|
7104
|
+
: category === "material" ? "inspection"
|
|
7105
|
+
: "studio";
|
|
7106
|
+
const bundle = createExternalParityEnvironmentLighting(preset);
|
|
7107
|
+
return {
|
|
7108
|
+
preset,
|
|
7109
|
+
intensity: bundle.lighting.environmentMapIntensity ?? bundle.lighting.intensity,
|
|
7110
|
+
evidence: `production bridge submitted generated HDR ${preset} environment lighting through RenderSource.environmentLighting`,
|
|
7111
|
+
lighting: bundle.lighting
|
|
7112
|
+
};
|
|
7113
|
+
}
|
|
7114
|
+
function createProductionRuntimePostprocess() {
|
|
7115
|
+
return PRODUCTION_RUNTIME_POSTPROCESS;
|
|
7116
|
+
}
|
|
7117
|
+
function createProductionRuntimeShadowOptions() {
|
|
7118
|
+
return PRODUCTION_RUNTIME_SHADOWS;
|
|
7119
|
+
}
|
|
7120
|
+
function createProductionRuntimePostprocessObservation(diagnostics) {
|
|
7121
|
+
const actualPasses = diagnostics.postprocessPassNames ?? [];
|
|
7122
|
+
const pixelBacked = actualPasses.length > 0 && diagnostics.lastError === null;
|
|
7123
|
+
return {
|
|
7124
|
+
renderPass: actualPasses.length > 0,
|
|
7125
|
+
outputPass: pixelBacked,
|
|
7126
|
+
bloomPass: actualPasses.includes("bloom"),
|
|
7127
|
+
ambientOcclusionPass: actualPasses.includes("ssao"),
|
|
7128
|
+
contactOcclusionReceiver: actualPasses.includes("contact-shadow"),
|
|
7129
|
+
pixelBacked,
|
|
7130
|
+
actualPasses,
|
|
7131
|
+
fallbackPasses: pixelBacked ? [] : ["direct-render"]
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
function createProductionRuntimeCollectedLights() {
|
|
7135
|
+
if (cachedProductionRuntimeLights)
|
|
7136
|
+
return cachedProductionRuntimeLights;
|
|
7137
|
+
const key = new DirectionalLight("aura3d-root-production-key-shadow");
|
|
7138
|
+
const fill = new DirectionalLight("aura3d-root-production-fill");
|
|
7139
|
+
const rim = new DirectionalLight("aura3d-root-production-rim");
|
|
7140
|
+
key.castsShadow = true;
|
|
7141
|
+
key.color = [1, 0.94, 0.82];
|
|
7142
|
+
key.intensity = 2.6;
|
|
7143
|
+
fill.color = [0.52, 0.64, 0.9];
|
|
7144
|
+
fill.intensity = 0.64;
|
|
7145
|
+
rim.color = [0.88, 0.94, 1];
|
|
7146
|
+
rim.intensity = 0.88;
|
|
7147
|
+
cachedProductionRuntimeLights = [
|
|
7148
|
+
{
|
|
7149
|
+
kind: "directional",
|
|
7150
|
+
color: [1, 0.94, 0.82],
|
|
7151
|
+
intensity: 2.6,
|
|
7152
|
+
position: [0, 0, 0],
|
|
7153
|
+
direction: [0.44, -0.64, -0.63],
|
|
7154
|
+
range: 0,
|
|
7155
|
+
spotAngle: 0,
|
|
7156
|
+
penumbra: 0,
|
|
7157
|
+
castsShadow: true,
|
|
7158
|
+
layerMask: 0xffffffff,
|
|
7159
|
+
source: key
|
|
7160
|
+
},
|
|
7161
|
+
{
|
|
7162
|
+
kind: "directional",
|
|
7163
|
+
color: [0.52, 0.64, 0.9],
|
|
7164
|
+
intensity: 0.64,
|
|
7165
|
+
position: [0, 0, 0],
|
|
7166
|
+
direction: [-0.5, -0.28, -0.82],
|
|
7167
|
+
range: 0,
|
|
7168
|
+
spotAngle: 0,
|
|
7169
|
+
penumbra: 0,
|
|
7170
|
+
castsShadow: false,
|
|
7171
|
+
layerMask: 0xffffffff,
|
|
7172
|
+
source: fill
|
|
7173
|
+
},
|
|
7174
|
+
{
|
|
7175
|
+
kind: "directional",
|
|
7176
|
+
color: [0.88, 0.94, 1],
|
|
7177
|
+
intensity: 0.88,
|
|
7178
|
+
position: [0, 0, 0],
|
|
7179
|
+
direction: [-0.24, -0.36, 0.9],
|
|
7180
|
+
range: 0,
|
|
7181
|
+
spotAngle: 0,
|
|
7182
|
+
penumbra: 0,
|
|
7183
|
+
castsShadow: false,
|
|
7184
|
+
layerMask: 0xffffffff,
|
|
7185
|
+
source: rim
|
|
7186
|
+
}
|
|
7187
|
+
];
|
|
7188
|
+
return cachedProductionRuntimeLights;
|
|
7189
|
+
}
|
|
7190
|
+
async function createProductionRuntimeSceneRenderer(canvas, snapshot, rendererOptions, runtimeNodes) {
|
|
7191
|
+
const flattened = groups.flatten(snapshot.nodes);
|
|
7192
|
+
const modelNodes = flattened.filter((node) => isRenderableModelNode(node) && createAssetProvenance(node.asset).source === "typed-aura-assets-manifest");
|
|
7193
|
+
const actorEntries = await Promise.all(modelNodes.map(async (node, index) => ({
|
|
7194
|
+
node,
|
|
7195
|
+
actor: await createTypedGLBActor({
|
|
7196
|
+
asset: node.asset,
|
|
7197
|
+
id: node.runtime?.id ?? node.asset.id ?? `model-${index + 1}`,
|
|
7198
|
+
name: node.name ?? node.asset.id ?? `model-${index + 1}`,
|
|
7199
|
+
width: canvas.width,
|
|
7200
|
+
height: canvas.height,
|
|
7201
|
+
...(node.material?.color ? { tint: { baseColor: colorToRgba(node.material.color) } } : {})
|
|
7202
|
+
})
|
|
7203
|
+
})));
|
|
7204
|
+
const primitiveEntries = createProductionRuntimePrimitiveEntries(flattened);
|
|
7205
|
+
const productionRenderer = await ProductionRuntimeRenderer.create({
|
|
7206
|
+
canvas,
|
|
7207
|
+
width: canvas.width,
|
|
7208
|
+
height: canvas.height,
|
|
7209
|
+
backend: "webgl2",
|
|
7210
|
+
antialias: true,
|
|
7211
|
+
preserveDrawingBuffer: true,
|
|
7212
|
+
clearColor: colorToClearColor(snapshot.background)
|
|
7213
|
+
});
|
|
7214
|
+
let latestDeviceDiagnostics = productionRenderer.getDiagnostics();
|
|
7215
|
+
let latestFeatures = productionRenderer.getFeatures();
|
|
7216
|
+
const runtimeWarnings = new Set();
|
|
7217
|
+
const productionEnvironment = createProductionRuntimeEnvironment(snapshot);
|
|
7218
|
+
const buildDiagnostics = () => createRendererDiagnosticReport(snapshot, {
|
|
7219
|
+
mounted: true,
|
|
7220
|
+
backend: "production-runtime",
|
|
7221
|
+
postprocess: createProductionRuntimePostprocessObservation(latestDeviceDiagnostics),
|
|
7222
|
+
environment: {
|
|
7223
|
+
enabled: true,
|
|
7224
|
+
preset: productionEnvironment.preset,
|
|
7225
|
+
intensity: productionEnvironment.intensity,
|
|
7226
|
+
evidence: productionEnvironment.evidence
|
|
7227
|
+
},
|
|
7228
|
+
warnings: [
|
|
7229
|
+
`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}.`,
|
|
7230
|
+
...(flattened.some((node) => node.kind === "effect")
|
|
7231
|
+
? ["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."]
|
|
7232
|
+
: []),
|
|
7233
|
+
...(productionRenderer.backendSelection.fallback ? [`Production runtime backend fallback: ${productionRenderer.backendSelection.reason}`] : []),
|
|
7234
|
+
...latestFeatures
|
|
7235
|
+
.filter((feature) => feature.state !== "supported")
|
|
7236
|
+
.map((feature) => `Production runtime feature ${feature.id} is ${feature.state}: ${feature.detail}`),
|
|
7237
|
+
...runtimeWarnings
|
|
7238
|
+
]
|
|
7239
|
+
}, rendererOptions);
|
|
7240
|
+
return {
|
|
7241
|
+
get backend() {
|
|
7242
|
+
return productionRenderer.backend;
|
|
7243
|
+
},
|
|
7244
|
+
get diagnostics() {
|
|
7245
|
+
return buildDiagnostics();
|
|
7246
|
+
},
|
|
7247
|
+
render(time) {
|
|
7248
|
+
runtimeWarnings.clear();
|
|
7249
|
+
const input = createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, productionEnvironment.lighting);
|
|
7250
|
+
const result = productionRenderer.renderInteractiveFrame(input);
|
|
7251
|
+
latestDeviceDiagnostics = result.diagnostics;
|
|
7252
|
+
latestFeatures = result.features;
|
|
7253
|
+
return latestDeviceDiagnostics.drawCalls;
|
|
7254
|
+
},
|
|
7255
|
+
dispose() {
|
|
7256
|
+
productionRenderer.dispose();
|
|
7257
|
+
for (const { actor } of actorEntries)
|
|
7258
|
+
actor.pipeline.dispose();
|
|
7259
|
+
for (const { geometry, material } of primitiveEntries) {
|
|
7260
|
+
geometry.dispose();
|
|
7261
|
+
material.dispose();
|
|
7262
|
+
}
|
|
7263
|
+
}
|
|
7264
|
+
};
|
|
7265
|
+
}
|
|
7266
|
+
function createProductionRuntimeRendererInput(snapshot, canvas, actorEntries, primitiveEntries, time, runtimeNodes, runtimeWarnings, environmentLighting) {
|
|
7267
|
+
const items = [];
|
|
7268
|
+
for (const entry of actorEntries) {
|
|
7269
|
+
const currentState = resolveProductionActorRuntimeState(entry, runtimeNodes);
|
|
7270
|
+
const currentNode = currentState.node;
|
|
7271
|
+
if (currentNode.visible === false)
|
|
7272
|
+
continue;
|
|
7273
|
+
applyProductionActorAnimation(entry, currentNode, currentState.animationBinding, time, runtimeWarnings);
|
|
7274
|
+
applyProductionActorMorphTargets(entry, currentState.morphTargets, runtimeWarnings);
|
|
7275
|
+
const modelMatrix = [...createModelMatrix(currentNode, boundsFromAuraAsset(currentNode.asset), shouldNormalizeModelNode(currentNode), time)];
|
|
7276
|
+
const actorItems = entry.actor.collectRenderItems({ modelMatrix });
|
|
7277
|
+
items.push(...actorItems);
|
|
7278
|
+
attachProductionActorEvidence(currentNode, entry.actor, actorItems, runtimeNodes);
|
|
7279
|
+
}
|
|
7280
|
+
for (const entry of primitiveEntries) {
|
|
7281
|
+
const currentState = resolveProductionPrimitiveRuntimeState(entry, runtimeNodes);
|
|
7282
|
+
if (!currentState.visible)
|
|
7283
|
+
continue;
|
|
7284
|
+
items.push({
|
|
7285
|
+
geometry: entry.geometry,
|
|
7286
|
+
material: entry.material,
|
|
7287
|
+
modelMatrix: createModelMatrix(currentState.node, entry.bounds, false, time),
|
|
7288
|
+
label: currentState.node.name ?? `aura-primitive-${currentState.node.primitive}`,
|
|
7289
|
+
includeInAutoFrame: false
|
|
7290
|
+
});
|
|
7291
|
+
}
|
|
7292
|
+
const viewProjectionMatrix = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
|
|
7293
|
+
const cameraPosition = resolveCameraEye(snapshot, snapshot.camera, time, runtimeNodes);
|
|
7294
|
+
const source = {
|
|
7295
|
+
collectRenderItems: () => items,
|
|
7296
|
+
cameraPolicy: "require",
|
|
7297
|
+
staticBatching: false,
|
|
7298
|
+
frustumCulling: false,
|
|
7299
|
+
collectedLights: createProductionRuntimeCollectedLights(),
|
|
7300
|
+
environmentLighting,
|
|
7301
|
+
postprocess: createProductionRuntimePostprocess(),
|
|
7302
|
+
shadow: createProductionRuntimeShadowOptions(),
|
|
7303
|
+
cameraPosition
|
|
7304
|
+
};
|
|
7305
|
+
const cameraLike = { viewProjectionMatrix };
|
|
7306
|
+
return {
|
|
7307
|
+
source,
|
|
7308
|
+
camera: cameraLike,
|
|
7309
|
+
metadata: createProductionRuntimeMetadata(actorEntries)
|
|
7310
|
+
};
|
|
7311
|
+
}
|
|
7312
|
+
function createProductionRuntimePrimitiveEntries(nodes) {
|
|
7313
|
+
return nodes
|
|
7314
|
+
.filter((node) => node.kind === "primitive")
|
|
7315
|
+
.map((node) => ({
|
|
7316
|
+
node,
|
|
7317
|
+
geometry: createProductionPrimitiveGeometry(node),
|
|
7318
|
+
material: createProductionPrimitiveMaterial(node),
|
|
7319
|
+
bounds: primitiveGeometryBounds(node.primitive)
|
|
7320
|
+
}));
|
|
7321
|
+
}
|
|
7322
|
+
function resolveProductionPrimitiveRuntimeState(entry, runtimeNodes) {
|
|
7323
|
+
const runtimeId = entry.node.runtime?.id;
|
|
7324
|
+
if (!runtimeId)
|
|
7325
|
+
return { node: entry.node, visible: true };
|
|
7326
|
+
const runtimeSnapshot = runtimeNodes?.get(runtimeId)?.snapshot();
|
|
7327
|
+
if (!runtimeSnapshot)
|
|
7328
|
+
return { node: entry.node, visible: true };
|
|
7329
|
+
return {
|
|
7330
|
+
node: {
|
|
7331
|
+
...entry.node,
|
|
7332
|
+
position: runtimeSnapshot.position,
|
|
7333
|
+
rotation: runtimeSnapshot.rotation,
|
|
7334
|
+
scale: runtimeSnapshot.scale,
|
|
7335
|
+
animation: runtimeSnapshot.animation ?? entry.node.animation
|
|
7336
|
+
},
|
|
7337
|
+
visible: runtimeSnapshot.visible
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7340
|
+
function createProductionPrimitiveMaterial(node) {
|
|
7341
|
+
const materialSpec = node.material;
|
|
7342
|
+
const baseColor = colorToRgba(materialSpec?.color ?? materialSpec?.emissive ?? "#d7dee8");
|
|
7343
|
+
const opacity = clamp01(materialSpec?.opacity ?? baseColor[3] ?? 1);
|
|
7344
|
+
const emissiveColor = materialSpec?.emissive ? colorToRgb(materialSpec.emissive) : [0, 0, 0];
|
|
7345
|
+
return new PBRMaterial({
|
|
7346
|
+
name: `a3d-production-primitive-${node.primitive}${node.name ? `-${node.name}` : ""}`,
|
|
7347
|
+
baseColor: [baseColor[0], baseColor[1], baseColor[2], opacity],
|
|
7348
|
+
metallic: clamp01(materialSpec?.metallic ?? materialSpec?.metalness ?? 0),
|
|
7349
|
+
roughness: clamp01(materialSpec?.roughness ?? 0.58),
|
|
7350
|
+
emissiveColor,
|
|
7351
|
+
emissiveStrength: Math.max(0, materialSpec?.emissiveIntensity ?? (materialSpec?.emissive ? 1.35 : 0)),
|
|
7352
|
+
clearcoatFactor: clamp01(materialSpec?.clearcoat ?? 0),
|
|
7353
|
+
clearcoatRoughnessFactor: clamp01(materialSpec?.clearcoatRoughness ?? 0.34),
|
|
7354
|
+
environmentIntensity: Math.max(0, materialSpec?.envMapIntensity ?? 0.75),
|
|
7355
|
+
renderState: {
|
|
7356
|
+
blend: opacity < 0.999,
|
|
7357
|
+
depthWrite: opacity >= 0.999,
|
|
7358
|
+
cullMode: node.primitive === "plane" || opacity < 0.999 ? "none" : "back"
|
|
7359
|
+
}
|
|
7360
|
+
});
|
|
7361
|
+
}
|
|
7362
|
+
function createProductionPrimitiveGeometry(node) {
|
|
7363
|
+
return createProductionGeometryFromPrimitiveMesh(createProductionPrimitiveMesh(node.primitive));
|
|
7364
|
+
}
|
|
7365
|
+
function primitiveGeometryBounds(primitive) {
|
|
7366
|
+
return createProductionPrimitiveMesh(primitive).bounds;
|
|
7367
|
+
}
|
|
7368
|
+
function createProductionPrimitiveMesh(primitive) {
|
|
7369
|
+
if (primitive === "sphere")
|
|
7370
|
+
return createSphereGeometry();
|
|
7371
|
+
if (primitive === "capsule")
|
|
7372
|
+
return createCapsuleApproxGeometry();
|
|
7373
|
+
if (primitive === "torus")
|
|
7374
|
+
return createTorusGeometry();
|
|
7375
|
+
if (primitive === "box")
|
|
7376
|
+
return createBoxGeometry();
|
|
7377
|
+
if (primitive === "cylinder")
|
|
7378
|
+
return createCylinderGeometry();
|
|
7379
|
+
return createPlaneGeometry();
|
|
7380
|
+
}
|
|
7381
|
+
function createProductionGeometryFromPrimitiveMesh(mesh) {
|
|
7382
|
+
const vertexCount = mesh.positions.length / 3;
|
|
7383
|
+
const vertices = new VertexBuffer(VertexFormat.P3N3, vertexCount);
|
|
7384
|
+
for (let index = 0; index < vertexCount; index += 1) {
|
|
7385
|
+
const base = index * 3;
|
|
7386
|
+
vertices.setAttribute(index, "position", [mesh.positions[base] ?? 0, mesh.positions[base + 1] ?? 0, mesh.positions[base + 2] ?? 0]);
|
|
7387
|
+
vertices.setAttribute(index, "normal", [mesh.normals[base] ?? 0, mesh.normals[base + 1] ?? 1, mesh.normals[base + 2] ?? 0]);
|
|
7388
|
+
}
|
|
7389
|
+
return new Geometry(vertices, new IndexBuffer(Array.from(mesh.indices), vertexCount), "triangles", mesh.bounds);
|
|
7390
|
+
}
|
|
7391
|
+
function resolveProductionActorRuntimeState(entry, runtimeNodes) {
|
|
7392
|
+
const runtimeId = entry.node.runtime?.id;
|
|
7393
|
+
if (!runtimeId)
|
|
7394
|
+
return { node: entry.node };
|
|
7395
|
+
const runtimeSnapshot = runtimeNodes?.get(runtimeId)?.snapshot();
|
|
7396
|
+
if (!runtimeSnapshot)
|
|
7397
|
+
return { node: entry.node };
|
|
7398
|
+
const morphTargets = runtimeSnapshot.morphTargets ?? {};
|
|
7399
|
+
return {
|
|
7400
|
+
node: {
|
|
7401
|
+
...entry.node,
|
|
7402
|
+
position: runtimeSnapshot.position,
|
|
7403
|
+
rotation: runtimeSnapshot.rotation,
|
|
7404
|
+
scale: runtimeSnapshot.scale,
|
|
7405
|
+
visible: runtimeSnapshot.visible,
|
|
7406
|
+
animation: runtimeSnapshot.animation ?? entry.node.animation
|
|
7407
|
+
},
|
|
7408
|
+
animationBinding: runtimeSnapshot.animationBinding,
|
|
7409
|
+
...(Object.keys(morphTargets).length > 0 ? { morphTargets } : {})
|
|
7410
|
+
};
|
|
7411
|
+
}
|
|
7412
|
+
function applyProductionActorAnimation(entry, node, animationBinding, time, runtimeWarnings) {
|
|
7413
|
+
const animation = node.animation;
|
|
7414
|
+
if (!animation?.clip || isModelTransformAnimationClip(animation.clip))
|
|
7415
|
+
return;
|
|
7416
|
+
const clipName = entry.actor.animation.resolveClipName(animation.clip);
|
|
7417
|
+
if (!clipName) {
|
|
7418
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" has no clips for requested animation "${animation.clip}".`);
|
|
7419
|
+
return;
|
|
7420
|
+
}
|
|
7421
|
+
try {
|
|
7422
|
+
entry.actor.playClip(clipName, resolveProductionActorAnimationSeconds(animation, animationBinding, time));
|
|
7423
|
+
}
|
|
7424
|
+
catch (error) {
|
|
7425
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply clip "${clipName}": ${productionRenderErrorMessage(error)}`);
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
function applyProductionActorMorphTargets(entry, morphTargets, runtimeWarnings) {
|
|
7429
|
+
if (!morphTargets || Object.keys(morphTargets).length === 0)
|
|
7430
|
+
return;
|
|
7431
|
+
try {
|
|
7432
|
+
const result = entry.actor.applyMorphTargets(morphTargets);
|
|
7433
|
+
for (const missing of result.missingTargets) {
|
|
7434
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" has no morph target "${missing}".`);
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7437
|
+
catch (error) {
|
|
7438
|
+
runtimeWarnings.add(`Typed GLB actor "${entry.actor.id}" failed to apply morph targets: ${productionRenderErrorMessage(error)}`);
|
|
7439
|
+
}
|
|
7440
|
+
}
|
|
7441
|
+
function attachProductionActorEvidence(node, actor, renderItems, runtimeNodes) {
|
|
7442
|
+
if (!node.runtime?.id || !runtimeNodes?.has(node.runtime.id))
|
|
7443
|
+
return;
|
|
7444
|
+
const evidence = createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems);
|
|
7445
|
+
runtimeNodes.get(node.runtime.id)?.setImportedAssetEvidence(evidence);
|
|
7446
|
+
}
|
|
7447
|
+
function createRuntimeEvidenceFromTypedGLBActor(node, actor, renderItems) {
|
|
7448
|
+
const actorEvidence = actor.evidence;
|
|
7449
|
+
const skeletonBones = actor.pipeline.asset.skins.flatMap((skin) => skin.jointNames);
|
|
7450
|
+
const morphTargets = actor.pipeline.asset.meshes.flatMap((mesh) => mesh.morphTargets.map((target, index) => target.name ?? `${mesh.name}-morph-${index + 1}`));
|
|
7451
|
+
const morphApply = actorEvidence.lastMorphApply;
|
|
7452
|
+
return createRuntimeNodeImportedAssetEvidence({
|
|
7453
|
+
assetId: node.asset.id,
|
|
7454
|
+
...(node.runtime?.id ? { nodeId: node.runtime.id } : {}),
|
|
7455
|
+
skeletonBones,
|
|
7456
|
+
clips: actorEvidence.clips,
|
|
7457
|
+
...(actorEvidence.lastClip ? { activeClip: actorEvidence.lastClip } : {}),
|
|
7458
|
+
...(actorEvidence.skinningBindingCount > 0 ? {
|
|
7459
|
+
skinningPalette: {
|
|
7460
|
+
jointCount: actorEvidence.skinningBindingCount,
|
|
7461
|
+
matrixCount: actorEvidence.skinningBindingCount,
|
|
7462
|
+
updated: actorEvidence.lastSkinningPalettesUpdated > 0
|
|
7463
|
+
}
|
|
7464
|
+
} : {}),
|
|
7465
|
+
morphTargets,
|
|
7466
|
+
activeMorphTargets: morphApply?.activeWeights ?? {},
|
|
7467
|
+
missingMorphTargets: morphApply?.missingTargets ?? [],
|
|
7468
|
+
bounds: {
|
|
7469
|
+
position: node.position,
|
|
7470
|
+
scale: node.scale,
|
|
7471
|
+
size: node.asset.bounds
|
|
7472
|
+
},
|
|
7473
|
+
renderItemCount: renderItems.length,
|
|
7474
|
+
skinnedRenderItemCount: renderItems.filter((item) => item.skinning).length,
|
|
7475
|
+
morphRenderItemCount: renderItems.filter((item) => item.morphTargets && item.morphTargets.length > 0).length
|
|
7476
|
+
});
|
|
7477
|
+
}
|
|
7478
|
+
function createProductionRuntimeMetadata(actorEntries) {
|
|
7479
|
+
const metadata = actorEntries.map((entry) => entry.actor.pipeline.metadata);
|
|
7480
|
+
const primary = metadata[0];
|
|
7481
|
+
return {
|
|
7482
|
+
assetId: metadata.length === 1 ? primary?.assetId ?? "typed-glb" : `typed-glb-batch-${metadata.length}`,
|
|
7483
|
+
assetUri: metadata.length === 1 ? primary?.assetUri ?? "aura3d://typed-glb" : "aura3d://scene/typed-glb-batch",
|
|
7484
|
+
...(metadata.length === 1 && primary?.assetName ? { assetName: primary.assetName } : metadata.length > 1 ? { assetName: "Aura3D typed GLB scene batch" } : {}),
|
|
7485
|
+
meshCount: sumProductionMetadata(metadata, "meshCount"),
|
|
7486
|
+
primitiveCount: sumProductionMetadata(metadata, "primitiveCount"),
|
|
7487
|
+
materialCount: sumProductionMetadata(metadata, "materialCount"),
|
|
7488
|
+
textureCount: sumProductionMetadata(metadata, "textureCount"),
|
|
7489
|
+
imageCount: sumProductionMetadata(metadata, "imageCount"),
|
|
7490
|
+
animationCount: sumProductionMetadata(metadata, "animationCount"),
|
|
7491
|
+
skinCount: sumProductionMetadata(metadata, "skinCount"),
|
|
7492
|
+
morphTargetCount: sumProductionMetadata(metadata, "morphTargetCount"),
|
|
7493
|
+
extensionsUsed: [...new Set(metadata.flatMap((item) => item.extensionsUsed))]
|
|
7494
|
+
};
|
|
7495
|
+
}
|
|
7496
|
+
function sumProductionMetadata(metadata, key) {
|
|
7497
|
+
return metadata.reduce((total, item) => total + item[key], 0);
|
|
7498
|
+
}
|
|
7499
|
+
function boundsFromAuraAsset(asset) {
|
|
7500
|
+
const metadataBounds = asset.metadata?.boundsMetadata;
|
|
7501
|
+
const metadataMin = vec3FromReadonly(metadataBounds?.min);
|
|
7502
|
+
const metadataMax = vec3FromReadonly(metadataBounds?.max);
|
|
7503
|
+
if (metadataMin && metadataMax)
|
|
7504
|
+
return { min: metadataMin, max: metadataMax };
|
|
7505
|
+
const metadataSize = vec3FromReadonly(metadataBounds?.size);
|
|
7506
|
+
const metadataCenter = vec3FromReadonly(metadataBounds?.center);
|
|
7507
|
+
if (metadataSize) {
|
|
7508
|
+
const center = metadataCenter ?? [0, metadataSize[1] / 2, 0];
|
|
7509
|
+
return {
|
|
7510
|
+
min: [center[0] - metadataSize[0] / 2, center[1] - metadataSize[1] / 2, center[2] - metadataSize[2] / 2],
|
|
7511
|
+
max: [center[0] + metadataSize[0] / 2, center[1] + metadataSize[1] / 2, center[2] + metadataSize[2] / 2]
|
|
7512
|
+
};
|
|
7513
|
+
}
|
|
7514
|
+
const bounds = asset.bounds;
|
|
7515
|
+
if (!bounds || bounds.length < 3)
|
|
7516
|
+
return { min: [-0.5, 0, -0.5], max: [0.5, 1, 0.5] };
|
|
7517
|
+
const halfX = Math.max(0.001, bounds[0] / 2);
|
|
7518
|
+
const halfZ = Math.max(0.001, bounds[2] / 2);
|
|
7519
|
+
return {
|
|
7520
|
+
min: [-halfX, 0, -halfZ],
|
|
7521
|
+
max: [halfX, Math.max(0.001, bounds[1]), halfZ]
|
|
7522
|
+
};
|
|
7523
|
+
}
|
|
7524
|
+
function vec3FromReadonly(value) {
|
|
7525
|
+
if (!value || value.length < 3)
|
|
7526
|
+
return undefined;
|
|
7527
|
+
const vec = [Number(value[0]), Number(value[1]), Number(value[2])];
|
|
7528
|
+
return vec.every((component) => Number.isFinite(component)) ? vec : undefined;
|
|
7529
|
+
}
|
|
7530
|
+
function colorToRgba(color) {
|
|
7531
|
+
const [r, g, b, a] = colorToClearColor(color);
|
|
7532
|
+
return [r, g, b, a];
|
|
5800
7533
|
}
|
|
5801
7534
|
function colorToClearColor(color) {
|
|
5802
7535
|
if (typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color)) {
|
|
@@ -5838,6 +7571,16 @@ function resolveAnimationSeconds(animation, time) {
|
|
|
5838
7571
|
return rawSeconds;
|
|
5839
7572
|
return animation.loop === false ? Math.min(rawSeconds, duration) : rawSeconds % duration;
|
|
5840
7573
|
}
|
|
7574
|
+
function resolveProductionActorAnimationSeconds(animation, animationBinding, time) {
|
|
7575
|
+
if (!animation || animation.captureTime === undefined || !animationBinding?.controllerId) {
|
|
7576
|
+
return resolveAnimationSeconds(animation, time);
|
|
7577
|
+
}
|
|
7578
|
+
const duration = Math.max(0, animation.duration ?? 0);
|
|
7579
|
+
const phase = Math.max(0, animationBinding.captureTime ?? animationBinding.localTime ?? animation.captureTime);
|
|
7580
|
+
if (duration <= 0)
|
|
7581
|
+
return phase;
|
|
7582
|
+
return animation.loop === false ? Math.min(phase, duration) : phase % duration;
|
|
7583
|
+
}
|
|
5841
7584
|
function orbitAnimatedAngle(seconds, speed) {
|
|
5842
7585
|
return seconds * speed * 0.58;
|
|
5843
7586
|
}
|
|
@@ -5917,17 +7660,59 @@ function seededRange(index, salt, min, max) {
|
|
|
5917
7660
|
const normalized = value - Math.floor(value);
|
|
5918
7661
|
return min + (max - min) * normalized;
|
|
5919
7662
|
}
|
|
5920
|
-
function
|
|
7663
|
+
function findRuntimeCameraTarget(cameraSpec, runtimeNodes) {
|
|
7664
|
+
if (cameraSpec.mode !== "follow" || !cameraSpec.targetNode || !runtimeNodes)
|
|
7665
|
+
return undefined;
|
|
7666
|
+
const targetNode = cameraSpec.targetNode;
|
|
7667
|
+
const directHandle = runtimeNodes.get(targetNode);
|
|
7668
|
+
if (directHandle && directHandle.visible !== false)
|
|
7669
|
+
return { position: directHandle.position, rotation: directHandle.rotation };
|
|
7670
|
+
const namedHandle = runtimeNodes.all().find((handle) => handle.visible !== false && (handle.name === targetNode || handle.tags.includes(targetNode)));
|
|
7671
|
+
return namedHandle ? { position: namedHandle.position, rotation: namedHandle.rotation } : undefined;
|
|
7672
|
+
}
|
|
7673
|
+
function rotateVec3BySceneYaw(vector, yaw) {
|
|
7674
|
+
const cos = Math.cos(yaw);
|
|
7675
|
+
const sin = Math.sin(yaw);
|
|
7676
|
+
return [
|
|
7677
|
+
vector[0] * cos + vector[2] * sin,
|
|
7678
|
+
vector[1],
|
|
7679
|
+
-vector[0] * sin + vector[2] * cos
|
|
7680
|
+
];
|
|
7681
|
+
}
|
|
7682
|
+
function applyCameraOffset(cameraSpec, offset, target) {
|
|
7683
|
+
if (cameraSpec.offsetMode !== "target-yaw" || !target)
|
|
7684
|
+
return offset;
|
|
7685
|
+
return rotateVec3BySceneYaw(offset, target.rotation[1]);
|
|
7686
|
+
}
|
|
7687
|
+
function add3(left, right) {
|
|
7688
|
+
return [left[0] + right[0], left[1] + right[1], left[2] + right[2]];
|
|
7689
|
+
}
|
|
7690
|
+
function resolveRuntimeCameraTarget(cameraSpec, runtimeNodes) {
|
|
7691
|
+
return findRuntimeCameraTarget(cameraSpec, runtimeNodes);
|
|
7692
|
+
}
|
|
7693
|
+
function resolveCameraTarget(snapshot, cameraSpec, runtimeNodes) {
|
|
7694
|
+
const runtimeTarget = resolveRuntimeCameraTarget(cameraSpec, runtimeNodes);
|
|
7695
|
+
if (runtimeTarget) {
|
|
7696
|
+
const targetOffset = cameraSpec.targetOffset
|
|
7697
|
+
? applyCameraOffset(cameraSpec, cameraSpec.targetOffset, runtimeTarget)
|
|
7698
|
+
: [0, 0, 0];
|
|
7699
|
+
return add3(runtimeTarget.position, targetOffset);
|
|
7700
|
+
}
|
|
5921
7701
|
if (cameraSpec.mode === "follow" && cameraSpec.targetNode) {
|
|
5922
|
-
const targetNode = snapshot.nodes.find((node) => (node.kind === "model" || node.kind === "primitive") &&
|
|
5923
|
-
(node.name === cameraSpec.targetNode ||
|
|
5924
|
-
|
|
5925
|
-
|
|
7702
|
+
const targetNode = groups.flatten(snapshot.nodes).find((node) => (node.kind === "model" || node.kind === "primitive") &&
|
|
7703
|
+
(node.name === cameraSpec.targetNode ||
|
|
7704
|
+
(node.kind === "model" && node.asset.id === cameraSpec.targetNode) ||
|
|
7705
|
+
node.runtime?.id === cameraSpec.targetNode));
|
|
7706
|
+
if (targetNode?.position) {
|
|
7707
|
+
const targetOffset = cameraSpec.targetOffset ?? [0, 0, 0];
|
|
7708
|
+
return add3(targetNode.position, targetOffset);
|
|
7709
|
+
}
|
|
5926
7710
|
}
|
|
5927
7711
|
return cameraSpec.target ?? [0, 0.7, 0];
|
|
5928
7712
|
}
|
|
5929
|
-
function resolveCameraEye(snapshot, cameraSpec, time) {
|
|
5930
|
-
const target = resolveCameraTarget(snapshot, cameraSpec);
|
|
7713
|
+
function resolveCameraEye(snapshot, cameraSpec, time, runtimeNodes) {
|
|
7714
|
+
const target = resolveCameraTarget(snapshot, cameraSpec, runtimeNodes);
|
|
7715
|
+
const runtimeTarget = resolveRuntimeCameraTarget(cameraSpec, runtimeNodes);
|
|
5931
7716
|
let eye = cameraSpec.position ?? [0, 1.4, cameraSpec.distance ?? 4];
|
|
5932
7717
|
if (cameraSpec.mode === "orbit") {
|
|
5933
7718
|
const distance = cameraSpec.distance ?? 4;
|
|
@@ -5935,7 +7720,11 @@ function resolveCameraEye(snapshot, cameraSpec, time) {
|
|
|
5935
7720
|
}
|
|
5936
7721
|
if (cameraSpec.mode === "follow") {
|
|
5937
7722
|
const distance = cameraSpec.distance ?? 4;
|
|
5938
|
-
|
|
7723
|
+
const offset = cameraSpec.offset ? applyCameraOffset(cameraSpec, cameraSpec.offset, runtimeTarget) : undefined;
|
|
7724
|
+
eye = cameraSpec.position
|
|
7725
|
+
?? (offset
|
|
7726
|
+
? [target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]]
|
|
7727
|
+
: [target[0] - distance * 0.38, target[1] + distance * 0.52, target[2] + distance * 0.82]);
|
|
5939
7728
|
}
|
|
5940
7729
|
if (cameraSpec.mode === "dolly") {
|
|
5941
7730
|
const seconds = cameraSpec.seconds ?? 6;
|
|
@@ -5962,7 +7751,7 @@ function collectRuntimeEffectNodes(snapshot) {
|
|
|
5962
7751
|
function hasRuntimePostProcessEffects(effectNodes) {
|
|
5963
7752
|
return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
|
|
5964
7753
|
}
|
|
5965
|
-
async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
7754
|
+
async function createWebGLSceneRenderer(canvas, snapshot, rendererOptions, runtimeWarnings = [], runtimeNodes) {
|
|
5966
7755
|
const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
|
|
5967
7756
|
if (!gl) {
|
|
5968
7757
|
throw new AuraRuntimeError("backend-fallback", "Aura3D could not create a WebGL2 renderer. Suggested fix: use a WebGL2-capable browser.");
|
|
@@ -5998,9 +7787,13 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
5998
7787
|
actualPasses: [],
|
|
5999
7788
|
fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
|
|
6000
7789
|
},
|
|
6001
|
-
warnings: [
|
|
6002
|
-
|
|
7790
|
+
warnings: [
|
|
7791
|
+
"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.",
|
|
7792
|
+
...runtimeWarnings
|
|
7793
|
+
]
|
|
7794
|
+
}, rendererOptions);
|
|
6003
7795
|
return {
|
|
7796
|
+
backend: "webgl2",
|
|
6004
7797
|
diagnostics: runtimeRendererDiagnostics,
|
|
6005
7798
|
render(time) {
|
|
6006
7799
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
@@ -6009,14 +7802,23 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
6009
7802
|
let drawCalls = backdrop.render();
|
|
6010
7803
|
gl.useProgram(program.program);
|
|
6011
7804
|
gl.enable(gl.DEPTH_TEST);
|
|
6012
|
-
const viewProjection = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time);
|
|
7805
|
+
const viewProjection = createViewProjection(snapshot, canvas.width / Math.max(1, canvas.height), time, runtimeNodes);
|
|
6013
7806
|
gl.uniformMatrix4fv(program.uniforms.viewProjection, false, viewProjection);
|
|
6014
7807
|
gl.uniform3fv(program.uniforms.lightDirection, new Float32Array(normalize3([0.45, 0.82, 0.36])));
|
|
7808
|
+
gl.uniform1i(program.uniforms.baseColorTexture, 0);
|
|
7809
|
+
gl.uniform1i(program.uniforms.metallicRoughnessTexture, 1);
|
|
7810
|
+
gl.uniform1i(program.uniforms.occlusionTexture, 2);
|
|
7811
|
+
gl.uniform1i(program.uniforms.emissiveTexture, 3);
|
|
6015
7812
|
for (const modelEntry of models) {
|
|
7813
|
+
modelEntry.update?.(time);
|
|
6016
7814
|
const modelMatrix = modelEntry.modelMatrix ?? createModelMatrix(modelEntry.node, modelEntry.bounds, modelEntry.normalizeToUnit, time);
|
|
6017
|
-
gl.uniformMatrix4fv(program.uniforms.model, false, modelMatrix);
|
|
6018
7815
|
for (const primitiveEntry of modelEntry.primitives) {
|
|
7816
|
+
const primitiveMatrix = primitiveEntry.modelMatrix ? multiply4(modelMatrix, primitiveEntry.modelMatrix(time)) : modelMatrix;
|
|
7817
|
+
gl.uniformMatrix4fv(program.uniforms.model, false, primitiveMatrix);
|
|
6019
7818
|
gl.uniform3fv(program.uniforms.color, new Float32Array(primitiveEntry.color ?? modelEntry.color));
|
|
7819
|
+
gl.uniform1f(program.uniforms.metallic, primitiveEntry.metallic ?? 0);
|
|
7820
|
+
gl.uniform1f(program.uniforms.roughness, primitiveEntry.roughness ?? 0.72);
|
|
7821
|
+
gl.uniform3fv(program.uniforms.emissive, new Float32Array(primitiveEntry.emissive ?? [0, 0, 0]));
|
|
6020
7822
|
gl.bindBuffer(gl.ARRAY_BUFFER, primitiveEntry.position);
|
|
6021
7823
|
gl.enableVertexAttribArray(program.attributes.position);
|
|
6022
7824
|
gl.vertexAttribPointer(program.attributes.position, 3, gl.FLOAT, false, 0, 0);
|
|
@@ -6032,6 +7834,55 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
6032
7834
|
gl.disableVertexAttribArray(program.attributes.color);
|
|
6033
7835
|
gl.vertexAttrib3f(program.attributes.color, 1, 1, 1);
|
|
6034
7836
|
}
|
|
7837
|
+
if (primitiveEntry.uv) {
|
|
7838
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, primitiveEntry.uv);
|
|
7839
|
+
gl.enableVertexAttribArray(program.attributes.uv);
|
|
7840
|
+
gl.vertexAttribPointer(program.attributes.uv, 2, gl.FLOAT, false, 0, 0);
|
|
7841
|
+
}
|
|
7842
|
+
else {
|
|
7843
|
+
gl.disableVertexAttribArray(program.attributes.uv);
|
|
7844
|
+
gl.vertexAttrib2f(program.attributes.uv, 0, 0);
|
|
7845
|
+
}
|
|
7846
|
+
if (primitiveEntry.texture) {
|
|
7847
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
7848
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.texture);
|
|
7849
|
+
gl.uniform1i(program.uniforms.useTexture, 1);
|
|
7850
|
+
}
|
|
7851
|
+
else {
|
|
7852
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
7853
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7854
|
+
gl.uniform1i(program.uniforms.useTexture, 0);
|
|
7855
|
+
}
|
|
7856
|
+
if (primitiveEntry.metallicRoughnessTexture) {
|
|
7857
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
7858
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.metallicRoughnessTexture);
|
|
7859
|
+
gl.uniform1i(program.uniforms.useMetallicRoughnessTexture, 1);
|
|
7860
|
+
}
|
|
7861
|
+
else {
|
|
7862
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
7863
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7864
|
+
gl.uniform1i(program.uniforms.useMetallicRoughnessTexture, 0);
|
|
7865
|
+
}
|
|
7866
|
+
if (primitiveEntry.occlusionTexture) {
|
|
7867
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
7868
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.occlusionTexture);
|
|
7869
|
+
gl.uniform1i(program.uniforms.useOcclusionTexture, 1);
|
|
7870
|
+
}
|
|
7871
|
+
else {
|
|
7872
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
7873
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7874
|
+
gl.uniform1i(program.uniforms.useOcclusionTexture, 0);
|
|
7875
|
+
}
|
|
7876
|
+
if (primitiveEntry.emissiveTexture) {
|
|
7877
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
7878
|
+
gl.bindTexture(gl.TEXTURE_2D, primitiveEntry.emissiveTexture);
|
|
7879
|
+
gl.uniform1i(program.uniforms.useEmissiveTexture, 1);
|
|
7880
|
+
}
|
|
7881
|
+
else {
|
|
7882
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
7883
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
7884
|
+
gl.uniform1i(program.uniforms.useEmissiveTexture, 0);
|
|
7885
|
+
}
|
|
6035
7886
|
if (primitiveEntry.index && primitiveEntry.indexType) {
|
|
6036
7887
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, primitiveEntry.index);
|
|
6037
7888
|
gl.drawElements(primitiveEntry.mode, primitiveEntry.count, primitiveEntry.indexType, 0);
|
|
@@ -6046,12 +7897,25 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
|
|
|
6046
7897
|
},
|
|
6047
7898
|
dispose() {
|
|
6048
7899
|
for (const modelEntry of models) {
|
|
7900
|
+
const textures = new Set();
|
|
6049
7901
|
for (const primitiveEntry of modelEntry.primitives) {
|
|
6050
7902
|
gl.deleteBuffer(primitiveEntry.position);
|
|
6051
7903
|
gl.deleteBuffer(primitiveEntry.normal);
|
|
7904
|
+
if (primitiveEntry.uv)
|
|
7905
|
+
gl.deleteBuffer(primitiveEntry.uv);
|
|
6052
7906
|
if (primitiveEntry.index)
|
|
6053
7907
|
gl.deleteBuffer(primitiveEntry.index);
|
|
7908
|
+
if (primitiveEntry.texture)
|
|
7909
|
+
textures.add(primitiveEntry.texture);
|
|
7910
|
+
if (primitiveEntry.metallicRoughnessTexture)
|
|
7911
|
+
textures.add(primitiveEntry.metallicRoughnessTexture);
|
|
7912
|
+
if (primitiveEntry.occlusionTexture)
|
|
7913
|
+
textures.add(primitiveEntry.occlusionTexture);
|
|
7914
|
+
if (primitiveEntry.emissiveTexture)
|
|
7915
|
+
textures.add(primitiveEntry.emissiveTexture);
|
|
6054
7916
|
}
|
|
7917
|
+
for (const texture of textures)
|
|
7918
|
+
gl.deleteTexture(texture);
|
|
6055
7919
|
}
|
|
6056
7920
|
gl.deleteProgram(program.program);
|
|
6057
7921
|
backdrop.dispose();
|
|
@@ -6145,27 +8009,156 @@ function createBackdropPalette(snapshot) {
|
|
|
6145
8009
|
};
|
|
6146
8010
|
}
|
|
6147
8011
|
function createWebGLModel(gl, node, modelData) {
|
|
8012
|
+
const textures = new Map();
|
|
8013
|
+
const textureFor = (index) => {
|
|
8014
|
+
if (index === undefined)
|
|
8015
|
+
return undefined;
|
|
8016
|
+
const existing = textures.get(index);
|
|
8017
|
+
if (existing)
|
|
8018
|
+
return existing;
|
|
8019
|
+
const source = modelData.textures[index];
|
|
8020
|
+
if (!source)
|
|
8021
|
+
return undefined;
|
|
8022
|
+
const texture = createTexture2D(gl, source.image);
|
|
8023
|
+
textures.set(index, texture);
|
|
8024
|
+
return texture;
|
|
8025
|
+
};
|
|
6148
8026
|
return {
|
|
6149
8027
|
node,
|
|
6150
8028
|
bounds: modelData.bounds,
|
|
6151
8029
|
color: colorToRgb(node.material?.color ?? "#8fb4ff"),
|
|
6152
|
-
normalizeToUnit:
|
|
8030
|
+
normalizeToUnit: shouldNormalizeModelNode(node),
|
|
6153
8031
|
primitives: modelData.primitives.map((primitiveEntry) => {
|
|
6154
8032
|
const position = createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.positions);
|
|
6155
8033
|
const normal = createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.normals);
|
|
8034
|
+
const uv = primitiveEntry.uvs ? createBuffer(gl, gl.ARRAY_BUFFER, primitiveEntry.uvs) : undefined;
|
|
6156
8035
|
const index = primitiveEntry.indices ? createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, primitiveEntry.indices) : undefined;
|
|
8036
|
+
const texture = textureFor(primitiveEntry.textureIndex);
|
|
8037
|
+
const metallicRoughnessTexture = textureFor(primitiveEntry.metallicRoughnessTextureIndex);
|
|
8038
|
+
const occlusionTexture = textureFor(primitiveEntry.occlusionTextureIndex);
|
|
8039
|
+
const emissiveTexture = textureFor(primitiveEntry.emissiveTextureIndex);
|
|
8040
|
+
const primitiveModelMatrix = createGltfPrimitiveModelMatrixResolver(modelData, node.animation, primitiveEntry);
|
|
6157
8041
|
return {
|
|
6158
8042
|
position,
|
|
6159
8043
|
normal,
|
|
8044
|
+
...(uv ? { uv } : {}),
|
|
6160
8045
|
...(index ? { index } : {}),
|
|
6161
8046
|
count: primitiveEntry.indices?.length ?? primitiveEntry.positions.length / 3,
|
|
6162
8047
|
mode: webglDrawMode(gl, primitiveEntry.mode),
|
|
6163
8048
|
color: node.material?.color ? colorToRgb(node.material.color) : primitiveEntry.color,
|
|
8049
|
+
...(texture ? { texture } : {}),
|
|
8050
|
+
...(metallicRoughnessTexture ? { metallicRoughnessTexture } : {}),
|
|
8051
|
+
...(occlusionTexture ? { occlusionTexture } : {}),
|
|
8052
|
+
...(emissiveTexture ? { emissiveTexture } : {}),
|
|
8053
|
+
...(primitiveEntry.metallic !== undefined ? { metallic: primitiveEntry.metallic } : {}),
|
|
8054
|
+
...(primitiveEntry.roughness !== undefined ? { roughness: primitiveEntry.roughness } : {}),
|
|
8055
|
+
...(primitiveEntry.emissive ? { emissive: primitiveEntry.emissive } : {}),
|
|
8056
|
+
...(primitiveModelMatrix ? { modelMatrix: primitiveModelMatrix } : {}),
|
|
6164
8057
|
...(primitiveEntry.indices ? { indexType: primitiveEntry.indices instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT } : {})
|
|
6165
8058
|
};
|
|
6166
8059
|
})
|
|
6167
8060
|
};
|
|
6168
8061
|
}
|
|
8062
|
+
function createGltfPrimitiveModelMatrixResolver(modelData, animation, primitiveEntry) {
|
|
8063
|
+
const staticMatrix = primitiveEntry.staticWorldMatrix;
|
|
8064
|
+
const nodeIndex = primitiveEntry.nodeIndex;
|
|
8065
|
+
if (nodeIndex === undefined)
|
|
8066
|
+
return staticMatrix ? () => staticMatrix : undefined;
|
|
8067
|
+
const clip = animation?.clip ? findGltfAnimationClip(modelData, animation.clip) : undefined;
|
|
8068
|
+
if (!clip)
|
|
8069
|
+
return staticMatrix ? () => staticMatrix : undefined;
|
|
8070
|
+
return (time) => resolveGltfAnimatedNodeMatrix(modelData, clip, animation, nodeIndex, time) ?? staticMatrix ?? identity4();
|
|
8071
|
+
}
|
|
8072
|
+
function findGltfAnimationClip(modelData, requestedClip) {
|
|
8073
|
+
const requested = requestedClip.trim().toLowerCase();
|
|
8074
|
+
if (!requested)
|
|
8075
|
+
return undefined;
|
|
8076
|
+
return modelData.animations.find((clip) => clip.name === requestedClip)
|
|
8077
|
+
?? modelData.animations.find((clip) => clip.name.toLowerCase() === requested);
|
|
8078
|
+
}
|
|
8079
|
+
function resolveGltfAnimatedNodeMatrix(modelData, clip, animation, nodeIndex, time) {
|
|
8080
|
+
if (nodeIndex < 0 || nodeIndex >= modelData.nodes.length)
|
|
8081
|
+
return undefined;
|
|
8082
|
+
const seconds = resolveGltfAnimationSeconds(animation, clip, time);
|
|
8083
|
+
const poses = modelData.nodes.map((node) => ({
|
|
8084
|
+
translation: [...node.baseTranslation],
|
|
8085
|
+
rotation: [...node.baseRotation],
|
|
8086
|
+
scale: [...node.baseScale],
|
|
8087
|
+
changed: false
|
|
8088
|
+
}));
|
|
8089
|
+
for (const channel of clip.channels) {
|
|
8090
|
+
const pose = poses[channel.nodeIndex];
|
|
8091
|
+
if (!pose)
|
|
8092
|
+
continue;
|
|
8093
|
+
pose.changed = true;
|
|
8094
|
+
if (channel.path === "rotation")
|
|
8095
|
+
pose.rotation = sampleGltfQuaternionChannel(channel, seconds);
|
|
8096
|
+
else if (channel.path === "scale")
|
|
8097
|
+
pose.scale = sampleGltfVec3Channel(channel, seconds);
|
|
8098
|
+
else
|
|
8099
|
+
pose.translation = sampleGltfVec3Channel(channel, seconds);
|
|
8100
|
+
}
|
|
8101
|
+
const localMatrices = modelData.nodes.map((node, index) => {
|
|
8102
|
+
const pose = poses[index];
|
|
8103
|
+
return pose?.changed ? gltfTrsMatrix(pose.translation, pose.rotation, pose.scale) : node.baseMatrix;
|
|
8104
|
+
});
|
|
8105
|
+
const worldMatrices = new Array(modelData.nodes.length);
|
|
8106
|
+
const visited = new Set();
|
|
8107
|
+
const visit = (currentIndex, parentMatrix) => {
|
|
8108
|
+
if (visited.has(currentIndex))
|
|
8109
|
+
return;
|
|
8110
|
+
const currentNode = modelData.nodes[currentIndex];
|
|
8111
|
+
const localMatrix = localMatrices[currentIndex];
|
|
8112
|
+
if (!currentNode || !localMatrix)
|
|
8113
|
+
return;
|
|
8114
|
+
visited.add(currentIndex);
|
|
8115
|
+
const worldMatrix = multiply4(parentMatrix, localMatrix);
|
|
8116
|
+
worldMatrices[currentIndex] = worldMatrix;
|
|
8117
|
+
for (const childIndex of currentNode.children)
|
|
8118
|
+
visit(childIndex, worldMatrix);
|
|
8119
|
+
};
|
|
8120
|
+
const roots = modelData.sceneRoots.length > 0 ? modelData.sceneRoots : modelData.nodes.map((_, index) => index);
|
|
8121
|
+
for (const rootIndex of roots)
|
|
8122
|
+
visit(rootIndex, identity4());
|
|
8123
|
+
for (let index = 0; index < modelData.nodes.length; index += 1) {
|
|
8124
|
+
if (!visited.has(index))
|
|
8125
|
+
visit(index, identity4());
|
|
8126
|
+
}
|
|
8127
|
+
return worldMatrices[nodeIndex];
|
|
8128
|
+
}
|
|
8129
|
+
function resolveGltfAnimationSeconds(animation, clip, time) {
|
|
8130
|
+
const speed = Math.max(0.05, animation?.speed ?? 1);
|
|
8131
|
+
const startTime = animation?.startTime ?? 0;
|
|
8132
|
+
const phase = Math.max(0, animation?.captureTime ?? 0);
|
|
8133
|
+
const rawSeconds = Math.max(0, time / 1000 - startTime) * speed;
|
|
8134
|
+
const seconds = phase + rawSeconds;
|
|
8135
|
+
const duration = Math.max(0, animation?.duration ?? clip.duration);
|
|
8136
|
+
if (duration <= 0)
|
|
8137
|
+
return seconds;
|
|
8138
|
+
return animation?.loop === false ? Math.min(seconds, duration) : seconds % duration;
|
|
8139
|
+
}
|
|
8140
|
+
function createTexture2D(gl, image) {
|
|
8141
|
+
const texture = gl.createTexture();
|
|
8142
|
+
if (!texture)
|
|
8143
|
+
throw new AuraRuntimeError("backend-fallback", "Aura3D WebGL2 texture allocation failed. Suggested fix: reduce texture count or reload the page.");
|
|
8144
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
8145
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
8146
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
8147
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
8148
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
8149
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
8150
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
|
8151
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
|
8152
|
+
const anisotropic = gl.getExtension("EXT_texture_filter_anisotropic")
|
|
8153
|
+
?? gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic")
|
|
8154
|
+
?? gl.getExtension("MOZ_EXT_texture_filter_anisotropic");
|
|
8155
|
+
if (anisotropic) {
|
|
8156
|
+
const maxAnisotropy = Number(gl.getParameter(anisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT)) || 1;
|
|
8157
|
+
gl.texParameterf(gl.TEXTURE_2D, anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(8, maxAnisotropy));
|
|
8158
|
+
}
|
|
8159
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
8160
|
+
return texture;
|
|
8161
|
+
}
|
|
6169
8162
|
function createWebGLPrimitiveModel(gl, node) {
|
|
6170
8163
|
const primitive = node.primitive === "sphere"
|
|
6171
8164
|
? createSphereGeometry()
|
|
@@ -6189,7 +8182,10 @@ function createWebGLPrimitiveModel(gl, node) {
|
|
|
6189
8182
|
index: createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, primitive.indices),
|
|
6190
8183
|
count: primitive.indices.length,
|
|
6191
8184
|
mode: gl.TRIANGLES,
|
|
6192
|
-
indexType: gl.UNSIGNED_SHORT
|
|
8185
|
+
indexType: gl.UNSIGNED_SHORT,
|
|
8186
|
+
metallic: clamp01(node.material?.metallic ?? node.material?.metalness ?? 0),
|
|
8187
|
+
roughness: clamp01(node.material?.roughness ?? 0.72),
|
|
8188
|
+
emissive: node.material?.emissive ? colorToRgb(node.material.emissive) : [0, 0, 0]
|
|
6193
8189
|
}]
|
|
6194
8190
|
};
|
|
6195
8191
|
}
|
|
@@ -6278,53 +8274,66 @@ function createWebGLParticleModel(gl, effect) {
|
|
|
6278
8274
|
? ["#fff7ad", "#fef08a", "#fb923c", "#60a5fa", "#38bdf8", "#fb7185"]
|
|
6279
8275
|
: [effect.color ?? "#7dfcff", "#ffd166", "#60a5fa"]);
|
|
6280
8276
|
const sizeCurve = effect.sizeOverLife ?? [0.35, 1, 0.58];
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
? 0.
|
|
6292
|
-
:
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
:
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
const
|
|
6302
|
-
const
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
8277
|
+
const writeParticleVertices = (seconds) => {
|
|
8278
|
+
const emitter = effect.emitter ?? "swirl";
|
|
8279
|
+
for (let index = 0; index < count; index += 1) {
|
|
8280
|
+
writeParticlePosition(center, 0, seconds, emitter, radius, height, index, turbulence, gravity, groundCollision, fountainLayer);
|
|
8281
|
+
const life = getParticleLife(index, seconds, emitter);
|
|
8282
|
+
const sizeLife = life < 0.5
|
|
8283
|
+
? (sizeCurve[0] ?? 0.35) + ((sizeCurve[1] ?? 1) - (sizeCurve[0] ?? 0.35)) * (life / 0.5)
|
|
8284
|
+
: (sizeCurve[1] ?? 1) + ((sizeCurve[2] ?? 0.58) - (sizeCurve[1] ?? 1)) * ((life - 0.5) / 0.5);
|
|
8285
|
+
const baseSize = isFountain
|
|
8286
|
+
? fountainLayer === "mist"
|
|
8287
|
+
? 0.012
|
|
8288
|
+
: fountainLayer === "splash"
|
|
8289
|
+
? 0.024
|
|
8290
|
+
: 0.032
|
|
8291
|
+
: materialMode === "dust" || materialMode === "smoke"
|
|
8292
|
+
? 0.011
|
|
8293
|
+
: materialMode === "star" || materialMode === "spark"
|
|
8294
|
+
? 0.016
|
|
8295
|
+
: 0.022;
|
|
8296
|
+
const size = baseSize * Math.max(0.38, sizeLife) * seededRange(index, 353, 0.72, 1.14);
|
|
8297
|
+
const particleColor = colorToRgb(lifetimeRamp[index % lifetimeRamp.length] ?? effect.color ?? "#7dfcff");
|
|
8298
|
+
const vertexBase = index * 6;
|
|
8299
|
+
const positionBase = vertexBase * 3;
|
|
8300
|
+
for (let vertex = 0; vertex < localVertices.length; vertex += 1) {
|
|
8301
|
+
const local = localVertices[vertex];
|
|
8302
|
+
const offset = positionBase + vertex * 3;
|
|
8303
|
+
positions[offset] = center[0] + local[0] * size;
|
|
8304
|
+
positions[offset + 1] = center[1] + local[1] * size;
|
|
8305
|
+
positions[offset + 2] = center[2] + local[2] * size;
|
|
8306
|
+
normals[offset] = 0.45;
|
|
8307
|
+
normals[offset + 1] = 0.82;
|
|
8308
|
+
normals[offset + 2] = 0.36;
|
|
8309
|
+
colors[offset] = particleColor[0];
|
|
8310
|
+
colors[offset + 1] = particleColor[1];
|
|
8311
|
+
colors[offset + 2] = particleColor[2];
|
|
8312
|
+
}
|
|
8313
|
+
const indexBase = index * 24;
|
|
8314
|
+
for (let tri = 0; tri < localTriangles.length; tri += 1) {
|
|
8315
|
+
const local = localTriangles[tri];
|
|
8316
|
+
indices[indexBase + tri * 3] = vertexBase + local[0];
|
|
8317
|
+
indices[indexBase + tri * 3 + 1] = vertexBase + local[1];
|
|
8318
|
+
indices[indexBase + tri * 3 + 2] = vertexBase + local[2];
|
|
8319
|
+
}
|
|
6319
8320
|
}
|
|
6320
|
-
}
|
|
8321
|
+
};
|
|
8322
|
+
writeParticleVertices(0);
|
|
8323
|
+
const position = createBuffer(gl, gl.ARRAY_BUFFER, positions);
|
|
6321
8324
|
return {
|
|
8325
|
+
node: effect,
|
|
6322
8326
|
bounds: { min: [-radius * 2, 0, -radius * 2], max: [radius * 2, height, radius * 2] },
|
|
6323
8327
|
color: [1, 1, 1],
|
|
6324
8328
|
normalizeToUnit: false,
|
|
6325
|
-
|
|
8329
|
+
update(time) {
|
|
8330
|
+
const seconds = time * Math.max(0.05, effect.speed ?? 1);
|
|
8331
|
+
writeParticleVertices(seconds);
|
|
8332
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, position);
|
|
8333
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, positions);
|
|
8334
|
+
},
|
|
6326
8335
|
primitives: [{
|
|
6327
|
-
position
|
|
8336
|
+
position,
|
|
6328
8337
|
normal: createBuffer(gl, gl.ARRAY_BUFFER, normals),
|
|
6329
8338
|
vertexColor: createBuffer(gl, gl.ARRAY_BUFFER, colors),
|
|
6330
8339
|
index: createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices),
|
|
@@ -6348,16 +8357,19 @@ precision highp float;
|
|
|
6348
8357
|
in vec3 a_position;
|
|
6349
8358
|
in vec3 a_normal;
|
|
6350
8359
|
in vec3 a_color;
|
|
8360
|
+
in vec2 a_uv;
|
|
6351
8361
|
uniform mat4 u_model;
|
|
6352
8362
|
uniform mat4 u_viewProjection;
|
|
6353
8363
|
out vec3 v_normal;
|
|
6354
8364
|
out vec3 v_world;
|
|
6355
8365
|
out vec3 v_color;
|
|
8366
|
+
out vec2 v_uv;
|
|
6356
8367
|
void main() {
|
|
6357
8368
|
vec4 world = u_model * vec4(a_position, 1.0);
|
|
6358
8369
|
v_world = world.xyz;
|
|
6359
8370
|
v_normal = normalize(mat3(u_model) * a_normal);
|
|
6360
8371
|
v_color = a_color;
|
|
8372
|
+
v_uv = a_uv;
|
|
6361
8373
|
gl_Position = u_viewProjection * world;
|
|
6362
8374
|
}`);
|
|
6363
8375
|
const fragment = compileShader(gl, gl.FRAGMENT_SHADER, `#version 300 es
|
|
@@ -6365,16 +8377,48 @@ precision highp float;
|
|
|
6365
8377
|
in vec3 v_normal;
|
|
6366
8378
|
in vec3 v_world;
|
|
6367
8379
|
in vec3 v_color;
|
|
8380
|
+
in vec2 v_uv;
|
|
6368
8381
|
uniform vec3 u_color;
|
|
6369
8382
|
uniform vec3 u_lightDirection;
|
|
8383
|
+
uniform sampler2D u_baseColorTexture;
|
|
8384
|
+
uniform int u_useTexture;
|
|
8385
|
+
uniform sampler2D u_metallicRoughnessTexture;
|
|
8386
|
+
uniform int u_useMetallicRoughnessTexture;
|
|
8387
|
+
uniform sampler2D u_occlusionTexture;
|
|
8388
|
+
uniform int u_useOcclusionTexture;
|
|
8389
|
+
uniform sampler2D u_emissiveTexture;
|
|
8390
|
+
uniform int u_useEmissiveTexture;
|
|
8391
|
+
uniform float u_metallic;
|
|
8392
|
+
uniform float u_roughness;
|
|
8393
|
+
uniform vec3 u_emissive;
|
|
6370
8394
|
out vec4 outColor;
|
|
6371
8395
|
void main() {
|
|
6372
8396
|
vec3 normal = normalize(v_normal);
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
vec3
|
|
6376
|
-
|
|
6377
|
-
|
|
8397
|
+
vec3 lightDirection = normalize(u_lightDirection);
|
|
8398
|
+
vec3 viewDirection = normalize(vec3(0.0, 0.34, 1.0));
|
|
8399
|
+
vec3 halfVector = normalize(lightDirection + viewDirection);
|
|
8400
|
+
float key = max(dot(normal, lightDirection), 0.0);
|
|
8401
|
+
float wrap = max(dot(normal, normalize(vec3(-0.36, 0.52, -0.28))), 0.0);
|
|
8402
|
+
float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 4.0);
|
|
8403
|
+
float rim = pow(1.0 - max(dot(normal, viewDirection), 0.0), 2.0);
|
|
8404
|
+
vec4 texel = u_useTexture == 1 ? texture(u_baseColorTexture, v_uv) : vec4(1.0);
|
|
8405
|
+
vec4 mrTexel = u_useMetallicRoughnessTexture == 1 ? texture(u_metallicRoughnessTexture, v_uv) : vec4(1.0);
|
|
8406
|
+
vec3 emissiveTexel = u_useEmissiveTexture == 1 ? texture(u_emissiveTexture, v_uv).rgb : vec3(1.0);
|
|
8407
|
+
float ao = u_useOcclusionTexture == 1 ? mix(0.45, 1.0, texture(u_occlusionTexture, v_uv).r) : 1.0;
|
|
8408
|
+
float roughness = clamp(u_roughness * mrTexel.g, 0.045, 1.0);
|
|
8409
|
+
float metallic = clamp(u_metallic * mrTexel.b, 0.0, 1.0);
|
|
8410
|
+
vec3 base = u_color * v_color * texel.rgb;
|
|
8411
|
+
vec3 dielectricSpecular = vec3(0.045);
|
|
8412
|
+
vec3 specularColor = mix(dielectricSpecular, base, metallic);
|
|
8413
|
+
float specularPower = mix(96.0, 10.0, roughness);
|
|
8414
|
+
float specular = pow(max(dot(normal, halfVector), 0.0), specularPower) * mix(0.95, 0.18, roughness);
|
|
8415
|
+
vec3 diffuse = base * (1.0 - metallic) * (0.22 + key * 0.78 + wrap * 0.12);
|
|
8416
|
+
vec3 environment = mix(vec3(0.10, 0.15, 0.18), vec3(0.74, 0.86, 1.0), normal.y * 0.5 + 0.5);
|
|
8417
|
+
vec3 reflection = environment * specularColor * (fresnel * 0.62 + (1.0 - roughness) * 0.22);
|
|
8418
|
+
vec3 emissive = u_emissive * emissiveTexel;
|
|
8419
|
+
vec3 color = (diffuse + specularColor * specular + reflection) * ao + emissive + vec3(0.35, 0.65, 1.0) * rim * 0.075;
|
|
8420
|
+
color = color / (color + vec3(1.0));
|
|
8421
|
+
outColor = vec4(pow(color, vec3(1.0 / 2.2)), texel.a);
|
|
6378
8422
|
}`);
|
|
6379
8423
|
const program = gl.createProgram();
|
|
6380
8424
|
if (!program)
|
|
@@ -6391,14 +8435,26 @@ void main() {
|
|
|
6391
8435
|
model: requiredUniform(gl, program, "u_model"),
|
|
6392
8436
|
viewProjection: requiredUniform(gl, program, "u_viewProjection"),
|
|
6393
8437
|
color: requiredUniform(gl, program, "u_color"),
|
|
6394
|
-
lightDirection: requiredUniform(gl, program, "u_lightDirection")
|
|
8438
|
+
lightDirection: requiredUniform(gl, program, "u_lightDirection"),
|
|
8439
|
+
baseColorTexture: requiredUniform(gl, program, "u_baseColorTexture"),
|
|
8440
|
+
useTexture: requiredUniform(gl, program, "u_useTexture"),
|
|
8441
|
+
metallicRoughnessTexture: requiredUniform(gl, program, "u_metallicRoughnessTexture"),
|
|
8442
|
+
useMetallicRoughnessTexture: requiredUniform(gl, program, "u_useMetallicRoughnessTexture"),
|
|
8443
|
+
occlusionTexture: requiredUniform(gl, program, "u_occlusionTexture"),
|
|
8444
|
+
useOcclusionTexture: requiredUniform(gl, program, "u_useOcclusionTexture"),
|
|
8445
|
+
emissiveTexture: requiredUniform(gl, program, "u_emissiveTexture"),
|
|
8446
|
+
useEmissiveTexture: requiredUniform(gl, program, "u_useEmissiveTexture"),
|
|
8447
|
+
metallic: requiredUniform(gl, program, "u_metallic"),
|
|
8448
|
+
roughness: requiredUniform(gl, program, "u_roughness"),
|
|
8449
|
+
emissive: requiredUniform(gl, program, "u_emissive")
|
|
6395
8450
|
};
|
|
6396
8451
|
return {
|
|
6397
8452
|
program,
|
|
6398
8453
|
attributes: {
|
|
6399
8454
|
position: gl.getAttribLocation(program, "a_position"),
|
|
6400
8455
|
normal: gl.getAttribLocation(program, "a_normal"),
|
|
6401
|
-
color: gl.getAttribLocation(program, "a_color")
|
|
8456
|
+
color: gl.getAttribLocation(program, "a_color"),
|
|
8457
|
+
uv: gl.getAttribLocation(program, "a_uv")
|
|
6402
8458
|
},
|
|
6403
8459
|
uniforms
|
|
6404
8460
|
};
|
|
@@ -6440,7 +8496,7 @@ async function loadGltfForWebGL(url) {
|
|
|
6440
8496
|
const bytes = await response.arrayBuffer();
|
|
6441
8497
|
const loaded = isGlb(bytes) ? parseGlb(bytes) : { json: JSON.parse(new TextDecoder().decode(bytes)), buffers: [] };
|
|
6442
8498
|
const buffers = loaded.buffers.length > 0 ? loaded.buffers : await loadExternalGltfBuffers(loaded.json, absoluteUrl);
|
|
6443
|
-
return createGltfModel(loaded.json, buffers);
|
|
8499
|
+
return await createGltfModel(loaded.json, buffers, absoluteUrl);
|
|
6444
8500
|
}
|
|
6445
8501
|
function isGlb(bytes) {
|
|
6446
8502
|
return new DataView(bytes).getUint32(0, true) === 0x46546c67;
|
|
@@ -6487,10 +8543,14 @@ function dataUriToArrayBuffer(uri) {
|
|
|
6487
8543
|
bytes[index] = binary.charCodeAt(index);
|
|
6488
8544
|
return bytes.buffer;
|
|
6489
8545
|
}
|
|
6490
|
-
function createGltfModel(json, buffers) {
|
|
8546
|
+
async function createGltfModel(json, buffers, modelUrl) {
|
|
6491
8547
|
const primitives = [];
|
|
8548
|
+
const textures = await createGltfTextureSources(json, buffers, modelUrl);
|
|
8549
|
+
const nodes = createGltfRuntimeNodes(json);
|
|
8550
|
+
const animations = createGltfAnimationClips(json, buffers);
|
|
8551
|
+
const sceneRoots = resolveGltfSceneRoots(json);
|
|
6492
8552
|
let bounds;
|
|
6493
|
-
const pushMesh = (meshIndex, matrix) => {
|
|
8553
|
+
const pushMesh = (meshIndex, matrix, nodeIndex) => {
|
|
6494
8554
|
const mesh = json.meshes?.[meshIndex];
|
|
6495
8555
|
if (!mesh)
|
|
6496
8556
|
return;
|
|
@@ -6502,17 +8562,29 @@ function createGltfModel(json, buffers) {
|
|
|
6502
8562
|
const sourceNormals = primitive.attributes?.NORMAL === undefined
|
|
6503
8563
|
? createDefaultNormals(sourcePositions.length / 3)
|
|
6504
8564
|
: readAccessor(json, buffers, primitive.attributes.NORMAL, 3);
|
|
6505
|
-
const
|
|
6506
|
-
|
|
8565
|
+
const sourceUvs = primitive.attributes?.TEXCOORD_0 === undefined
|
|
8566
|
+
? undefined
|
|
8567
|
+
: readAccessor(json, buffers, primitive.attributes.TEXCOORD_0, 2);
|
|
6507
8568
|
const indices = primitive.indices === undefined ? undefined : readIndices(json, buffers, primitive.indices);
|
|
6508
|
-
const
|
|
8569
|
+
const materialInfo = materialRenderInfo(json, primitive.material, sourceUvs);
|
|
8570
|
+
const primitiveBounds = boundsFromPositions(transformPositions(sourcePositions, matrix));
|
|
6509
8571
|
bounds = bounds ? mergeBounds(bounds, primitiveBounds) : primitiveBounds;
|
|
6510
8572
|
primitives.push({
|
|
6511
|
-
positions,
|
|
6512
|
-
normals,
|
|
8573
|
+
positions: sourcePositions,
|
|
8574
|
+
normals: sourceNormals,
|
|
8575
|
+
...(sourceUvs ? { uvs: sourceUvs } : {}),
|
|
6513
8576
|
...(indices ? { indices } : {}),
|
|
6514
8577
|
mode: primitive.mode ?? 4,
|
|
6515
|
-
color:
|
|
8578
|
+
color: materialInfo.color,
|
|
8579
|
+
...(materialInfo.textureIndex !== undefined ? { textureIndex: materialInfo.textureIndex } : {}),
|
|
8580
|
+
...(materialInfo.metallicRoughnessTextureIndex !== undefined ? { metallicRoughnessTextureIndex: materialInfo.metallicRoughnessTextureIndex } : {}),
|
|
8581
|
+
...(materialInfo.occlusionTextureIndex !== undefined ? { occlusionTextureIndex: materialInfo.occlusionTextureIndex } : {}),
|
|
8582
|
+
...(materialInfo.emissiveTextureIndex !== undefined ? { emissiveTextureIndex: materialInfo.emissiveTextureIndex } : {}),
|
|
8583
|
+
metallic: materialInfo.metallic,
|
|
8584
|
+
roughness: materialInfo.roughness,
|
|
8585
|
+
emissive: materialInfo.emissive,
|
|
8586
|
+
...(nodeIndex !== undefined ? { nodeIndex } : {}),
|
|
8587
|
+
staticWorldMatrix: matrix
|
|
6516
8588
|
});
|
|
6517
8589
|
}
|
|
6518
8590
|
};
|
|
@@ -6527,22 +8599,16 @@ function createGltfModel(json, buffers) {
|
|
|
6527
8599
|
const nextStack = new Set(stack);
|
|
6528
8600
|
nextStack.add(nodeIndex);
|
|
6529
8601
|
if (node.mesh !== undefined)
|
|
6530
|
-
pushMesh(node.mesh, worldMatrix);
|
|
8602
|
+
pushMesh(node.mesh, worldMatrix, nodeIndex);
|
|
6531
8603
|
for (const childIndex of node.children ?? [])
|
|
6532
8604
|
visitNode(childIndex, worldMatrix, nextStack);
|
|
6533
8605
|
};
|
|
6534
|
-
|
|
6535
|
-
if (json.nodes?.length && sceneRoots?.length) {
|
|
8606
|
+
if (json.nodes?.length && sceneRoots.length) {
|
|
6536
8607
|
for (const nodeIndex of sceneRoots)
|
|
6537
8608
|
visitNode(nodeIndex, identity4(), new Set());
|
|
6538
8609
|
}
|
|
6539
8610
|
else if (json.nodes?.length) {
|
|
6540
|
-
const
|
|
6541
|
-
for (const node of json.nodes)
|
|
6542
|
-
for (const childIndex of node.children ?? [])
|
|
6543
|
-
childNodes.add(childIndex);
|
|
6544
|
-
const roots = json.nodes.map((_, nodeIndex) => nodeIndex).filter((nodeIndex) => !childNodes.has(nodeIndex));
|
|
6545
|
-
for (const nodeIndex of roots.length > 0 ? roots : json.nodes.map((_, nodeIndex) => nodeIndex))
|
|
8611
|
+
for (const nodeIndex of json.nodes.map((_, nodeIndex) => nodeIndex))
|
|
6546
8612
|
visitNode(nodeIndex, identity4(), new Set());
|
|
6547
8613
|
}
|
|
6548
8614
|
else {
|
|
@@ -6552,19 +8618,163 @@ function createGltfModel(json, buffers) {
|
|
|
6552
8618
|
if (primitives.length === 0) {
|
|
6553
8619
|
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.");
|
|
6554
8620
|
}
|
|
6555
|
-
return { primitives, bounds: bounds ?? { min: [-1, -1, -1], max: [1, 1, 1] } };
|
|
8621
|
+
return { primitives, textures, nodes, animations, sceneRoots, bounds: bounds ?? { min: [-1, -1, -1], max: [1, 1, 1] } };
|
|
8622
|
+
}
|
|
8623
|
+
function resolveGltfSceneRoots(json) {
|
|
8624
|
+
const nodeCount = json.nodes?.length ?? 0;
|
|
8625
|
+
const sceneRoots = json.scenes?.[json.scene ?? 0]?.nodes?.filter((nodeIndex) => nodeIndex >= 0 && nodeIndex < nodeCount) ?? [];
|
|
8626
|
+
if (sceneRoots.length > 0)
|
|
8627
|
+
return sceneRoots;
|
|
8628
|
+
if (nodeCount === 0)
|
|
8629
|
+
return [];
|
|
8630
|
+
const childNodes = new Set();
|
|
8631
|
+
for (const node of json.nodes ?? []) {
|
|
8632
|
+
for (const childIndex of node.children ?? [])
|
|
8633
|
+
childNodes.add(childIndex);
|
|
8634
|
+
}
|
|
8635
|
+
const roots = (json.nodes ?? []).map((_, nodeIndex) => nodeIndex).filter((nodeIndex) => !childNodes.has(nodeIndex));
|
|
8636
|
+
return roots.length > 0 ? roots : (json.nodes ?? []).map((_, nodeIndex) => nodeIndex);
|
|
8637
|
+
}
|
|
8638
|
+
function createGltfRuntimeNodes(json) {
|
|
8639
|
+
return (json.nodes ?? []).map((node) => {
|
|
8640
|
+
const translate = node.translation ?? [0, 0, 0];
|
|
8641
|
+
const rotate = node.rotation ?? [0, 0, 0, 1];
|
|
8642
|
+
const scale = node.scale ?? [1, 1, 1];
|
|
8643
|
+
return {
|
|
8644
|
+
children: node.children ?? [],
|
|
8645
|
+
baseTranslation: [translate[0] ?? 0, translate[1] ?? 0, translate[2] ?? 0],
|
|
8646
|
+
baseRotation: normalizeQuaternion([rotate[0] ?? 0, rotate[1] ?? 0, rotate[2] ?? 0, rotate[3] ?? 1]),
|
|
8647
|
+
baseScale: [scale[0] ?? 1, scale[1] ?? 1, scale[2] ?? 1],
|
|
8648
|
+
baseMatrix: gltfNodeMatrix(node)
|
|
8649
|
+
};
|
|
8650
|
+
});
|
|
6556
8651
|
}
|
|
6557
|
-
function
|
|
6558
|
-
|
|
8652
|
+
function createGltfAnimationClips(json, buffers) {
|
|
8653
|
+
return (json.animations ?? []).map((animation, animationIndex) => {
|
|
8654
|
+
const channels = [];
|
|
8655
|
+
let duration = 0;
|
|
8656
|
+
for (const channel of animation.channels ?? []) {
|
|
8657
|
+
const sampler = animation.samplers?.[channel.sampler];
|
|
8658
|
+
const nodeIndex = channel.target.node;
|
|
8659
|
+
const path = parseGltfAnimationPath(channel.target.path);
|
|
8660
|
+
if (!sampler || nodeIndex === undefined || !path)
|
|
8661
|
+
continue;
|
|
8662
|
+
const times = readAccessor(json, buffers, sampler.input, 1);
|
|
8663
|
+
const values = readAccessor(json, buffers, sampler.output, path === "rotation" ? 4 : 3);
|
|
8664
|
+
if (times.length === 0 || values.length === 0)
|
|
8665
|
+
continue;
|
|
8666
|
+
duration = Math.max(duration, times[times.length - 1] ?? 0);
|
|
8667
|
+
channels.push({
|
|
8668
|
+
nodeIndex,
|
|
8669
|
+
path,
|
|
8670
|
+
times,
|
|
8671
|
+
values,
|
|
8672
|
+
interpolation: sampler.interpolation ?? "LINEAR"
|
|
8673
|
+
});
|
|
8674
|
+
}
|
|
8675
|
+
return {
|
|
8676
|
+
name: animation.name ?? `animation-${animationIndex + 1}`,
|
|
8677
|
+
duration,
|
|
8678
|
+
channels
|
|
8679
|
+
};
|
|
8680
|
+
}).filter((clip) => clip.channels.length > 0);
|
|
8681
|
+
}
|
|
8682
|
+
function parseGltfAnimationPath(path) {
|
|
8683
|
+
return path === "translation" || path === "rotation" || path === "scale" ? path : undefined;
|
|
8684
|
+
}
|
|
8685
|
+
async function createGltfTextureSources(json, buffers, modelUrl) {
|
|
8686
|
+
const images = new Map();
|
|
8687
|
+
const loadImage = (imageIndex) => {
|
|
8688
|
+
const existing = images.get(imageIndex);
|
|
8689
|
+
if (existing)
|
|
8690
|
+
return existing;
|
|
8691
|
+
const promise = loadGltfImage(json, buffers, imageIndex, modelUrl).catch(() => undefined);
|
|
8692
|
+
images.set(imageIndex, promise);
|
|
8693
|
+
return promise;
|
|
8694
|
+
};
|
|
8695
|
+
return await Promise.all((json.textures ?? []).map(async (texture) => {
|
|
8696
|
+
if (texture.source === undefined)
|
|
8697
|
+
return undefined;
|
|
8698
|
+
const image = await loadImage(texture.source);
|
|
8699
|
+
return image ? { image } : undefined;
|
|
8700
|
+
}));
|
|
8701
|
+
}
|
|
8702
|
+
async function loadGltfImage(json, buffers, imageIndex, modelUrl) {
|
|
8703
|
+
const image = json.images?.[imageIndex];
|
|
8704
|
+
if (!image)
|
|
8705
|
+
return undefined;
|
|
8706
|
+
let blob;
|
|
8707
|
+
if (image.bufferView !== undefined) {
|
|
8708
|
+
const view = json.bufferViews?.[image.bufferView];
|
|
8709
|
+
if (!view)
|
|
8710
|
+
return undefined;
|
|
8711
|
+
const buffer = buffers[view.buffer];
|
|
8712
|
+
if (!buffer)
|
|
8713
|
+
return undefined;
|
|
8714
|
+
const bytes = new Uint8Array(buffer, view.byteOffset ?? 0, view.byteLength);
|
|
8715
|
+
blob = new Blob([bytes], { type: image.mimeType ?? "application/octet-stream" });
|
|
8716
|
+
}
|
|
8717
|
+
else if (image.uri?.startsWith("data:")) {
|
|
8718
|
+
const response = await fetch(image.uri);
|
|
8719
|
+
blob = await response.blob();
|
|
8720
|
+
}
|
|
8721
|
+
else if (image.uri) {
|
|
8722
|
+
const response = await fetch(new URL(image.uri, modelUrl).href);
|
|
8723
|
+
if (!response.ok)
|
|
8724
|
+
return undefined;
|
|
8725
|
+
blob = await response.blob();
|
|
8726
|
+
}
|
|
8727
|
+
else {
|
|
6559
8728
|
return undefined;
|
|
8729
|
+
}
|
|
8730
|
+
if (typeof createImageBitmap === "function")
|
|
8731
|
+
return await createImageBitmap(blob);
|
|
8732
|
+
return await blobToHtmlImage(blob);
|
|
8733
|
+
}
|
|
8734
|
+
async function blobToHtmlImage(blob) {
|
|
8735
|
+
const url = URL.createObjectURL(blob);
|
|
8736
|
+
try {
|
|
8737
|
+
const image = new Image();
|
|
8738
|
+
image.decoding = "async";
|
|
8739
|
+
image.src = url;
|
|
8740
|
+
if (typeof image.decode === "function")
|
|
8741
|
+
await image.decode();
|
|
8742
|
+
else
|
|
8743
|
+
await new Promise((resolve, reject) => {
|
|
8744
|
+
image.onload = () => resolve();
|
|
8745
|
+
image.onerror = () => reject(new Error("Aura3D failed to decode glTF image."));
|
|
8746
|
+
});
|
|
8747
|
+
return image;
|
|
8748
|
+
}
|
|
8749
|
+
finally {
|
|
8750
|
+
URL.revokeObjectURL(url);
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
function materialRenderInfo(json, materialIndex, uvs) {
|
|
8754
|
+
if (materialIndex === undefined) {
|
|
8755
|
+
return { metallic: 0, roughness: 0.72, emissive: [0, 0, 0] };
|
|
8756
|
+
}
|
|
6560
8757
|
const materialEntry = json.materials?.[materialIndex];
|
|
6561
|
-
const
|
|
6562
|
-
|
|
6563
|
-
return [clamp01(base[0]), clamp01(base[1]), clamp01(base[2])];
|
|
8758
|
+
const pbr = materialEntry?.pbrMetallicRoughness;
|
|
8759
|
+
const base = pbr?.baseColorFactor;
|
|
6564
8760
|
const emissive = materialEntry?.emissiveFactor;
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
8761
|
+
return {
|
|
8762
|
+
...(base && base.length >= 3 ? { color: [clamp01(base[0]), clamp01(base[1]), clamp01(base[2])] } : {}),
|
|
8763
|
+
...textureRef(pbr?.baseColorTexture, uvs, "textureIndex"),
|
|
8764
|
+
...textureRef(pbr?.metallicRoughnessTexture, uvs, "metallicRoughnessTextureIndex"),
|
|
8765
|
+
...textureRef(materialEntry?.occlusionTexture, uvs, "occlusionTextureIndex"),
|
|
8766
|
+
...textureRef(materialEntry?.emissiveTexture, uvs, "emissiveTextureIndex"),
|
|
8767
|
+
metallic: clamp01(pbr?.metallicFactor ?? 0),
|
|
8768
|
+
roughness: clamp01(pbr?.roughnessFactor ?? 0.72),
|
|
8769
|
+
emissive: emissive && emissive.length >= 3
|
|
8770
|
+
? [clamp01(emissive[0]), clamp01(emissive[1]), clamp01(emissive[2])]
|
|
8771
|
+
: [0, 0, 0]
|
|
8772
|
+
};
|
|
8773
|
+
}
|
|
8774
|
+
function textureRef(texture, uvs, key) {
|
|
8775
|
+
if (!texture || !uvs || texture.texCoord !== undefined && texture.texCoord !== 0)
|
|
8776
|
+
return {};
|
|
8777
|
+
return { [key]: texture.index };
|
|
6568
8778
|
}
|
|
6569
8779
|
function createPlaneGeometry() {
|
|
6570
8780
|
return {
|
|
@@ -6809,6 +9019,106 @@ function gltfNodeMatrix(node) {
|
|
|
6809
9019
|
const scale = node.scale ?? [1, 1, 1];
|
|
6810
9020
|
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)));
|
|
6811
9021
|
}
|
|
9022
|
+
function gltfTrsMatrix(translate, rotate, scale) {
|
|
9023
|
+
return multiply4(translation(translate[0], translate[1], translate[2]), multiply4(rotationQuaternion(rotate), scaling(scale[0], scale[1], scale[2])));
|
|
9024
|
+
}
|
|
9025
|
+
function sampleGltfVec3Channel(channel, seconds) {
|
|
9026
|
+
const [left, right, t] = gltfKeyframePair(channel.times, seconds);
|
|
9027
|
+
const a = readGltfChannelValue(channel, left, 3);
|
|
9028
|
+
if (left === right || channel.interpolation === "STEP")
|
|
9029
|
+
return [a[0], a[1], a[2]];
|
|
9030
|
+
const b = readGltfChannelValue(channel, right, 3);
|
|
9031
|
+
return [
|
|
9032
|
+
a[0] + (b[0] - a[0]) * t,
|
|
9033
|
+
a[1] + (b[1] - a[1]) * t,
|
|
9034
|
+
a[2] + (b[2] - a[2]) * t
|
|
9035
|
+
];
|
|
9036
|
+
}
|
|
9037
|
+
function sampleGltfQuaternionChannel(channel, seconds) {
|
|
9038
|
+
const [left, right, t] = gltfKeyframePair(channel.times, seconds);
|
|
9039
|
+
const a = normalizeQuaternion(readGltfChannelValue(channel, left, 4));
|
|
9040
|
+
if (left === right || channel.interpolation === "STEP")
|
|
9041
|
+
return a;
|
|
9042
|
+
const b = normalizeQuaternion(readGltfChannelValue(channel, right, 4));
|
|
9043
|
+
return slerpQuaternion(a, b, t);
|
|
9044
|
+
}
|
|
9045
|
+
function gltfKeyframePair(times, seconds) {
|
|
9046
|
+
if (times.length <= 1)
|
|
9047
|
+
return [0, 0, 0];
|
|
9048
|
+
if (seconds <= (times[0] ?? 0))
|
|
9049
|
+
return [0, 0, 0];
|
|
9050
|
+
const lastIndex = times.length - 1;
|
|
9051
|
+
if (seconds >= (times[lastIndex] ?? seconds))
|
|
9052
|
+
return [lastIndex, lastIndex, 0];
|
|
9053
|
+
for (let index = 0; index < lastIndex; index += 1) {
|
|
9054
|
+
const start = times[index] ?? 0;
|
|
9055
|
+
const end = times[index + 1] ?? start;
|
|
9056
|
+
if (seconds >= start && seconds <= end) {
|
|
9057
|
+
const duration = Math.max(0.000001, end - start);
|
|
9058
|
+
return [index, index + 1, Math.min(1, Math.max(0, (seconds - start) / duration))];
|
|
9059
|
+
}
|
|
9060
|
+
}
|
|
9061
|
+
return [lastIndex, lastIndex, 0];
|
|
9062
|
+
}
|
|
9063
|
+
function readGltfChannelValue(channel, keyframeIndex, components) {
|
|
9064
|
+
const cubic = channel.interpolation === "CUBICSPLINE";
|
|
9065
|
+
const stride = components * (cubic ? 3 : 1);
|
|
9066
|
+
const offset = keyframeIndex * stride + (cubic ? components : 0);
|
|
9067
|
+
if (components === 4) {
|
|
9068
|
+
return [
|
|
9069
|
+
channel.values[offset] ?? 0,
|
|
9070
|
+
channel.values[offset + 1] ?? 0,
|
|
9071
|
+
channel.values[offset + 2] ?? 0,
|
|
9072
|
+
channel.values[offset + 3] ?? 1
|
|
9073
|
+
];
|
|
9074
|
+
}
|
|
9075
|
+
return [
|
|
9076
|
+
channel.values[offset] ?? 0,
|
|
9077
|
+
channel.values[offset + 1] ?? 0,
|
|
9078
|
+
channel.values[offset + 2] ?? 0
|
|
9079
|
+
];
|
|
9080
|
+
}
|
|
9081
|
+
function normalizeQuaternion(rotation) {
|
|
9082
|
+
const length = Math.hypot(rotation[0] ?? 0, rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 1) || 1;
|
|
9083
|
+
return [
|
|
9084
|
+
(rotation[0] ?? 0) / length,
|
|
9085
|
+
(rotation[1] ?? 0) / length,
|
|
9086
|
+
(rotation[2] ?? 0) / length,
|
|
9087
|
+
(rotation[3] ?? 1) / length
|
|
9088
|
+
];
|
|
9089
|
+
}
|
|
9090
|
+
function slerpQuaternion(a, b, t) {
|
|
9091
|
+
let bx = b[0];
|
|
9092
|
+
let by = b[1];
|
|
9093
|
+
let bz = b[2];
|
|
9094
|
+
let bw = b[3];
|
|
9095
|
+
let dot = a[0] * bx + a[1] * by + a[2] * bz + a[3] * bw;
|
|
9096
|
+
if (dot < 0) {
|
|
9097
|
+
dot = -dot;
|
|
9098
|
+
bx = -bx;
|
|
9099
|
+
by = -by;
|
|
9100
|
+
bz = -bz;
|
|
9101
|
+
bw = -bw;
|
|
9102
|
+
}
|
|
9103
|
+
if (dot > 0.9995) {
|
|
9104
|
+
return normalizeQuaternion([
|
|
9105
|
+
a[0] + (bx - a[0]) * t,
|
|
9106
|
+
a[1] + (by - a[1]) * t,
|
|
9107
|
+
a[2] + (bz - a[2]) * t,
|
|
9108
|
+
a[3] + (bw - a[3]) * t
|
|
9109
|
+
]);
|
|
9110
|
+
}
|
|
9111
|
+
const theta = Math.acos(Math.min(1, Math.max(-1, dot)));
|
|
9112
|
+
const sinTheta = Math.sin(theta) || 1;
|
|
9113
|
+
const wa = Math.sin((1 - t) * theta) / sinTheta;
|
|
9114
|
+
const wb = Math.sin(t * theta) / sinTheta;
|
|
9115
|
+
return normalizeQuaternion([
|
|
9116
|
+
a[0] * wa + bx * wb,
|
|
9117
|
+
a[1] * wa + by * wb,
|
|
9118
|
+
a[2] * wa + bz * wb,
|
|
9119
|
+
a[3] * wa + bw * wb
|
|
9120
|
+
]);
|
|
9121
|
+
}
|
|
6812
9122
|
function rotationQuaternion(rotation) {
|
|
6813
9123
|
const length = Math.hypot(rotation[0] ?? 0, rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 1) || 1;
|
|
6814
9124
|
const x = (rotation[0] ?? 0) / length;
|
|
@@ -6878,10 +9188,10 @@ function mergeBounds(a, b) {
|
|
|
6878
9188
|
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])]
|
|
6879
9189
|
};
|
|
6880
9190
|
}
|
|
6881
|
-
function createViewProjection(snapshot, aspect, time) {
|
|
9191
|
+
function createViewProjection(snapshot, aspect, time, runtimeNodes) {
|
|
6882
9192
|
const cameraSpec = snapshot.camera;
|
|
6883
|
-
const target = resolveCameraTarget(snapshot, cameraSpec);
|
|
6884
|
-
const eye = resolveCameraEye(snapshot, cameraSpec, time);
|
|
9193
|
+
const target = resolveCameraTarget(snapshot, cameraSpec, runtimeNodes);
|
|
9194
|
+
const eye = resolveCameraEye(snapshot, cameraSpec, time, runtimeNodes);
|
|
6885
9195
|
const view = lookAt(eye, target, [0, 1, 0]);
|
|
6886
9196
|
const projection = perspective(((cameraSpec.fov ?? 45) * Math.PI) / 180, aspect, 0.05, 100);
|
|
6887
9197
|
return multiply4(projection, view);
|
|
@@ -6892,7 +9202,7 @@ function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
|
|
|
6892
9202
|
Math.max(0.001, bounds.max[1] - bounds.min[1]),
|
|
6893
9203
|
Math.max(0.001, bounds.max[2] - bounds.min[2])
|
|
6894
9204
|
];
|
|
6895
|
-
const fitScale =
|
|
9205
|
+
const fitScale = resolveModelFitScale(node, extent, normalizeToUnit);
|
|
6896
9206
|
const centerX = (bounds.min[0] + bounds.max[0]) / 2;
|
|
6897
9207
|
const centerZ = (bounds.min[2] + bounds.max[2]) / 2;
|
|
6898
9208
|
const baseSize = node?.kind === "primitive" ? primitiveSize(node) : [1, 1, 1];
|
|
@@ -6901,6 +9211,23 @@ function createModelMatrix(node, bounds, normalizeToUnit, time = 0) {
|
|
|
6901
9211
|
const rotation = animatedRotation(node, time);
|
|
6902
9212
|
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())));
|
|
6903
9213
|
}
|
|
9214
|
+
function resolveModelFitScale(node, extent, normalizeToUnit) {
|
|
9215
|
+
if (node?.kind === "model") {
|
|
9216
|
+
if (isPositiveFinite(node.targetHeight))
|
|
9217
|
+
return node.targetHeight / extent[1];
|
|
9218
|
+
if (isPositiveFinite(node.targetLength))
|
|
9219
|
+
return node.targetLength / Math.max(extent[0], extent[2]);
|
|
9220
|
+
if (isPositiveFinite(node.targetMaxDimension))
|
|
9221
|
+
return node.targetMaxDimension / Math.max(extent[0], extent[1], extent[2]);
|
|
9222
|
+
}
|
|
9223
|
+
return normalizeToUnit ? AURA_NORMALIZED_MODEL_MAX_DIMENSION / Math.max(extent[0], extent[1], extent[2]) : 1;
|
|
9224
|
+
}
|
|
9225
|
+
function isPositiveFinite(value) {
|
|
9226
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
9227
|
+
}
|
|
9228
|
+
function shouldNormalizeModelNode(node) {
|
|
9229
|
+
return node?.scaleMode !== "world";
|
|
9230
|
+
}
|
|
6904
9231
|
function animatedPosition(node, time) {
|
|
6905
9232
|
const basePosition = node?.position ?? [0, 0, 0];
|
|
6906
9233
|
if (!node?.animation)
|
|
@@ -6918,6 +9245,8 @@ function animatedRotation(node, time) {
|
|
|
6918
9245
|
const baseRotation = node?.rotation ?? [0, 0, 0];
|
|
6919
9246
|
if (!node?.animation)
|
|
6920
9247
|
return baseRotation;
|
|
9248
|
+
if (node.kind === "model" && !isModelTransformAnimationClip(node.animation.clip))
|
|
9249
|
+
return baseRotation;
|
|
6921
9250
|
const speed = Math.max(0.05, node.animation.speed ?? 1);
|
|
6922
9251
|
const seconds = resolveAnimationSeconds(node.animation, time);
|
|
6923
9252
|
if (node.animation.clip === "turntable") {
|
|
@@ -6933,6 +9262,9 @@ function animatedRotation(node, time) {
|
|
|
6933
9262
|
return baseRotation;
|
|
6934
9263
|
return [baseRotation[0], baseRotation[1] + seconds * speed, baseRotation[2]];
|
|
6935
9264
|
}
|
|
9265
|
+
function isModelTransformAnimationClip(clip) {
|
|
9266
|
+
return clip === "turntable" || clip === "float" || clip === "orbit";
|
|
9267
|
+
}
|
|
6936
9268
|
function primitiveSize(node) {
|
|
6937
9269
|
if (typeof node.size === "number")
|
|
6938
9270
|
return [node.size, node.size, node.size];
|
|
@@ -7215,7 +9547,7 @@ function validateMaterialTexture(materialSpec) {
|
|
|
7215
9547
|
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.`);
|
|
7216
9548
|
}
|
|
7217
9549
|
}
|
|
7218
|
-
function createInitialDiagnostics(snapshot) {
|
|
9550
|
+
function createInitialDiagnostics(snapshot, rendererOptions) {
|
|
7219
9551
|
return {
|
|
7220
9552
|
backend: "headless",
|
|
7221
9553
|
fps: 0,
|
|
@@ -7223,7 +9555,7 @@ function createInitialDiagnostics(snapshot) {
|
|
|
7223
9555
|
renderSize: [0, 0],
|
|
7224
9556
|
assets: [],
|
|
7225
9557
|
evidence: collectAuraSceneEvidence(snapshot),
|
|
7226
|
-
renderer: createRendererDiagnosticReport(snapshot),
|
|
9558
|
+
renderer: createRendererDiagnosticReport(snapshot, undefined, rendererOptions),
|
|
7227
9559
|
warnings: snapshot.nodes.length === 0 ? ["Scene contains no renderable nodes. Add model(assets.assetId) or primitives.box()."] : [],
|
|
7228
9560
|
errors: []
|
|
7229
9561
|
};
|
|
@@ -7250,7 +9582,7 @@ function collectGeneratedCodeWarnings(snapshot) {
|
|
|
7250
9582
|
warnings.push("Scene has no interactions. Suggested fix: add interactions.orbit() for product/viewer scenes.");
|
|
7251
9583
|
}
|
|
7252
9584
|
for (const node of snapshot.nodes) {
|
|
7253
|
-
if (node.kind === "model" && node.asset.
|
|
9585
|
+
if (node.kind === "model" && createAssetProvenance(node.asset).source === "unsafe-url") {
|
|
7254
9586
|
warnings.push(`Model uses unsafeModelUrl("${node.asset.url}"). Suggested fix: run assets add and use typed assets.`);
|
|
7255
9587
|
}
|
|
7256
9588
|
}
|