@aura3d/engine 1.0.2 → 1.0.3

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.
Files changed (107) hide show
  1. package/README.md +29 -16
  2. package/dist/engine/agent-api/humanoid-walk-runtime.d.ts +17 -81
  3. package/dist/engine/agent-api/humanoid-walk-runtime.d.ts.map +1 -1
  4. package/dist/engine/agent-api/humanoid-walk-runtime.js +4 -279
  5. package/dist/engine/agent-api/humanoid-walk-runtime.js.map +1 -1
  6. package/dist/engine/agent-api/index.d.ts +3 -3
  7. package/dist/engine/agent-api/index.d.ts.map +1 -1
  8. package/dist/engine/agent-api/index.js +19 -1796
  9. package/dist/engine/agent-api/index.js.map +1 -1
  10. package/dist/engine/agent-api/particle-fountain-runtime.d.ts +5 -80
  11. package/dist/engine/agent-api/particle-fountain-runtime.d.ts.map +1 -1
  12. package/dist/engine/agent-api/particle-fountain-runtime.js +7 -291
  13. package/dist/engine/agent-api/particle-fountain-runtime.js.map +1 -1
  14. package/dist/engine/agent-api/product-viewer-runtime.d.ts +17 -107
  15. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  16. package/dist/engine/agent-api/product-viewer-runtime.js +4 -330
  17. package/dist/engine/agent-api/product-viewer-runtime.js.map +1 -1
  18. package/package.json +4 -6
  19. package/templates/product-viewer/src/main.ts +1 -0
  20. package/dist/three-compat/ThreeApiInventory.d.ts +0 -18
  21. package/dist/three-compat/ThreeApiInventory.d.ts.map +0 -1
  22. package/dist/three-compat/ThreeApiInventory.js +0 -99
  23. package/dist/three-compat/ThreeApiInventory.js.map +0 -1
  24. package/dist/three-compat/ThreeCompatibilityMatrix.d.ts +0 -30
  25. package/dist/three-compat/ThreeCompatibilityMatrix.d.ts.map +0 -1
  26. package/dist/three-compat/ThreeCompatibilityMatrix.js +0 -69
  27. package/dist/three-compat/ThreeCompatibilityMatrix.js.map +0 -1
  28. package/dist/three-compat/animation/index.d.ts +0 -3
  29. package/dist/three-compat/animation/index.d.ts.map +0 -1
  30. package/dist/three-compat/animation/index.js +0 -2
  31. package/dist/three-compat/animation/index.js.map +0 -1
  32. package/dist/three-compat/cameras/index.d.ts +0 -24
  33. package/dist/three-compat/cameras/index.d.ts.map +0 -1
  34. package/dist/three-compat/cameras/index.js +0 -44
  35. package/dist/three-compat/cameras/index.js.map +0 -1
  36. package/dist/three-compat/controls/index.d.ts +0 -3
  37. package/dist/three-compat/controls/index.d.ts.map +0 -1
  38. package/dist/three-compat/controls/index.js +0 -2
  39. package/dist/three-compat/controls/index.js.map +0 -1
  40. package/dist/three-compat/core/Object3DCompat.d.ts +0 -62
  41. package/dist/three-compat/core/Object3DCompat.d.ts.map +0 -1
  42. package/dist/three-compat/core/Object3DCompat.js +0 -112
  43. package/dist/three-compat/core/Object3DCompat.js.map +0 -1
  44. package/dist/three-compat/core/RaycasterCompat.d.ts +0 -18
  45. package/dist/three-compat/core/RaycasterCompat.d.ts.map +0 -1
  46. package/dist/three-compat/core/RaycasterCompat.js +0 -28
  47. package/dist/three-compat/core/RaycasterCompat.js.map +0 -1
  48. package/dist/three-compat/core/SceneCompat.d.ts +0 -8
  49. package/dist/three-compat/core/SceneCompat.d.ts.map +0 -1
  50. package/dist/three-compat/core/SceneCompat.js +0 -8
  51. package/dist/three-compat/core/SceneCompat.js.map +0 -1
  52. package/dist/three-compat/geometries/index.d.ts +0 -67
  53. package/dist/three-compat/geometries/index.d.ts.map +0 -1
  54. package/dist/three-compat/geometries/index.js +0 -114
  55. package/dist/three-compat/geometries/index.js.map +0 -1
  56. package/dist/three-compat/helpers/index.d.ts +0 -40
  57. package/dist/three-compat/helpers/index.d.ts.map +0 -1
  58. package/dist/three-compat/helpers/index.js +0 -129
  59. package/dist/three-compat/helpers/index.js.map +0 -1
  60. package/dist/three-compat/index.d.ts +0 -32
  61. package/dist/three-compat/index.d.ts.map +0 -1
  62. package/dist/three-compat/index.js +0 -22
  63. package/dist/three-compat/index.js.map +0 -1
  64. package/dist/three-compat/lights/index.d.ts +0 -35
  65. package/dist/three-compat/lights/index.d.ts.map +0 -1
  66. package/dist/three-compat/lights/index.js +0 -39
  67. package/dist/three-compat/lights/index.js.map +0 -1
  68. package/dist/three-compat/loaders/index.d.ts +0 -54
  69. package/dist/three-compat/loaders/index.d.ts.map +0 -1
  70. package/dist/three-compat/loaders/index.js +0 -79
  71. package/dist/three-compat/loaders/index.js.map +0 -1
  72. package/dist/three-compat/materials/index.d.ts +0 -70
  73. package/dist/three-compat/materials/index.d.ts.map +0 -1
  74. package/dist/three-compat/materials/index.js +0 -85
  75. package/dist/three-compat/materials/index.js.map +0 -1
  76. package/dist/three-compat/math/index.d.ts +0 -34
  77. package/dist/three-compat/math/index.d.ts.map +0 -1
  78. package/dist/three-compat/math/index.js +0 -90
  79. package/dist/three-compat/math/index.js.map +0 -1
  80. package/dist/three-compat/migration/CompatibilityWarnings.d.ts +0 -6
  81. package/dist/three-compat/migration/CompatibilityWarnings.d.ts.map +0 -1
  82. package/dist/three-compat/migration/CompatibilityWarnings.js +0 -8
  83. package/dist/three-compat/migration/CompatibilityWarnings.js.map +0 -1
  84. package/dist/three-compat/migration/ImportMap.d.ts +0 -2
  85. package/dist/three-compat/migration/ImportMap.d.ts.map +0 -1
  86. package/dist/three-compat/migration/ImportMap.js +0 -10
  87. package/dist/three-compat/migration/ImportMap.js.map +0 -1
  88. package/dist/three-compat/migration/ThreeToA3DAdapter.d.ts +0 -8
  89. package/dist/three-compat/migration/ThreeToA3DAdapter.d.ts.map +0 -1
  90. package/dist/three-compat/migration/ThreeToA3DAdapter.js +0 -20
  91. package/dist/three-compat/migration/ThreeToA3DAdapter.js.map +0 -1
  92. package/dist/three-compat/postprocessing/index.d.ts +0 -2
  93. package/dist/three-compat/postprocessing/index.d.ts.map +0 -1
  94. package/dist/three-compat/postprocessing/index.js +0 -2
  95. package/dist/three-compat/postprocessing/index.js.map +0 -1
  96. package/dist/three-compat/render-targets/index.d.ts +0 -17
  97. package/dist/three-compat/render-targets/index.d.ts.map +0 -1
  98. package/dist/three-compat/render-targets/index.js +0 -29
  99. package/dist/three-compat/render-targets/index.js.map +0 -1
  100. package/dist/three-compat/shaders/index.d.ts +0 -3
  101. package/dist/three-compat/shaders/index.d.ts.map +0 -1
  102. package/dist/three-compat/shaders/index.js +0 -3
  103. package/dist/three-compat/shaders/index.js.map +0 -1
  104. package/dist/three-compat/textures/index.d.ts +0 -19
  105. package/dist/three-compat/textures/index.d.ts.map +0 -1
  106. package/dist/three-compat/textures/index.js +0 -24
  107. package/dist/three-compat/textures/index.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { PhysicsDebugDraw, PhysicsStepper, PhysicsWorld, ScenePhysicsBridge, Shape as PhysicsShapeFactory } from "../../physics/index.js";
2
+ export { Engine } from "../../core/index.js";
2
3
  const auraAssetRefBrand = Symbol("AuraAssetRef");
3
4
  export function defineAuraAssets(definitions) {
4
5
  const refs = {};
@@ -1145,7 +1146,7 @@ const rendererColorManagementPreset = {
1145
1146
  toneMapping: "aces-filmic",
1146
1147
  defaultExposure: 1.05,
1147
1148
  notes: [
1148
- "Three.js renderer uses SRGBColorSpace output and ACESFilmicToneMapping.",
1149
+ "Aura3D WebGL2 renderer uses sRGB output and ACES filmic tone mapping.",
1149
1150
  "Exposure is selected by scene category to avoid blown-out product/material whites and crushed dark scenes."
1150
1151
  ]
1151
1152
  };
@@ -1237,7 +1238,7 @@ function createRendererDiagnosticReport(snapshot, runtime) {
1237
1238
  intensity: environment?.intensity,
1238
1239
  evidence: environment
1239
1240
  ? `${environment.environment} IBL requested at intensity ${environment.intensity}`
1240
- : "procedural fallback environment requested only; runtime PMREM status is unavailable until render"
1241
+ : "procedural fallback environment requested only; runtime environment prefilter status is unavailable until render"
1241
1242
  };
1242
1243
  const postprocessEvidence = !postprocessRequested
1243
1244
  ? "no renderer postprocess effects requested"
@@ -3550,7 +3551,7 @@ function collectCityInstancingPlan(nodes) {
3550
3551
  ].filter(Boolean);
3551
3552
  return {
3552
3553
  kind: "aura-city-instancing-plan",
3553
- rendererPath: "createThreePrimitiveBatches",
3554
+ rendererPath: "auraWebGL2PrimitiveBatches",
3554
3555
  windows,
3555
3556
  props,
3556
3557
  roadMarkings,
@@ -4730,7 +4731,11 @@ function collectHelperPerformanceBudgets(nodes) {
4730
4731
  const helpers = new Set();
4731
4732
  if (hasName("visible rigid body cube") || hasName("physics collider debug line"))
4732
4733
  helpers.add("physicsPlayground");
4733
- if (hasName("particle collision ground plane") || hasName("gravity fountain plume"))
4734
+ if (hasName("particle collision ground plane") ||
4735
+ hasName("fountain collision ground plane") ||
4736
+ hasName("particle emission nozzle") ||
4737
+ hasName("fountain droplet plume") ||
4738
+ hasName("gravity fountain plume"))
4734
4739
  helpers.add("particleFountain");
4735
4740
  if (hasName("smooth orbit ring") || hasName("readable planet label"))
4736
4741
  helpers.add("solarSystem");
@@ -4854,14 +4859,7 @@ function shouldContinuouslyRender(snapshot) {
4854
4859
  });
4855
4860
  }
4856
4861
  async function createProductionSceneRenderer(canvas, snapshot) {
4857
- try {
4858
- return await createThreeSceneRenderer(canvas, snapshot);
4859
- }
4860
- catch (error) {
4861
- if (typeof console !== "undefined")
4862
- console.warn("Aura3D Three.js renderer fallback:", error);
4863
- return await createWebGLSceneRenderer(canvas, snapshot);
4864
- }
4862
+ return await createWebGLSceneRenderer(canvas, snapshot);
4865
4863
  }
4866
4864
  function colorToClearColor(color) {
4867
4865
  if (typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color)) {
@@ -4870,672 +4868,10 @@ function colorToClearColor(color) {
4870
4868
  }
4871
4869
  return [0.02, 0.025, 0.035, 1];
4872
4870
  }
4873
- function resolveThreeToneMappingExposure(snapshot) {
4874
- const names = groups.flatten(snapshot.nodes).map((node) => "name" in node ? node.name?.toLowerCase() ?? "" : "");
4875
- const category = resolveRendererSceneCategory(snapshot, names);
4876
- if (sceneExposurePresets[category])
4877
- return sceneExposurePresets[category].exposure;
4878
- const [r, g, b] = colorToClearColor(snapshot.background);
4879
- const luminance = r * 0.2126 + g * 0.7152 + b * 0.0722;
4880
- const hasProductOrMaterialLighting = snapshot.nodes.some((node) => node.kind === "environment" && (node.environment === "studio" || node.environment === "material-lab" || node.environment === "product-hero"));
4881
- const hasDarkCinematicCues = snapshot.nodes.some((node) => (node.kind === "effect" && (node.effect === "fog" || node.effect === "bloom")) ||
4882
- (node.kind === "primitive" && node.material?.emissive !== undefined));
4883
- if (hasProductOrMaterialLighting || luminance > 0.72)
4884
- return 0.92;
4885
- if (luminance < 0.08 && hasDarkCinematicCues)
4886
- return 1.34;
4887
- if (luminance < 0.16)
4888
- return 1.18;
4889
- return 1.05;
4890
- }
4891
4871
  function colorWithAlpha(color, alpha) {
4892
4872
  const [r, g, b] = colorToClearColor(color);
4893
4873
  return `rgba(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)},${Math.min(1, Math.max(0, alpha))})`;
4894
4874
  }
4895
- async function createThreeSceneRenderer(canvas, snapshot) {
4896
- const THREE = await import("three");
4897
- const hasRenderableModels = groups.flatten(snapshot.nodes).some((node) => node.kind === "model" && isRenderableModelNode(node));
4898
- const GLTFLoader = hasRenderableModels
4899
- ? (await import("three/examples/jsm/loaders/GLTFLoader.js")).GLTFLoader
4900
- : undefined;
4901
- const renderer = new THREE.WebGLRenderer({
4902
- canvas,
4903
- antialias: true,
4904
- alpha: false,
4905
- preserveDrawingBuffer: true,
4906
- powerPreference: "high-performance"
4907
- });
4908
- renderer.setPixelRatio(Math.min(2, Math.max(1, window.devicePixelRatio || 1)));
4909
- renderer.setSize(canvas.width, canvas.height, false);
4910
- renderer.outputColorSpace = THREE.SRGBColorSpace;
4911
- renderer.toneMapping = THREE.ACESFilmicToneMapping;
4912
- renderer.toneMappingExposure = resolveThreeToneMappingExposure(snapshot);
4913
- renderer.shadowMap.enabled = true;
4914
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
4915
- const threeScene = new THREE.Scene();
4916
- threeScene.background = new THREE.Color(snapshot.background);
4917
- const materialInspectionEnvironment = await createThreeMaterialInspectionEnvironment(THREE, renderer, snapshot);
4918
- if (materialInspectionEnvironment) {
4919
- threeScene.environment = materialInspectionEnvironment.texture;
4920
- if ("environmentIntensity" in threeScene) {
4921
- threeScene.environmentIntensity = materialInspectionEnvironment.intensity;
4922
- }
4923
- }
4924
- const fog = snapshot.nodes.find((node) => node.kind === "effect" && node.effect === "fog");
4925
- if (fog) {
4926
- threeScene.fog = new THREE.FogExp2(new THREE.Color(fog.color ?? "#9fb7d9"), Math.max(0.004, (fog.density ?? 0.1) * 0.045));
4927
- }
4928
- addThreeLights(THREE, threeScene, snapshot.nodes);
4929
- const loader = GLTFLoader ? new GLTFLoader() : undefined;
4930
- const disposables = materialInspectionEnvironment
4931
- ? [materialInspectionEnvironment.texture, materialInspectionEnvironment.generator]
4932
- : [];
4933
- const frameUpdaters = [];
4934
- const primitiveBatchNodes = [];
4935
- const namedObjects = new Map();
4936
- const hoverRuntimeNames = collectThreeHoverRuntimeObjectNames(snapshot);
4937
- const miniGolfRuntimeRequested = hasThreeMiniGolfRuntimeInteraction(snapshot);
4938
- const runtimePhysics = miniGolfRuntimeRequested ? undefined : createRuntimeScenePhysics(snapshot);
4939
- const effectNodes = collectThreeEffectNodes(snapshot);
4940
- for (const node of snapshot.nodes) {
4941
- if (node.kind === "label") {
4942
- // HUD labels are screen-space overlays, not world geometry. Rendering them as
4943
- // 3D sprites produced giant text clipping across the scene, so skip them here.
4944
- if (node.label === "hud")
4945
- continue;
4946
- const label = createThreeLabelNode(THREE, node);
4947
- threeScene.add(label);
4948
- registerThreeNamedObject(namedObjects, node, label);
4949
- registerThreeNodeAnimation(label, node, frameUpdaters);
4950
- disposables.push(label, label.material, label.material.map);
4951
- continue;
4952
- }
4953
- if (node.kind === "model" && isRenderableModelNode(node)) {
4954
- if (!loader) {
4955
- throw createAuraAssetLoadError(node.asset, "the GLB loader was not initialized for this scene");
4956
- }
4957
- const gltf = await loader.loadAsync(new URL(node.asset.url, document.baseURI).href);
4958
- const modelRoot = gltf.scene ?? gltf.scenes?.[0];
4959
- if (!modelRoot) {
4960
- throw createAuraAssetLoadError(node.asset, "the GLB loaded but did not contain a default scene");
4961
- }
4962
- modelRoot.traverse((child) => {
4963
- if (!child.isMesh)
4964
- return;
4965
- child.castShadow = node.castShadow;
4966
- child.receiveShadow = node.receiveShadow;
4967
- if (node.material)
4968
- child.material = createThreeMaterial(THREE, node.material);
4969
- else if (child.material)
4970
- enhanceLoadedMaterial(child.material);
4971
- disposables.push(child.geometry, child.material);
4972
- });
4973
- const pivot = normalizeThreeModel(THREE, modelRoot, node);
4974
- threeScene.add(pivot);
4975
- registerThreeNamedObject(namedObjects, node, pivot);
4976
- runtimePhysics?.bindObject(node, pivot);
4977
- registerThreeModelClipAnimation(THREE, gltf, modelRoot, node, frameUpdaters, disposables);
4978
- registerThreeNodeAnimation(pivot, node, frameUpdaters);
4979
- disposables.push(modelRoot);
4980
- continue;
4981
- }
4982
- if (node.kind === "primitive") {
4983
- if (isThreeReadableTextLabel(node)) {
4984
- const label = createThreeReadableTextLabel(THREE, node);
4985
- threeScene.add(label);
4986
- registerThreeNamedObject(namedObjects, node, label);
4987
- disposables.push(label, label.material, label.material.map);
4988
- continue;
4989
- }
4990
- if (!node.animation && !node.physics && !isThreeMiniGolfRuntimePrimitive(node) && !hoverRuntimeNames.has(node.name ?? "")) {
4991
- primitiveBatchNodes.push(node);
4992
- continue;
4993
- }
4994
- const mesh = createThreePrimitive(THREE, node);
4995
- threeScene.add(mesh);
4996
- registerThreeNamedObject(namedObjects, node, mesh);
4997
- runtimePhysics?.bindObject(node, mesh);
4998
- registerThreeNodeAnimation(mesh, node, frameUpdaters);
4999
- disposables.push(mesh.geometry, mesh.material);
5000
- continue;
5001
- }
5002
- }
5003
- for (const batch of createThreePrimitiveBatches(THREE, primitiveBatchNodes)) {
5004
- threeScene.add(batch);
5005
- disposables.push(batch, batch.geometry, batch.material);
5006
- }
5007
- const contactOcclusionEffect = effectNodes.find((node) => node.effect === "contact-occlusion");
5008
- let contactReceiverAdded = false;
5009
- if (contactOcclusionEffect) {
5010
- const contactReceiver = createThreeContactShadowReceiver(THREE, snapshot, contactOcclusionEffect);
5011
- threeScene.add(contactReceiver);
5012
- disposables.push(contactReceiver, contactReceiver.geometry, contactReceiver.material);
5013
- contactReceiverAdded = true;
5014
- }
5015
- const bloomEffect = effectNodes.find((node) => node.effect === "bloom");
5016
- const rainEffect = effectNodes.find((node) => node.effect === "rain");
5017
- if (rainEffect) {
5018
- const rain = createThreeRain(THREE, rainEffect);
5019
- threeScene.add(rain);
5020
- disposables.push(rain, rain.userData.mistTexture);
5021
- if (typeof rain.userData.update === "function")
5022
- frameUpdaters.push(rain.userData.update);
5023
- }
5024
- const particleEffects = effectNodes.filter((node) => node.effect === "particles");
5025
- for (const particleEffect of particleEffects) {
5026
- const particles = createThreeParticles(THREE, particleEffect);
5027
- threeScene.add(particles);
5028
- disposables.push(particles, particles.geometry, particles.material);
5029
- if (typeof particles.userData.update === "function")
5030
- frameUpdaters.push(particles.userData.update);
5031
- }
5032
- const cameraObject = new THREE.PerspectiveCamera(snapshot.camera.fov ?? 45, canvas.width / Math.max(1, canvas.height), 0.05, 100);
5033
- const postProcess = hasThreePostProcessEffects(effectNodes)
5034
- ? await createThreePostProcessPipeline(THREE, renderer, threeScene, cameraObject, snapshot)
5035
- : null;
5036
- let fallbackBloomAdded = false;
5037
- if (bloomEffect && !postProcess) {
5038
- const bloom = createThreeBloom(THREE, snapshot, bloomEffect);
5039
- threeScene.add(bloom);
5040
- disposables.push(bloom, bloom.userData.texture);
5041
- if (typeof bloom.userData.update === "function")
5042
- frameUpdaters.push(bloom.userData.update);
5043
- fallbackBloomAdded = true;
5044
- }
5045
- const miniGolfRuntime = createThreeMiniGolfRuntime(canvas, snapshot, namedObjects);
5046
- if (miniGolfRuntime)
5047
- frameUpdaters.push((time) => miniGolfRuntime.update(time));
5048
- const hoverRuntime = createThreeHoverRuntime(THREE, canvas, cameraObject, snapshot, namedObjects);
5049
- const actualPasses = [
5050
- ...(postProcess?.passNames ?? []),
5051
- ...(contactReceiverAdded ? ["contact-shadow-receiver"] : [])
5052
- ];
5053
- const fallbackPasses = fallbackBloomAdded ? ["sprite-bloom-halo"] : [];
5054
- const runtimeRendererDiagnostics = createRendererDiagnosticReport(snapshot, {
5055
- mounted: true,
5056
- backend: "three-webgl",
5057
- postprocess: {
5058
- renderPass: postProcess?.passNames.includes("render") ?? false,
5059
- outputPass: postProcess?.passNames.includes("output") ?? false,
5060
- bloomPass: postProcess?.passNames.includes("bloom") ?? false,
5061
- ambientOcclusionPass: postProcess?.passNames.includes("ssao") ?? false,
5062
- contactOcclusionReceiver: contactReceiverAdded,
5063
- pixelBacked: actualPasses.length > 0,
5064
- actualPasses,
5065
- fallbackPasses
5066
- },
5067
- environment: {
5068
- enabled: Boolean(materialInspectionEnvironment),
5069
- preset: materialInspectionEnvironment?.preset,
5070
- intensity: materialInspectionEnvironment?.intensity,
5071
- evidence: materialInspectionEnvironment
5072
- ? `runtime PMREM environment initialized from ${materialInspectionEnvironment.preset}`
5073
- : "no runtime PMREM/IBL environment initialized"
5074
- },
5075
- warnings: postProcess?.warnings ?? []
5076
- });
5077
- let lastPhysicsTime = 0;
5078
- return {
5079
- diagnostics: runtimeRendererDiagnostics,
5080
- render(time) {
5081
- if (renderer.domElement.width !== canvas.width || renderer.domElement.height !== canvas.height) {
5082
- renderer.setSize(canvas.width, canvas.height, false);
5083
- postProcess?.setSize(canvas.width, canvas.height);
5084
- }
5085
- for (const update of frameUpdaters)
5086
- update(time);
5087
- const physicsDelta = lastPhysicsTime > 0 ? Math.max(0, (time - lastPhysicsTime) / 1000) : 1 / 60;
5088
- lastPhysicsTime = time;
5089
- runtimePhysics?.step(physicsDelta);
5090
- updateThreeCamera(THREE, cameraObject, snapshot, canvas, time);
5091
- if (postProcess)
5092
- postProcess.render();
5093
- else
5094
- renderer.render(threeScene, cameraObject);
5095
- return Math.max(1, renderer.info.render.calls);
5096
- },
5097
- dispose() {
5098
- miniGolfRuntime?.dispose();
5099
- hoverRuntime?.dispose();
5100
- for (const item of disposables)
5101
- disposeThreeResource(item);
5102
- postProcess?.dispose();
5103
- renderer.dispose();
5104
- }
5105
- };
5106
- }
5107
- function registerThreeNamedObject(registry, node, object) {
5108
- const name = "name" in node ? node.name : undefined;
5109
- if (name)
5110
- registry.set(name, object);
5111
- }
5112
- function collectThreeHoverRuntimeInteractions(snapshot) {
5113
- return groups.flatten(snapshot.nodes).filter((node) => node.kind === "interaction" &&
5114
- node.mode === "hover" &&
5115
- Boolean(node.target ?? node.selected));
5116
- }
5117
- function collectThreeHoverRuntimeObjectNames(snapshot) {
5118
- const names = new Set();
5119
- for (const interaction of collectThreeHoverRuntimeInteractions(snapshot)) {
5120
- if (interaction.target)
5121
- names.add(interaction.target);
5122
- if (interaction.selected)
5123
- names.add(interaction.selected);
5124
- }
5125
- return names;
5126
- }
5127
- function createThreeHoverRuntime(THREE, canvas, cameraObject, snapshot, objects) {
5128
- const interactions = collectThreeHoverRuntimeInteractions(snapshot);
5129
- if (interactions.length === 0)
5130
- return undefined;
5131
- const entries = interactions
5132
- .map((interaction) => {
5133
- const targetName = interaction.target ?? interaction.selected;
5134
- const selectedName = interaction.selected ?? interaction.target;
5135
- return {
5136
- targetName,
5137
- selectedName,
5138
- target: targetName ? objects.get(targetName) : undefined,
5139
- selected: selectedName ? objects.get(selectedName) : undefined
5140
- };
5141
- })
5142
- .filter((entry) => Boolean(entry.targetName && entry.selectedName && entry.target && entry.selected));
5143
- if (entries.length === 0)
5144
- return undefined;
5145
- const ownerWindow = canvas.ownerDocument?.defaultView;
5146
- const pointer = new THREE.Vector2();
5147
- const raycaster = new THREE.Raycaster();
5148
- const baseScales = new Map();
5149
- let activeEntry;
5150
- let pointerState = { x: 0, y: 0 };
5151
- const setHighlighted = (object, highlighted) => {
5152
- if (!object)
5153
- return;
5154
- if (!baseScales.has(object) && typeof object.scale?.clone === "function")
5155
- baseScales.set(object, object.scale.clone());
5156
- const baseScale = baseScales.get(object);
5157
- if (baseScale && object.scale) {
5158
- object.scale.copy(baseScale);
5159
- if (highlighted)
5160
- object.scale.multiplyScalar(1.1);
5161
- }
5162
- object.traverse?.((child) => {
5163
- const materials = Array.isArray(child.material) ? child.material : child.material ? [child.material] : [];
5164
- for (const materialValue of materials) {
5165
- if (!materialValue || typeof materialValue !== "object")
5166
- continue;
5167
- if (materialValue.userData?.auraHoverBaseEmissiveIntensity === undefined) {
5168
- materialValue.userData = materialValue.userData ?? {};
5169
- materialValue.userData.auraHoverBaseEmissiveIntensity = materialValue.emissiveIntensity ?? 0;
5170
- }
5171
- if ("emissiveIntensity" in materialValue) {
5172
- const base = Number(materialValue.userData.auraHoverBaseEmissiveIntensity ?? 0);
5173
- materialValue.emissiveIntensity = highlighted ? Math.max(base, 0.9) : base;
5174
- }
5175
- materialValue.needsUpdate = true;
5176
- }
5177
- });
5178
- };
5179
- const publish = () => {
5180
- const state = {
5181
- kind: "aura-hover-browser-runtime",
5182
- mounted: true,
5183
- active: Boolean(activeEntry),
5184
- target: activeEntry?.targetName,
5185
- selected: activeEntry?.selectedName,
5186
- pointer: pointerState
5187
- };
5188
- canvas.dataset.auraHoverRuntime = "mounted";
5189
- canvas.dataset.auraHoverActive = String(state.active);
5190
- if (state.target)
5191
- canvas.dataset.auraHoverTarget = state.target;
5192
- else
5193
- delete canvas.dataset.auraHoverTarget;
5194
- if (state.selected)
5195
- canvas.dataset.auraHoverSelected = state.selected;
5196
- else
5197
- delete canvas.dataset.auraHoverSelected;
5198
- if (ownerWindow) {
5199
- ownerWindow.__AURA3D_HOVER__ = state;
5200
- }
5201
- };
5202
- const setActiveEntry = (next) => {
5203
- if (activeEntry?.selected === next?.selected) {
5204
- publish();
5205
- return;
5206
- }
5207
- if (activeEntry)
5208
- setHighlighted(activeEntry.selected, false);
5209
- activeEntry = next;
5210
- if (activeEntry)
5211
- setHighlighted(activeEntry.selected, true);
5212
- publish();
5213
- };
5214
- const resolveHitEntry = (hitObject) => entries.find((entry) => isThreeObjectOrDescendant(hitObject, entry.target));
5215
- const onPointerMove = (event) => {
5216
- const rect = canvas.getBoundingClientRect();
5217
- const width = Math.max(1, rect.width);
5218
- const height = Math.max(1, rect.height);
5219
- pointerState = { x: event.clientX - rect.left, y: event.clientY - rect.top };
5220
- pointer.x = (pointerState.x / width) * 2 - 1;
5221
- pointer.y = -(pointerState.y / height) * 2 + 1;
5222
- raycaster.setFromCamera(pointer, cameraObject);
5223
- const hits = raycaster.intersectObjects(entries.map((entry) => entry.target), true);
5224
- setActiveEntry(hits.length > 0 ? resolveHitEntry(hits[0]?.object) : undefined);
5225
- };
5226
- const onPointerLeave = () => setActiveEntry(undefined);
5227
- canvas.addEventListener("pointermove", onPointerMove);
5228
- canvas.addEventListener("pointerleave", onPointerLeave);
5229
- publish();
5230
- return {
5231
- dispose() {
5232
- canvas.removeEventListener("pointermove", onPointerMove);
5233
- canvas.removeEventListener("pointerleave", onPointerLeave);
5234
- setActiveEntry(undefined);
5235
- if (ownerWindow) {
5236
- const target = ownerWindow;
5237
- if (target.__AURA3D_HOVER__?.kind === "aura-hover-browser-runtime")
5238
- delete target.__AURA3D_HOVER__;
5239
- }
5240
- }
5241
- };
5242
- }
5243
- function isThreeObjectOrDescendant(object, root) {
5244
- let current = object;
5245
- while (current) {
5246
- if (current === root)
5247
- return true;
5248
- current = current.parent;
5249
- }
5250
- return false;
5251
- }
5252
- function hasThreeMiniGolfRuntimeInteraction(snapshot) {
5253
- return snapshot.nodes.some((node) => node.kind === "interaction" &&
5254
- (node.mode === "drag-vector" || node.mode === "click-impulse") &&
5255
- node.target === "white physics golf ball");
5256
- }
5257
- function isThreeMiniGolfRuntimePrimitive(node) {
5258
- return [
5259
- "white physics golf ball",
5260
- "ball contact shadow on felt",
5261
- "ball aim selection ring",
5262
- "cyan aim direction line",
5263
- "shot power meter fill",
5264
- "follow camera target beacon above ball"
5265
- ].includes(node.name ?? "");
5266
- }
5267
- function createThreeMiniGolfRuntime(canvas, snapshot, objects) {
5268
- if (!hasThreeMiniGolfRuntimeInteraction(snapshot))
5269
- return undefined;
5270
- const state = createMiniGolfStateController();
5271
- const ownerWindow = canvas.ownerDocument?.defaultView;
5272
- let metrics = state.snapshot();
5273
- let pointerStart;
5274
- let inputMode = "idle";
5275
- let lastShot = { vector: metrics.aimVector, power: 0 };
5276
- let lastUpdateTime = 0;
5277
- const setObjectPosition = (name, position) => {
5278
- const object = objects.get(name);
5279
- object?.position?.set(position[0], position[1], position[2]);
5280
- };
5281
- const syncObjects = () => {
5282
- const ball = metrics.ballPosition;
5283
- setObjectPosition("white physics golf ball", ball);
5284
- setObjectPosition("ball contact shadow on felt", [ball[0], 0.018, ball[2]]);
5285
- setObjectPosition("ball aim selection ring", [ball[0], 0.035, ball[2]]);
5286
- setObjectPosition("follow camera target beacon above ball", [ball[0], ball[1] + 0.46, ball[2]]);
5287
- setObjectPosition("cyan aim direction line", [ball[0] + metrics.aimVector[0] * 0.54, 0.085, ball[2] + metrics.aimVector[2] * 0.54]);
5288
- };
5289
- const publish = () => {
5290
- const runtimeState = {
5291
- kind: "aura-mini-golf-browser-runtime",
5292
- mounted: true,
5293
- inputMode,
5294
- shotVector: lastShot.vector,
5295
- shotPower: lastShot.power,
5296
- ...metrics
5297
- };
5298
- canvas.dataset.auraMiniGolfRuntime = "mounted";
5299
- canvas.dataset.auraMiniGolfShots = String(metrics.shots);
5300
- canvas.dataset.auraMiniGolfScore = String(metrics.score);
5301
- canvas.dataset.auraMiniGolfCupTriggered = String(metrics.cupTriggered);
5302
- if (ownerWindow) {
5303
- ownerWindow.__AURA3D_MINI_GOLF__ = runtimeState;
5304
- }
5305
- };
5306
- const onPointerDown = (event) => {
5307
- pointerStart = { x: event.clientX, y: event.clientY };
5308
- inputMode = "aiming";
5309
- canvas.setPointerCapture?.(event.pointerId);
5310
- publish();
5311
- };
5312
- const onPointerMove = (event) => {
5313
- if (!pointerStart)
5314
- return;
5315
- lastShot = miniGolfPointerShotFromDrag(pointerStart, { x: event.clientX, y: event.clientY });
5316
- inputMode = "aiming";
5317
- publish();
5318
- };
5319
- const onPointerUp = (event) => {
5320
- const start = pointerStart ?? { x: event.clientX - 64, y: event.clientY + 36 };
5321
- pointerStart = undefined;
5322
- if (metrics.cupTriggered)
5323
- metrics = state.reset();
5324
- lastShot = miniGolfPointerShotFromDrag(start, { x: event.clientX, y: event.clientY });
5325
- metrics = state.shoot(lastShot);
5326
- inputMode = "shot";
5327
- syncObjects();
5328
- publish();
5329
- canvas.releasePointerCapture?.(event.pointerId);
5330
- };
5331
- canvas.addEventListener("pointerdown", onPointerDown);
5332
- canvas.addEventListener("pointermove", onPointerMove);
5333
- canvas.addEventListener("pointerup", onPointerUp);
5334
- canvas.addEventListener("pointercancel", onPointerUp);
5335
- syncObjects();
5336
- publish();
5337
- return {
5338
- update(time) {
5339
- const elapsed = lastUpdateTime > 0 ? Math.max(0, time - lastUpdateTime) : 16.67;
5340
- lastUpdateTime = time;
5341
- if (metrics.shots > 0 && !metrics.settled && !metrics.cupTriggered) {
5342
- const steps = Math.max(1, Math.min(10, Math.round(elapsed / 16.67)));
5343
- metrics = state.step(steps);
5344
- inputMode = metrics.settled ? "idle" : "shot";
5345
- syncObjects();
5346
- publish();
5347
- }
5348
- },
5349
- dispose() {
5350
- canvas.removeEventListener("pointerdown", onPointerDown);
5351
- canvas.removeEventListener("pointermove", onPointerMove);
5352
- canvas.removeEventListener("pointerup", onPointerUp);
5353
- canvas.removeEventListener("pointercancel", onPointerUp);
5354
- if (ownerWindow) {
5355
- const target = ownerWindow;
5356
- if (target.__AURA3D_MINI_GOLF__?.kind === "aura-mini-golf-browser-runtime")
5357
- delete target.__AURA3D_MINI_GOLF__;
5358
- }
5359
- }
5360
- };
5361
- }
5362
- function addThreeLights(THREE, threeScene, nodes) {
5363
- const lightNodes = nodes.filter((node) => node.kind === "light");
5364
- if (lightNodes.length === 0) {
5365
- const emissivePostScene = nodes.some((node) => node.kind === "effect" && node.effect === "bloom") &&
5366
- nodes.some((node) => node.kind === "primitive" && Boolean(node.material?.emissive));
5367
- if (emissivePostScene) {
5368
- threeScene.add(new THREE.HemisphereLight("#9fc7ff", "#020617", 0.18));
5369
- return;
5370
- }
5371
- threeScene.add(new THREE.HemisphereLight("#dfefff", "#071019", 1.35));
5372
- const key = new THREE.DirectionalLight("#ffffff", 2.4);
5373
- key.position.set(3.8, 5.6, 4.2);
5374
- key.castShadow = true;
5375
- configureThreeShadowLight(key);
5376
- threeScene.add(key);
5377
- return;
5378
- }
5379
- for (const node of lightNodes) {
5380
- const color = new THREE.Color(node.color ?? "#ffffff");
5381
- if (node.light === "ambient") {
5382
- threeScene.add(new THREE.HemisphereLight(color, new THREE.Color("#05070b"), Math.max(0.05, node.intensity * 1.8)));
5383
- continue;
5384
- }
5385
- if (node.light === "point") {
5386
- const light = new THREE.PointLight(color, Math.max(0.1, node.intensity * 26), 16, 1.65);
5387
- const position = node.position ?? [2, 2.5, 1.5];
5388
- light.position.set(position[0], position[1], position[2]);
5389
- light.castShadow = true;
5390
- configureThreeShadowLight(light);
5391
- threeScene.add(light);
5392
- continue;
5393
- }
5394
- if (node.light === "rect" || node.light === "softbox") {
5395
- const light = new THREE.RectAreaLight(color, Math.max(0.1, node.intensity * 6), node.width ?? 2, node.height ?? 1);
5396
- const position = node.position ?? [0, 2.4, 2.2];
5397
- light.position.set(position[0], position[1], position[2]);
5398
- light.lookAt(0, 0.55, 0);
5399
- threeScene.add(light);
5400
- continue;
5401
- }
5402
- if (node.light === "studio") {
5403
- threeScene.add(new THREE.HemisphereLight(color, new THREE.Color("#111827"), Math.max(0.05, node.intensity * 1.3)));
5404
- const key = new THREE.DirectionalLight(color, Math.max(0.1, node.intensity * 2.4));
5405
- const position = node.position ?? [3, 4, 3];
5406
- key.position.set(position[0], position[1], position[2]);
5407
- key.castShadow = true;
5408
- configureThreeShadowLight(key);
5409
- threeScene.add(key);
5410
- continue;
5411
- }
5412
- const light = new THREE.DirectionalLight(color, Math.max(0.1, node.intensity * 2.1));
5413
- const position = node.position ?? [3, 4, 3];
5414
- light.position.set(position[0], position[1], position[2]);
5415
- light.castShadow = true;
5416
- configureThreeShadowLight(light);
5417
- threeScene.add(light);
5418
- }
5419
- }
5420
- function configureThreeShadowLight(light) {
5421
- if (!light.shadow)
5422
- return;
5423
- light.shadow.mapSize.width = 1024;
5424
- light.shadow.mapSize.height = 1024;
5425
- if (light.shadow.camera) {
5426
- light.shadow.camera.near = 0.1;
5427
- light.shadow.camera.far = Math.max(light.shadow.camera.far ?? 24, 24);
5428
- if ("left" in light.shadow.camera) {
5429
- light.shadow.camera.left = -6;
5430
- light.shadow.camera.right = 6;
5431
- light.shadow.camera.top = 6;
5432
- light.shadow.camera.bottom = -6;
5433
- }
5434
- }
5435
- }
5436
- function createThreePrimitiveBatches(THREE, nodes) {
5437
- const groups = new Map();
5438
- for (const node of nodes) {
5439
- const key = primitiveBatchKey(node);
5440
- const existing = groups.get(key);
5441
- if (existing)
5442
- existing.push(node);
5443
- else
5444
- groups.set(key, [node]);
5445
- }
5446
- const meshes = [];
5447
- for (const group of groups.values()) {
5448
- const first = group[0];
5449
- if (!first)
5450
- continue;
5451
- if (group.length === 1) {
5452
- meshes.push(createThreePrimitive(THREE, first));
5453
- continue;
5454
- }
5455
- const geometry = createThreePrimitiveGeometry(THREE, first.primitive);
5456
- const materialValue = createThreeMaterial(THREE, first.material ?? material.pbr());
5457
- const mesh = new THREE.InstancedMesh(geometry, materialValue, group.length);
5458
- mesh.name = `aura-instanced-${first.primitive}-${group.length}`;
5459
- mesh.castShadow = first.castShadow ?? (first.primitive !== "plane" && !first.material?.emissive);
5460
- mesh.receiveShadow = first.receiveShadow ?? true;
5461
- const dummy = new THREE.Object3D();
5462
- group.forEach((node, index) => {
5463
- applyThreeTransform(dummy, node, primitiveSize(node));
5464
- dummy.updateMatrix();
5465
- mesh.setMatrixAt(index, dummy.matrix);
5466
- });
5467
- mesh.instanceMatrix.needsUpdate = true;
5468
- meshes.push(mesh);
5469
- }
5470
- return meshes;
5471
- }
5472
- function primitiveBatchKey(node) {
5473
- return JSON.stringify({
5474
- primitive: node.primitive,
5475
- material: node.material ?? material.pbr(),
5476
- castShadow: node.castShadow ?? (node.primitive !== "plane" && !node.material?.emissive),
5477
- receiveShadow: node.receiveShadow ?? true
5478
- });
5479
- }
5480
- function createThreePrimitiveGeometry(THREE, primitiveName) {
5481
- return primitiveName === "sphere"
5482
- ? new THREE.SphereGeometry(0.5, 24, 12)
5483
- : primitiveName === "capsule"
5484
- ? new THREE.CapsuleGeometry(0.32, 0.68, 8, 16)
5485
- : primitiveName === "torus"
5486
- ? new THREE.TorusGeometry(0.5, 0.035, 12, 64)
5487
- : primitiveName === "box"
5488
- ? new THREE.BoxGeometry(1, 1, 1)
5489
- : primitiveName === "cylinder"
5490
- ? new THREE.CylinderGeometry(0.5, 0.5, 1, 24, 1)
5491
- : new THREE.PlaneGeometry(1, 1, 1, 1).rotateX(-Math.PI / 2);
5492
- }
5493
- function createThreePrimitive(THREE, node) {
5494
- const geometry = createThreePrimitiveGeometry(THREE, node.primitive);
5495
- const materialValue = createThreeMaterial(THREE, node.material ?? material.pbr());
5496
- const mesh = new THREE.Mesh(geometry, materialValue);
5497
- mesh.castShadow = node.castShadow ?? (node.primitive !== "plane" && !node.material?.emissive);
5498
- mesh.receiveShadow = node.receiveShadow ?? true;
5499
- applyThreeTransform(mesh, node, primitiveSize(node));
5500
- return mesh;
5501
- }
5502
- function isThreeReadableTextLabel(node) {
5503
- return node.primitive === "plane" && node.name?.endsWith("readable planet label") === true;
5504
- }
5505
- function createThreeReadableTextLabel(THREE, node) {
5506
- const label = (node.name ?? "label").replace(/\s*readable planet label$/, "");
5507
- const color = String(node.material?.emissive ?? node.material?.color ?? "#ffffff");
5508
- const texture = createThreeTextTexture(THREE, label, color);
5509
- const sprite = new THREE.Sprite(new THREE.SpriteMaterial({
5510
- map: texture,
5511
- transparent: true,
5512
- depthWrite: false,
5513
- depthTest: false
5514
- }));
5515
- const position = node.position ?? [0, 0.4, 0];
5516
- const scale = typeof node.scale === "number" ? [node.scale, node.scale, node.scale] : node.scale ?? [0.8, 1, 0.24];
5517
- sprite.name = node.name ?? label;
5518
- sprite.position.set(position[0], position[1], position[2]);
5519
- sprite.scale.set(scale[0], scale[2] * 1.35, 1);
5520
- return sprite;
5521
- }
5522
- function createThreeLabelNode(THREE, node) {
5523
- const color = String(node.color ?? "#e0f2fe");
5524
- const texture = createThreeTextTexture(THREE, node.text, color, String(node.background ?? "#020617"));
5525
- const sprite = new THREE.Sprite(new THREE.SpriteMaterial({
5526
- map: texture,
5527
- transparent: true,
5528
- depthWrite: false,
5529
- depthTest: node.occlusionAware === true
5530
- }));
5531
- const position = node.position ?? labelDefaultPosition(node);
5532
- const size = Math.max(0.16, node.size ?? 0.36);
5533
- const width = Math.max(0.56, Math.min(2.8, node.text.length * size * 0.22));
5534
- sprite.name = node.name ?? `${node.label} label`;
5535
- sprite.position.set(position[0], position[1], position[2]);
5536
- sprite.scale.set(width, size * 0.62, 1);
5537
- return sprite;
5538
- }
5539
4875
  function labelDefaultPosition(node) {
5540
4876
  if (node.screenAnchor === "top-right")
5541
4877
  return [1.35, 1.85, -0.2];
@@ -5547,485 +4883,6 @@ function labelDefaultPosition(node) {
5547
4883
  return [-1.35, 1.85, -0.2];
5548
4884
  return [0, 1.28, 0];
5549
4885
  }
5550
- function createThreeTextTexture(THREE, text, color, background = "#020617") {
5551
- const canvas = document.createElement("canvas");
5552
- canvas.width = 512;
5553
- canvas.height = 160;
5554
- const context = canvas.getContext("2d");
5555
- if (context) {
5556
- context.clearRect(0, 0, canvas.width, canvas.height);
5557
- context.fillStyle = colorWithAlpha(background, 0.86);
5558
- context.fillRect(8, 18, canvas.width - 16, canvas.height - 36);
5559
- context.strokeStyle = color;
5560
- context.lineWidth = 8;
5561
- context.strokeRect(12, 22, canvas.width - 24, canvas.height - 44);
5562
- context.fillStyle = color;
5563
- context.font = "700 64px system-ui, -apple-system, BlinkMacSystemFont, sans-serif";
5564
- context.textAlign = "center";
5565
- context.textBaseline = "middle";
5566
- context.fillText(text, canvas.width / 2, canvas.height / 2 + 2);
5567
- }
5568
- const texture = new THREE.CanvasTexture(canvas);
5569
- texture.colorSpace = THREE.SRGBColorSpace;
5570
- texture.needsUpdate = true;
5571
- return texture;
5572
- }
5573
- async function createThreeMaterialInspectionEnvironment(THREE, renderer, snapshot) {
5574
- const environmentNode = snapshot.nodes.find((node) => node.kind === "environment");
5575
- const needsInspectionEnvironment = Boolean(environmentNode) || snapshot.nodes.some((node) => node.kind === "environment" ||
5576
- (node.kind === "primitive" && (node.name === "mirror chrome metal swatch" ||
5577
- node.name === "transparent cyan glass swatch" ||
5578
- node.name === "red automotive clearcoat swatch")));
5579
- if (!needsInspectionEnvironment)
5580
- return undefined;
5581
- try {
5582
- const generator = new THREE.PMREMGenerator(renderer);
5583
- const profile = resolveThreeEnvironmentProfile(environmentNode);
5584
- const environment = createThreeProceduralIblScene(THREE, profile);
5585
- const texture = generator.fromScene(environment, profile.blur).texture;
5586
- disposeThreeResource(environment);
5587
- return { texture, generator, intensity: profile.intensity, preset: profile.preset };
5588
- }
5589
- catch {
5590
- try {
5591
- const { RoomEnvironment } = await import("three/examples/jsm/environments/RoomEnvironment.js");
5592
- const generator = new THREE.PMREMGenerator(renderer);
5593
- const environment = new RoomEnvironment(renderer);
5594
- const texture = generator.fromScene(environment, 0.04).texture;
5595
- disposeThreeResource(environment);
5596
- return { texture, generator, intensity: environmentNode?.intensity ?? 1, preset: environmentNode?.environment ?? "room-fallback" };
5597
- }
5598
- catch {
5599
- return undefined;
5600
- }
5601
- }
5602
- }
5603
- function resolveThreeEnvironmentProfile(node) {
5604
- const preset = node?.environment ?? "studio";
5605
- const fallback = environmentMapPresets.find((entry) => entry.id === preset);
5606
- const intensity = node?.intensity ?? fallback?.intensity ?? 1;
5607
- const roomColor = node?.color ?? fallback?.color ?? "#eef4ff";
5608
- const profiles = {
5609
- studio: [
5610
- { name: "left broad studio softbox", color: "#ffffff", position: [-3.4, 2.2, 1.8], rotation: [0, Math.PI * 0.38, 0], scale: [2.5, 1.15, 1] },
5611
- { name: "right cool studio fill", color: "#cfe9ff", position: [3.2, 1.75, 1.2], rotation: [0, -Math.PI * 0.36, 0], scale: [1.8, 0.95, 1] },
5612
- { name: "low dark reflection card", color: "#111827", position: [0, 0.55, -2.8], rotation: [0, 0, 0], scale: [3.8, 0.65, 1] }
5613
- ],
5614
- "material-lab": [
5615
- { name: "white material strip reflection", color: "#ffffff", position: [0, 2.7, -2.9], rotation: [0, 0, 0], scale: [4.6, 0.42, 1] },
5616
- { name: "black material contrast card", color: "#030712", position: [0, 1.65, -3.0], rotation: [0, 0, 0], scale: [4.1, 0.34, 1] },
5617
- { name: "cool cyan material card", color: "#67e8f9", position: [-3.1, 1.65, -0.8], rotation: [0, Math.PI * 0.42, 0], scale: [0.68, 2.1, 1] },
5618
- { name: "warm gold material card", color: "#fbbf24", position: [3.1, 1.65, -0.8], rotation: [0, -Math.PI * 0.42, 0], scale: [0.68, 2.1, 1] }
5619
- ],
5620
- "product-hero": [
5621
- { name: "large product key softbox", color: "#ffffff", position: [-2.4, 2.8, 2.4], rotation: [-0.25, Math.PI * 0.3, 0], scale: [2.7, 1.45, 1] },
5622
- { name: "thin product rim strip", color: "#dbeafe", position: [2.8, 1.55, -1.9], rotation: [0, -Math.PI * 0.34, 0], scale: [0.55, 2.0, 1] },
5623
- { name: "product floor dark bounce", color: "#172033", position: [0, 0.32, 2.8], rotation: [0, Math.PI, 0], scale: [3.4, 0.5, 1] }
5624
- ],
5625
- "night-cinematic": [
5626
- { name: "cyan night tunnel reflection", color: "#22d3ee", position: [-3.2, 1.55, 0.2], rotation: [0, Math.PI * 0.42, 0], scale: [0.44, 2.4, 1] },
5627
- { name: "magenta night tunnel reflection", color: "#ff42c8", position: [3.2, 1.1, -0.6], rotation: [0, -Math.PI * 0.42, 0], scale: [0.44, 1.9, 1] },
5628
- { name: "dim blue vanishing reflection", color: "#1d4ed8", position: [0, 0.95, -3.0], rotation: [0, 0, 0], scale: [2.2, 0.75, 1] }
5629
- ],
5630
- "metal-studio": [
5631
- { name: "hard white metal reflection bar", color: "#ffffff", position: [0, 2.3, -2.9], rotation: [0, 0, 0], scale: [5.2, 0.28, 1] },
5632
- { name: "deep black metal reflection bar", color: "#020617", position: [0, 1.65, -3.0], rotation: [0, 0, 0], scale: [4.6, 0.22, 1] },
5633
- { name: "blue metal side card", color: "#93c5fd", position: [-3.25, 1.35, -0.4], rotation: [0, Math.PI * 0.43, 0], scale: [0.42, 2.2, 1] },
5634
- { name: "amber metal side card", color: "#f59e0b", position: [3.25, 1.35, -0.4], rotation: [0, -Math.PI * 0.43, 0], scale: [0.42, 2.2, 1] }
5635
- ],
5636
- "glass-studio": [
5637
- { name: "glass white caustic strip", color: "#ffffff", position: [-1.1, 2.35, -2.7], rotation: [0, 0.08, 0], scale: [2.5, 0.32, 1] },
5638
- { name: "glass cyan refraction panel", color: "#99f6e4", position: [2.9, 1.55, -0.6], rotation: [0, -Math.PI * 0.4, 0], scale: [0.56, 2.0, 1] },
5639
- { name: "glass dark edge card", color: "#06111c", position: [-2.8, 1.18, 0.2], rotation: [0, Math.PI * 0.4, 0], scale: [0.5, 1.6, 1] }
5640
- ]
5641
- };
5642
- return {
5643
- preset,
5644
- intensity,
5645
- blur: preset === "night-cinematic" ? 0.02 : 0.04,
5646
- roomColor,
5647
- panels: profiles[preset] ?? profiles.studio
5648
- };
5649
- }
5650
- function createThreeProceduralIblScene(THREE, profile) {
5651
- const environment = new THREE.Scene();
5652
- const room = new THREE.Mesh(new THREE.BoxGeometry(18, 18, 18), new THREE.MeshBasicMaterial({ color: new THREE.Color(profile.roomColor), side: THREE.BackSide }));
5653
- environment.add(room);
5654
- for (const panel of profile.panels) {
5655
- const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ color: new THREE.Color(panel.color), side: THREE.DoubleSide }));
5656
- mesh.name = panel.name;
5657
- mesh.position.set(panel.position[0], panel.position[1], panel.position[2]);
5658
- mesh.rotation.set(panel.rotation[0], panel.rotation[1], panel.rotation[2]);
5659
- mesh.scale.set(panel.scale[0], panel.scale[1], panel.scale[2]);
5660
- environment.add(mesh);
5661
- }
5662
- return environment;
5663
- }
5664
- function createThreeMaterial(THREE, spec) {
5665
- if (spec.shader === "solar-sun")
5666
- return createThreeSolarSunMaterial(THREE, spec);
5667
- if (spec.shader === "solar-corona")
5668
- return createThreeSolarCoronaMaterial(THREE, spec);
5669
- const color = new THREE.Color(spec.color ?? "#d7dee8");
5670
- const metalness = spec.metalness ?? spec.metallic ?? 0;
5671
- const usePhysical = spec.transmission !== undefined ||
5672
- spec.clearcoat !== undefined ||
5673
- spec.sheen !== undefined ||
5674
- spec.iridescence !== undefined ||
5675
- spec.anisotropy !== undefined;
5676
- const transparent = spec.opacity !== undefined && spec.opacity < 1;
5677
- const materialValue = usePhysical
5678
- ? new THREE.MeshPhysicalMaterial({
5679
- color,
5680
- roughness: spec.roughness ?? 0.54,
5681
- metalness,
5682
- transparent,
5683
- opacity: spec.opacity ?? 1,
5684
- transmission: spec.transmission ?? 0,
5685
- clearcoat: spec.clearcoat ?? 0,
5686
- clearcoatRoughness: spec.clearcoatRoughness ?? 0.18,
5687
- thickness: spec.thickness ?? 0,
5688
- ior: spec.ior ?? 1.5,
5689
- attenuationColor: new THREE.Color(spec.attenuationColor ?? spec.color ?? "#ffffff"),
5690
- attenuationDistance: spec.attenuationDistance ?? Infinity,
5691
- envMapIntensity: spec.envMapIntensity ?? 1,
5692
- depthWrite: !transparent,
5693
- side: transparent ? THREE.DoubleSide : THREE.FrontSide
5694
- })
5695
- : new THREE.MeshStandardMaterial({
5696
- color,
5697
- roughness: spec.roughness ?? 0.54,
5698
- metalness,
5699
- transparent,
5700
- opacity: spec.opacity ?? 1,
5701
- envMapIntensity: spec.envMapIntensity ?? 1,
5702
- depthWrite: !transparent,
5703
- side: transparent ? THREE.DoubleSide : THREE.FrontSide
5704
- });
5705
- if (spec.emissive) {
5706
- materialValue.emissive = new THREE.Color(spec.emissive);
5707
- materialValue.emissiveIntensity = spec.emissiveIntensity ?? 1.55;
5708
- }
5709
- if ("sheen" in materialValue && spec.sheen !== undefined)
5710
- materialValue.sheen = clampMaterialScalar(spec.sheen, 0);
5711
- if ("sheenRoughness" in materialValue && spec.sheenRoughness !== undefined)
5712
- materialValue.sheenRoughness = clampMaterialScalar(spec.sheenRoughness, 0.5);
5713
- if ("sheenColor" in materialValue && spec.sheenColor)
5714
- materialValue.sheenColor = new THREE.Color(spec.sheenColor);
5715
- if ("iridescence" in materialValue && spec.iridescence !== undefined)
5716
- materialValue.iridescence = clampMaterialScalar(spec.iridescence, 0);
5717
- if ("iridescenceIOR" in materialValue && spec.iridescenceIOR !== undefined)
5718
- materialValue.iridescenceIOR = spec.iridescenceIOR;
5719
- if ("iridescenceThicknessRange" in materialValue && spec.iridescenceThicknessRange)
5720
- materialValue.iridescenceThicknessRange = spec.iridescenceThicknessRange;
5721
- if ("anisotropy" in materialValue && spec.anisotropy !== undefined)
5722
- materialValue.anisotropy = clampMaterialScalar(spec.anisotropy, 0, -1, 1);
5723
- if ("anisotropyRotation" in materialValue && spec.anisotropyRotation !== undefined)
5724
- materialValue.anisotropyRotation = spec.anisotropyRotation;
5725
- const normalMap = createThreeTextureFromMaterialInput(THREE, spec.normal, "normal");
5726
- if (normalMap) {
5727
- materialValue.normalMap = normalMap;
5728
- materialValue.normalScale = new THREE.Vector2(spec.normalScale ?? 1, spec.normalScale ?? 1);
5729
- }
5730
- const roughnessMap = createThreeTextureFromMaterialInput(THREE, spec.roughnessMap, "roughness");
5731
- if (roughnessMap)
5732
- materialValue.roughnessMap = roughnessMap;
5733
- const metalnessMap = createThreeTextureFromMaterialInput(THREE, spec.metalnessMap, "metalness");
5734
- if (metalnessMap)
5735
- materialValue.metalnessMap = metalnessMap;
5736
- materialValue.needsUpdate = true;
5737
- return materialValue;
5738
- }
5739
- function createThreeSolarSunMaterial(THREE, spec) {
5740
- const materialValue = new THREE.ShaderMaterial({
5741
- transparent: false,
5742
- depthWrite: true,
5743
- uniforms: {
5744
- uColor: { value: new THREE.Color(spec.color ?? "#ffd166") },
5745
- uCoreColor: { value: new THREE.Color(spec.coreColor ?? "#fff7ad") },
5746
- uRimColor: { value: new THREE.Color(spec.rimColor ?? "#f97316") },
5747
- uIntensity: { value: spec.emissiveIntensity ?? 2.45 },
5748
- uNoiseStrength: { value: spec.noiseStrength ?? 0.18 }
5749
- },
5750
- vertexShader: `
5751
- varying vec3 vNormalW;
5752
- varying vec3 vPositionW;
5753
- void main() {
5754
- vec4 worldPosition = modelMatrix * vec4(position, 1.0);
5755
- vPositionW = worldPosition.xyz;
5756
- vNormalW = normalize(mat3(modelMatrix) * normal);
5757
- gl_Position = projectionMatrix * viewMatrix * worldPosition;
5758
- }
5759
- `,
5760
- fragmentShader: `
5761
- uniform vec3 uColor;
5762
- uniform vec3 uCoreColor;
5763
- uniform vec3 uRimColor;
5764
- uniform float uIntensity;
5765
- uniform float uNoiseStrength;
5766
- varying vec3 vNormalW;
5767
- varying vec3 vPositionW;
5768
- float auraHash(vec3 p) {
5769
- return fract(sin(dot(p, vec3(17.17, 43.31, 91.73))) * 43758.5453);
5770
- }
5771
- void main() {
5772
- vec3 n = normalize(vNormalW);
5773
- vec3 viewDir = normalize(cameraPosition - vPositionW);
5774
- float facing = clamp(dot(n, viewDir), 0.0, 1.0);
5775
- float rim = pow(1.0 - facing, 2.1);
5776
- float grain = auraHash(n * 3.7) * 0.55 + auraHash(n * 9.3 + 4.2) * 0.45;
5777
- vec3 hotCore = mix(uColor, uCoreColor, smoothstep(0.16, 0.92, facing));
5778
- vec3 color = mix(hotCore, uRimColor, rim * 0.82);
5779
- color *= uIntensity * (0.88 + grain * uNoiseStrength);
5780
- gl_FragColor = vec4(color, 1.0);
5781
- }
5782
- `
5783
- });
5784
- return materialValue;
5785
- }
5786
- function createThreeSolarCoronaMaterial(THREE, spec) {
5787
- const materialValue = new THREE.ShaderMaterial({
5788
- transparent: true,
5789
- depthWrite: false,
5790
- blending: THREE.AdditiveBlending,
5791
- side: THREE.BackSide,
5792
- uniforms: {
5793
- uColor: { value: new THREE.Color(spec.color ?? "#ff9f1c") },
5794
- uCoreColor: { value: new THREE.Color(spec.coreColor ?? "#ffd166") },
5795
- uRimColor: { value: new THREE.Color(spec.rimColor ?? "#f97316") },
5796
- uOpacity: { value: spec.opacity ?? 0.34 },
5797
- uIntensity: { value: spec.emissiveIntensity ?? 1.55 },
5798
- uFalloff: { value: spec.falloff ?? 2.7 },
5799
- uNoiseStrength: { value: spec.noiseStrength ?? 0.14 }
5800
- },
5801
- vertexShader: `
5802
- varying vec3 vNormalW;
5803
- varying vec3 vPositionW;
5804
- void main() {
5805
- vec4 worldPosition = modelMatrix * vec4(position, 1.0);
5806
- vPositionW = worldPosition.xyz;
5807
- vNormalW = normalize(mat3(modelMatrix) * normal);
5808
- gl_Position = projectionMatrix * viewMatrix * worldPosition;
5809
- }
5810
- `,
5811
- fragmentShader: `
5812
- uniform vec3 uColor;
5813
- uniform vec3 uCoreColor;
5814
- uniform vec3 uRimColor;
5815
- uniform float uOpacity;
5816
- uniform float uIntensity;
5817
- uniform float uFalloff;
5818
- uniform float uNoiseStrength;
5819
- varying vec3 vNormalW;
5820
- varying vec3 vPositionW;
5821
- float auraHash(vec3 p) {
5822
- return fract(sin(dot(p, vec3(23.11, 59.37, 101.19))) * 24634.6345);
5823
- }
5824
- void main() {
5825
- vec3 n = normalize(vNormalW);
5826
- vec3 viewDir = normalize(cameraPosition - vPositionW);
5827
- float facing = clamp(dot(n, viewDir), 0.0, 1.0);
5828
- float rim = pow(1.0 - facing, uFalloff);
5829
- float grain = auraHash(n * 5.0) * 0.6 + auraHash(n * 13.0 + 1.7) * 0.4;
5830
- float alpha = clamp((rim * 0.9 + grain * uNoiseStrength) * uOpacity, 0.0, 0.62);
5831
- vec3 color = mix(uCoreColor, mix(uColor, uRimColor, rim), 0.78) * uIntensity;
5832
- gl_FragColor = vec4(color, alpha);
5833
- }
5834
- `
5835
- });
5836
- return materialValue;
5837
- }
5838
- function createThreeTextureFromMaterialInput(THREE, input, usage) {
5839
- if (!input || !("kind" in input) || input.kind !== "aura-procedural-texture")
5840
- return undefined;
5841
- return createThreeProceduralMaterialTexture(THREE, input, usage);
5842
- }
5843
- function createThreeProceduralMaterialTexture(THREE, spec, usage) {
5844
- if (typeof document === "undefined")
5845
- return undefined;
5846
- const canvas = document.createElement("canvas");
5847
- canvas.width = 128;
5848
- canvas.height = 128;
5849
- const context = canvas.getContext("2d");
5850
- if (!context)
5851
- return undefined;
5852
- const image = context.createImageData(canvas.width, canvas.height);
5853
- const scale = Math.max(1, spec.scale);
5854
- const strength = Math.max(0, spec.strength);
5855
- const contrast = spec.contrast ?? 0.5;
5856
- for (let y = 0; y < canvas.height; y += 1) {
5857
- for (let x = 0; x < canvas.width; x += 1) {
5858
- const index = (y * canvas.width + x) * 4;
5859
- const u = x / canvas.width;
5860
- const v = y / canvas.height;
5861
- const grain = proceduralTextureNoise(u, v, scale, spec.texture);
5862
- const stripe = Math.sin((spec.texture === "brushed-metal-anisotropy" ? u : v) * scale * Math.PI * 2) * 0.5 + 0.5;
5863
- const micro = Math.max(0, Math.min(1, grain * (1 - contrast) + stripe * contrast));
5864
- if (usage === "normal") {
5865
- const nx = Math.round(128 + (micro - 0.5) * 96 * strength);
5866
- const ny = Math.round(128 + (grain - 0.5) * 74 * strength);
5867
- image.data[index] = nx;
5868
- image.data[index + 1] = spec.texture === "brushed-metal-anisotropy" ? 128 : ny;
5869
- image.data[index + 2] = 255;
5870
- image.data[index + 3] = 255;
5871
- }
5872
- else {
5873
- const value = Math.round(255 * Math.max(0, Math.min(1, 0.28 + micro * 0.62 * strength)));
5874
- image.data[index] = value;
5875
- image.data[index + 1] = value;
5876
- image.data[index + 2] = value;
5877
- image.data[index + 3] = 255;
5878
- }
5879
- }
5880
- }
5881
- context.putImageData(image, 0, 0);
5882
- const texture = new THREE.CanvasTexture(canvas);
5883
- texture.wrapS = THREE.RepeatWrapping;
5884
- texture.wrapT = THREE.RepeatWrapping;
5885
- texture.repeat.set(1, 1);
5886
- texture.needsUpdate = true;
5887
- return texture;
5888
- }
5889
- function proceduralTextureNoise(u, v, scale, kind) {
5890
- const salt = kind === "fabric-normal"
5891
- ? 19.17
5892
- : kind === "rubber-roughness"
5893
- ? 37.41
5894
- : kind === "brushed-metal-anisotropy"
5895
- ? 53.29
5896
- : 71.83;
5897
- const a = Math.sin((u * scale + salt) * 12.9898 + (v * scale - salt) * 78.233) * 43758.5453;
5898
- const b = Math.sin((u * scale * 0.37 - salt) * 93.989 + (v * scale * 0.61 + salt) * 67.345) * 24634.6345;
5899
- const value = (a - Math.floor(a)) * 0.58 + (b - Math.floor(b)) * 0.42;
5900
- if (kind === "fabric-normal") {
5901
- const weave = (Math.sin(u * scale * Math.PI * 2) + Math.sin(v * scale * Math.PI * 2)) * 0.25 + 0.5;
5902
- return value * 0.44 + weave * 0.56;
5903
- }
5904
- if (kind === "brushed-metal-anisotropy") {
5905
- const brush = Math.sin(u * scale * Math.PI * 4) * 0.18 + 0.5;
5906
- return value * 0.25 + brush * 0.75;
5907
- }
5908
- return value;
5909
- }
5910
- function enhanceLoadedMaterial(materialValue) {
5911
- const materials = Array.isArray(materialValue) ? materialValue : [materialValue];
5912
- for (const entry of materials) {
5913
- if (!entry || typeof entry !== "object")
5914
- continue;
5915
- if ("roughness" in entry && typeof entry.roughness === "number")
5916
- entry.roughness = Math.min(entry.roughness, 0.82);
5917
- if ("metalness" in entry && typeof entry.metalness === "number")
5918
- entry.metalness = Math.max(entry.metalness, 0.02);
5919
- entry.needsUpdate = true;
5920
- }
5921
- }
5922
- function normalizeThreeModel(THREE, modelRoot, node) {
5923
- const box = new THREE.Box3().setFromObject(modelRoot);
5924
- const size = box.getSize(new THREE.Vector3());
5925
- const center = box.getCenter(new THREE.Vector3());
5926
- const fitScale = 1.55 / Math.max(0.001, size.x, size.y, size.z);
5927
- modelRoot.position.sub(new THREE.Vector3(center.x, box.min.y, center.z));
5928
- const pivot = new THREE.Group();
5929
- pivot.add(modelRoot);
5930
- applyThreeTransform(pivot, node, [fitScale, fitScale, fitScale]);
5931
- return pivot;
5932
- }
5933
- function registerThreeModelClipAnimation(THREE, gltf, modelRoot, node, frameUpdaters, disposables) {
5934
- const clips = Array.isArray(gltf.animations) ? gltf.animations : [];
5935
- if (clips.length === 0)
5936
- return;
5937
- const requestedClip = String(node.animation?.clip ?? "").toLowerCase();
5938
- const clip = clips.find((entry) => String(entry.name ?? "").toLowerCase() === requestedClip) ??
5939
- clips.find((entry) => requestedClip && String(entry.name ?? "").toLowerCase().includes(requestedClip)) ??
5940
- clips[0];
5941
- if (!clip)
5942
- return;
5943
- const mixer = new THREE.AnimationMixer(modelRoot);
5944
- const action = mixer.clipAction(clip);
5945
- action.reset();
5946
- action.enabled = true;
5947
- action.play();
5948
- const speed = Math.max(0.05, node.animation?.speed ?? 1);
5949
- frameUpdaters.push((time) => {
5950
- const seconds = node.animation ? resolveAnimationSeconds(node.animation, time) : time * 0.001;
5951
- const duration = Math.max(0.001, clip.duration ?? 1);
5952
- mixer.setTime((seconds * speed) % duration);
5953
- });
5954
- disposables.push({ dispose: () => mixer.uncacheRoot(modelRoot) });
5955
- }
5956
- function applyThreeTransform(object, node, baseScale) {
5957
- const position = node.position ?? [0, 0, 0];
5958
- const rotation = node.rotation ?? [0, 0, 0];
5959
- const scaleValue = typeof node.scale === "number"
5960
- ? [node.scale, node.scale, node.scale]
5961
- : node.scale ?? [1, 1, 1];
5962
- object.position.set(position[0], position[1], position[2]);
5963
- object.rotation.set(rotation[0], rotation[1], rotation[2]);
5964
- object.scale.set(baseScale[0] * scaleValue[0], baseScale[1] * scaleValue[1], baseScale[2] * scaleValue[2]);
5965
- if (node.lookAt)
5966
- object.lookAt(node.lookAt[0], node.lookAt[1], node.lookAt[2]);
5967
- }
5968
- function registerThreeNodeAnimation(object, node, frameUpdaters) {
5969
- if (!node.animation)
5970
- return;
5971
- const basePosition = node.position ?? [0, 0, 0];
5972
- const baseRotation = node.rotation ?? [0, 0, 0];
5973
- const baseScale = typeof object.scale?.clone === "function" ? object.scale.clone() : undefined;
5974
- const speed = Math.max(0.05, node.animation.speed ?? 1);
5975
- frameUpdaters.push((time) => {
5976
- const seconds = resolveAnimationSeconds(node.animation, time);
5977
- if (node.animation?.clip === "orbit") {
5978
- const position = orbitAnimatedPosition(node.animation, basePosition, seconds, speed);
5979
- object.position.x = position[0];
5980
- object.position.y = position[1];
5981
- object.position.z = position[2];
5982
- if (object.rotation)
5983
- object.rotation.y = baseRotation[1] + orbitAnimatedAngle(seconds, speed);
5984
- return;
5985
- }
5986
- if (node.animation?.clip === "bar-height-grow") {
5987
- const duration = Math.max(0.05, node.animation.duration ?? 1.1);
5988
- const linear = Math.min(1, seconds / duration);
5989
- const eased = node.animation.easing === "linear" ? linear : 0.5 - Math.cos(linear * Math.PI) * 0.5;
5990
- if (baseScale && object.scale) {
5991
- object.scale.x = baseScale.x;
5992
- object.scale.y = Math.max(baseScale.y * eased, baseScale.y * 0.06);
5993
- object.scale.z = baseScale.z;
5994
- }
5995
- object.position.y = basePosition[1] * eased;
5996
- return;
5997
- }
5998
- if (node.animation?.clip === "walk") {
5999
- const phase = seconds * speed * Math.PI * 2;
6000
- const travel = Math.sin(seconds * speed * 0.72) * 0.24;
6001
- const bodyBob = Math.abs(Math.sin(phase)) * 0.028;
6002
- object.position.x = basePosition[0] + travel;
6003
- object.position.y = basePosition[1] + bodyBob;
6004
- object.position.z = basePosition[2];
6005
- object.rotation.x = baseRotation[0];
6006
- object.rotation.y = baseRotation[1];
6007
- object.rotation.z = baseRotation[2];
6008
- return;
6009
- }
6010
- if (node.animation?.clip === "float") {
6011
- object.position.y = (node.position?.[1] ?? 0) + Math.sin(seconds * speed) * 0.08;
6012
- object.rotation.y = baseRotation[1] + seconds * speed * 0.28;
6013
- return;
6014
- }
6015
- if (node.animation?.clip === "turntable") {
6016
- object.position.y = basePosition[1];
6017
- object.rotation.y = baseRotation[1] + seconds * speed * 0.72;
6018
- return;
6019
- }
6020
- if (node.animation?.clip === "pulse") {
6021
- const pulse = 1 + Math.sin(seconds * speed * 2) * 0.08;
6022
- object.scale.multiplyScalar(pulse / (object.userData.lastAuraPulse ?? 1));
6023
- object.userData.lastAuraPulse = pulse;
6024
- return;
6025
- }
6026
- object.rotation.y = baseRotation[1] + seconds * speed;
6027
- });
6028
- }
6029
4886
  function resolveAnimationSeconds(animation, time) {
6030
4887
  if (!animation)
6031
4888
  return time / 1000;
@@ -6058,602 +4915,6 @@ function orbitAnimatedPosition(animation, basePosition, seconds, speed) {
6058
4915
  center[2] + Math.sin(angle) * radius
6059
4916
  ];
6060
4917
  }
6061
- function collectThreeEffectNodes(snapshot) {
6062
- return groups.flatten(snapshot.nodes).filter((node) => node.kind === "effect");
6063
- }
6064
- function hasThreePostProcessEffects(effectNodes) {
6065
- return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
6066
- }
6067
- async function createThreePostProcessPipeline(THREE, renderer, threeScene, cameraObject, snapshot) {
6068
- try {
6069
- const [{ EffectComposer }, { RenderPass }] = await Promise.all([
6070
- import("three/examples/jsm/postprocessing/EffectComposer.js"),
6071
- import("three/examples/jsm/postprocessing/RenderPass.js")
6072
- ]);
6073
- const effectNodes = collectThreeEffectNodes(snapshot);
6074
- const bloomEffect = effectNodes.find((node) => node.effect === "bloom");
6075
- const occlusionEffect = effectNodes.find((node) => node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
6076
- const composer = new EffectComposer(renderer);
6077
- const renderPass = new RenderPass(threeScene, cameraObject);
6078
- const width = renderer.domElement.width || 1;
6079
- const height = renderer.domElement.height || 1;
6080
- let bloomPass;
6081
- let ssaoPass;
6082
- let outputPass;
6083
- let effectPasses = 0;
6084
- const passNames = ["render"];
6085
- const warnings = [];
6086
- composer.setSize(width, height);
6087
- if (typeof composer.setPixelRatio === "function") {
6088
- composer.setPixelRatio(Math.min(2, Math.max(1, window.devicePixelRatio || 1)));
6089
- }
6090
- composer.addPass(renderPass);
6091
- if (occlusionEffect) {
6092
- try {
6093
- const { SSAOPass } = await import("three/examples/jsm/postprocessing/SSAOPass.js");
6094
- ssaoPass = new SSAOPass(threeScene, cameraObject, width, height);
6095
- ssaoPass.kernelRadius = Math.max(4, Math.min(24, Math.round((occlusionEffect.radius ?? 0.64) * 24)));
6096
- ssaoPass.minDistance = 0.001;
6097
- ssaoPass.maxDistance = Math.max(0.04, Math.min(0.28, (occlusionEffect.radius ?? 0.64) * 0.22));
6098
- composer.addPass(ssaoPass);
6099
- effectPasses += 1;
6100
- passNames.push("ssao");
6101
- }
6102
- catch (error) {
6103
- warnings.push("SSAO pass unavailable; contact receiver shadows remain active");
6104
- console.warn("Aura3D SSAO pass unavailable; contact receiver shadows remain active", error);
6105
- }
6106
- }
6107
- if (bloomEffect) {
6108
- try {
6109
- const { UnrealBloomPass } = await import("three/examples/jsm/postprocessing/UnrealBloomPass.js");
6110
- bloomPass = new UnrealBloomPass(new THREE.Vector2(width, height), Math.max(0, Math.min(bloomEffect.maxIntensity ?? 0.92, bloomEffect.intensity ?? 0.35)), Math.max(0.05, Math.min(1.2, bloomEffect.radius ?? 0.38)), Math.max(0, Math.min(1, bloomEffect.threshold ?? 0.7)));
6111
- composer.addPass(bloomPass);
6112
- effectPasses += 1;
6113
- passNames.push("bloom");
6114
- }
6115
- catch (error) {
6116
- warnings.push("UnrealBloomPass unavailable; emissive sprite fallback may be used instead of true bloom");
6117
- console.warn("Aura3D bloom pass unavailable; using direct-render fallback", error);
6118
- }
6119
- }
6120
- if (effectPasses === 0) {
6121
- if (typeof renderPass.dispose === "function")
6122
- renderPass.dispose();
6123
- if (typeof composer.dispose === "function")
6124
- composer.dispose();
6125
- return null;
6126
- }
6127
- try {
6128
- const { OutputPass } = await import("three/examples/jsm/postprocessing/OutputPass.js");
6129
- outputPass = new OutputPass();
6130
- composer.addPass(outputPass);
6131
- passNames.push("output");
6132
- }
6133
- catch {
6134
- warnings.push("OutputPass unavailable; renderer tone mapping remains configured without an explicit output pass");
6135
- // Older Three.js builds may not ship OutputPass; renderer tone mapping remains configured.
6136
- }
6137
- return {
6138
- passNames,
6139
- warnings,
6140
- render: () => composer.render(),
6141
- setSize: (nextWidth, nextHeight) => {
6142
- composer.setSize(nextWidth, nextHeight);
6143
- if (typeof bloomPass?.setSize === "function")
6144
- bloomPass.setSize(nextWidth, nextHeight);
6145
- if (typeof ssaoPass?.setSize === "function")
6146
- ssaoPass.setSize(nextWidth, nextHeight);
6147
- },
6148
- dispose: () => {
6149
- if (typeof bloomPass?.dispose === "function")
6150
- bloomPass.dispose();
6151
- if (typeof ssaoPass?.dispose === "function")
6152
- ssaoPass.dispose();
6153
- if (typeof outputPass?.dispose === "function")
6154
- outputPass.dispose();
6155
- if (typeof renderPass.dispose === "function")
6156
- renderPass.dispose();
6157
- if (typeof composer.dispose === "function")
6158
- composer.dispose();
6159
- }
6160
- };
6161
- }
6162
- catch (error) {
6163
- console.warn("Aura3D postprocess composer unavailable; falling back to direct render", error);
6164
- return null;
6165
- }
6166
- }
6167
- function createThreeContactShadowReceiver(THREE, snapshot, effect) {
6168
- const footprint = estimateSceneFootprint(snapshot.nodes);
6169
- const geometry = new THREE.PlaneGeometry(footprint.width, footprint.depth).rotateX(-Math.PI / 2);
6170
- const shadowMaterial = new THREE.ShadowMaterial({
6171
- color: new THREE.Color(effect.color ?? "#020617"),
6172
- opacity: Math.max(0.08, Math.min(0.42, effect.intensity ?? 0.28)),
6173
- transparent: true,
6174
- depthWrite: false
6175
- });
6176
- const receiver = new THREE.Mesh(geometry, shadowMaterial);
6177
- receiver.name = "aura-pixel-contact-shadow-receiver";
6178
- receiver.position.set(footprint.centerX, estimateSceneFloorTop(snapshot.nodes) + 0.006, footprint.centerZ);
6179
- receiver.receiveShadow = true;
6180
- receiver.castShadow = false;
6181
- return receiver;
6182
- }
6183
- function estimateSceneFloorTop(nodes) {
6184
- let top = 0;
6185
- for (const node of groups.flatten(nodes)) {
6186
- if (node.kind !== "primitive")
6187
- continue;
6188
- const name = (node.name ?? "").toLowerCase();
6189
- const isFloorLike = name.includes("floor") || name.includes("ground") || name.includes("stage") || name.includes("course") || name.includes("court");
6190
- if (!isFloorLike)
6191
- continue;
6192
- const y = node.position?.[1] ?? 0;
6193
- if (node.primitive === "box") {
6194
- const scale = scaleToVec3(node.scale);
6195
- top = Math.max(top, y + scale[1] * 0.5);
6196
- continue;
6197
- }
6198
- if (node.primitive === "plane") {
6199
- top = Math.max(top, y);
6200
- }
6201
- }
6202
- return top;
6203
- }
6204
- function estimateSceneFootprint(nodes) {
6205
- let minX = Number.POSITIVE_INFINITY;
6206
- let maxX = Number.NEGATIVE_INFINITY;
6207
- let minZ = Number.POSITIVE_INFINITY;
6208
- let maxZ = Number.NEGATIVE_INFINITY;
6209
- for (const node of groups.flatten(nodes)) {
6210
- if (node.kind !== "primitive" && node.kind !== "model")
6211
- continue;
6212
- const name = String(node.name ?? "").toLowerCase();
6213
- if (node.kind === "primitive" && node.primitive === "plane")
6214
- continue;
6215
- if (name.includes("sky") || name.includes("backdrop") || name.includes("wall") || name.includes("ground plane"))
6216
- continue;
6217
- const position = node.position ?? [0, 0, 0];
6218
- const scaleValue = typeof node.scale === "number"
6219
- ? [node.scale, node.scale, node.scale]
6220
- : node.scale ?? [1, 1, 1];
6221
- const baseSize = node.kind === "primitive" ? primitiveSize(node) : node.asset.bounds ?? [1, 1, 1];
6222
- const width = Math.max(0.08, Math.abs(baseSize[0] * scaleValue[0]));
6223
- const depth = Math.max(0.08, Math.abs(baseSize[2] * scaleValue[2]));
6224
- minX = Math.min(minX, position[0] - width * 0.55);
6225
- maxX = Math.max(maxX, position[0] + width * 0.55);
6226
- minZ = Math.min(minZ, position[2] - depth * 0.55);
6227
- maxZ = Math.max(maxZ, position[2] + depth * 0.55);
6228
- }
6229
- if (!Number.isFinite(minX) || !Number.isFinite(minZ)) {
6230
- return { centerX: 0, centerZ: 0, width: 4, depth: 3 };
6231
- }
6232
- const width = Math.max(2, Math.min(8, maxX - minX + 0.9));
6233
- const depth = Math.max(1.8, Math.min(7, maxZ - minZ + 0.9));
6234
- return { centerX: (minX + maxX) * 0.5, centerZ: (minZ + maxZ) * 0.5, width, depth };
6235
- }
6236
- function createThreeBloom(THREE, snapshot, effect) {
6237
- const group = new THREE.Group();
6238
- const intensity = Math.max(0.05, Math.min(1.4, effect.intensity ?? 0.35));
6239
- const texture = createThreeRadialTexture(THREE, effect.color ?? "#ffffff");
6240
- group.userData.texture = texture;
6241
- const anchors = collectBloomAnchors(snapshot);
6242
- anchors.forEach((anchor, index) => {
6243
- const materialValue = new THREE.SpriteMaterial({
6244
- map: texture,
6245
- color: new THREE.Color(anchor.color ?? effect.color ?? "#ffffff"),
6246
- transparent: true,
6247
- opacity: Math.min(0.46, 0.12 + intensity * anchor.opacity),
6248
- depthWrite: false,
6249
- depthTest: false,
6250
- blending: THREE.AdditiveBlending
6251
- });
6252
- const sprite = new THREE.Sprite(materialValue);
6253
- sprite.name = `aura-bloom-halo-${index}`;
6254
- sprite.position.set(anchor.position[0], anchor.position[1], anchor.position[2]);
6255
- sprite.scale.setScalar(anchor.size * (0.85 + intensity * 1.4));
6256
- group.add(sprite);
6257
- });
6258
- group.userData.update = (time) => {
6259
- const pulse = 1 + Math.sin(time * 0.0012) * 0.035 * intensity;
6260
- group.children.forEach((child, index) => {
6261
- const base = anchors[index]?.size ?? 1;
6262
- child.scale.setScalar(base * (0.85 + intensity * 1.4) * pulse);
6263
- });
6264
- };
6265
- return group;
6266
- }
6267
- function collectBloomAnchors(snapshot) {
6268
- const anchors = [];
6269
- for (const node of snapshot.nodes) {
6270
- if (node.kind === "primitive" && node.material?.emissive) {
6271
- const size = primitiveSize(node);
6272
- const scaleValue = typeof node.scale === "number" ? [node.scale, node.scale, node.scale] : node.scale ?? [1, 1, 1];
6273
- anchors.push({
6274
- position: node.position ?? [0, 0.65, -0.6],
6275
- size: Math.max(0.34, Math.max(size[0] * scaleValue[0], size[1] * scaleValue[1], size[2] * scaleValue[2]) * 1.45),
6276
- opacity: 0.24,
6277
- color: node.material.emissive
6278
- });
6279
- }
6280
- if (node.kind === "light" && node.light === "point") {
6281
- anchors.push({
6282
- position: node.position ?? [0, 1.4, 0.6],
6283
- size: Math.max(0.55, 0.28 + node.intensity * 0.32),
6284
- opacity: 0.18,
6285
- color: node.color
6286
- });
6287
- }
6288
- }
6289
- const modelNode = snapshot.nodes.find((node) => node.kind === "model");
6290
- if (modelNode) {
6291
- const position = modelNode.position ?? [0, 0.42, -0.65];
6292
- anchors.push({
6293
- position: [position[0], position[1] + 0.42, position[2] + 0.08],
6294
- size: 1.2,
6295
- opacity: 0.12
6296
- });
6297
- }
6298
- return anchors.length > 0 ? anchors.slice(0, 10) : [{ position: [0, 0.75, -0.75], size: 1.6, opacity: 0.16 }];
6299
- }
6300
- function createThreeRadialTexture(THREE, color) {
6301
- const canvas = document.createElement("canvas");
6302
- canvas.width = 128;
6303
- canvas.height = 128;
6304
- const context = canvas.getContext("2d");
6305
- if (context) {
6306
- const gradient = context.createRadialGradient(64, 64, 0, 64, 64, 64);
6307
- gradient.addColorStop(0, toAlphaColor(String(color), 0.95));
6308
- gradient.addColorStop(0.18, toAlphaColor(String(color), 0.42));
6309
- gradient.addColorStop(0.52, toAlphaColor(String(color), 0.14));
6310
- gradient.addColorStop(1, toAlphaColor(String(color), 0));
6311
- context.fillStyle = gradient;
6312
- context.fillRect(0, 0, canvas.width, canvas.height);
6313
- }
6314
- const texture = new THREE.CanvasTexture(canvas);
6315
- texture.colorSpace = THREE.SRGBColorSpace;
6316
- return texture;
6317
- }
6318
- function createThreeRain(THREE, effect) {
6319
- const group = new THREE.Group();
6320
- const density = Math.max(0.2, Math.min(1.6, effect.density ?? effect.intensity ?? 0.72));
6321
- const intensity = Math.max(0.1, Math.min(1.4, effect.intensity ?? 0.4));
6322
- const color = effect.color ?? "#c9e8ff";
6323
- const wind = effect.wind ?? [-0.32, -5.4, -0.16];
6324
- const requestedCount = effect.particleCount ?? Math.round(520 * density);
6325
- const dummy = new THREE.Object3D();
6326
- const layers = [];
6327
- const makeLayer = (name, count, zMin, zMax, length, width, opacity, speed) => {
6328
- const geometry = new THREE.PlaneGeometry(width, length);
6329
- const materialValue = new THREE.MeshBasicMaterial({
6330
- color: new THREE.Color(color),
6331
- transparent: true,
6332
- opacity,
6333
- depthWrite: false,
6334
- side: THREE.DoubleSide,
6335
- blending: THREE.AdditiveBlending
6336
- });
6337
- const mesh = new THREE.InstancedMesh(geometry, materialValue, count);
6338
- mesh.name = name;
6339
- mesh.userData.entries = [];
6340
- for (let index = 0; index < count; index += 1) {
6341
- const entry = {
6342
- x: seededRange(index, 11, -4.2, 4.2),
6343
- y: seededRange(index, 23, 0.22, 3.25),
6344
- z: seededRange(index, 37, zMin, zMax),
6345
- scale: seededRange(index, 41, 0.72, 1.32),
6346
- phase: seededRange(index, 53, 0, 1)
6347
- };
6348
- mesh.userData.entries.push(entry);
6349
- applyRainInstance(dummy, mesh, index, entry, 0, length, wind, speed);
6350
- }
6351
- mesh.instanceMatrix.needsUpdate = true;
6352
- layers.push({ mesh, length, speed });
6353
- group.add(mesh);
6354
- };
6355
- makeLayer("aura-rain-background-volume", Math.round(requestedCount * 0.42), -3.8, -1.1, 0.36, 0.018, Math.min(0.34, intensity * 0.36), 0.6);
6356
- makeLayer("aura-rain-midground-volume", Math.round(requestedCount * 0.36), -1.3, 0.9, 0.58, 0.026, Math.min(0.48, intensity * 0.54), 0.92);
6357
- makeLayer("aura-rain-foreground-streaks", Math.round(requestedCount * 0.22), 0.75, 2.35, 0.82, 0.034, Math.min(0.66, intensity * 0.72), 1.22);
6358
- const splashMaterial = new THREE.MeshBasicMaterial({
6359
- color: new THREE.Color(color),
6360
- transparent: true,
6361
- opacity: effect.splashes === false ? 0 : 0.48,
6362
- depthWrite: false,
6363
- side: THREE.DoubleSide,
6364
- blending: THREE.AdditiveBlending
6365
- });
6366
- const splashGeometry = new THREE.RingGeometry(0.028, 0.046, 20);
6367
- const splashCount = effect.splashes === false ? 0 : Math.round(64 * density);
6368
- const splashes = new THREE.InstancedMesh(splashGeometry, splashMaterial, splashCount);
6369
- splashes.name = "aura-rain-floor-splash-ripples";
6370
- splashes.userData.entries = [];
6371
- for (let index = 0; index < splashCount; index += 1) {
6372
- const entry = {
6373
- x: seededRange(index, 71, -3.2, 3.2),
6374
- z: seededRange(index, 89, -1.95, 1.5),
6375
- scale: seededRange(index, 97, 0.58, 1.65),
6376
- phase: seededRange(index, 109, 0, 1)
6377
- };
6378
- splashes.userData.entries.push(entry);
6379
- applySplashInstance(dummy, splashes, index, entry, 0);
6380
- }
6381
- splashes.instanceMatrix.needsUpdate = true;
6382
- group.add(splashes);
6383
- if (effect.mist !== false) {
6384
- const mistTexture = createThreeRadialTexture(THREE, color);
6385
- group.userData.mistTexture = mistTexture;
6386
- const mistMaterial = new THREE.SpriteMaterial({
6387
- map: mistTexture,
6388
- color: new THREE.Color(color),
6389
- transparent: true,
6390
- opacity: Math.min(0.18, 0.08 + intensity * 0.08),
6391
- depthWrite: false,
6392
- depthTest: false,
6393
- blending: THREE.AdditiveBlending
6394
- });
6395
- for (let index = 0; index < 5; index += 1) {
6396
- const sprite = new THREE.Sprite(mistMaterial.clone());
6397
- sprite.name = `aura-rain-mist-bank-${index}`;
6398
- sprite.position.set(-2.4 + index * 1.2, 0.32 + (index % 2) * 0.12, -1.5 + (index % 3) * 0.48);
6399
- sprite.scale.set(1.4 + index * 0.18, 0.42, 1);
6400
- group.add(sprite);
6401
- }
6402
- }
6403
- group.userData.update = (time) => {
6404
- const seconds = time / 1000;
6405
- for (const layer of layers) {
6406
- const entries = layer.mesh.userData.entries;
6407
- entries.forEach((entry, index) => {
6408
- applyRainInstance(dummy, layer.mesh, index, entry, seconds, layer.length, wind, layer.speed * (effect.speed ?? 1));
6409
- });
6410
- layer.mesh.instanceMatrix.needsUpdate = true;
6411
- }
6412
- const splashEntries = splashes.userData.entries;
6413
- splashEntries.forEach((entry, index) => applySplashInstance(dummy, splashes, index, entry, seconds));
6414
- splashes.instanceMatrix.needsUpdate = true;
6415
- };
6416
- return group;
6417
- }
6418
- function createThreeParticles(THREE, effect) {
6419
- const isFountain = effect.emitter === "fountain";
6420
- const requestedCount = effect.particleCount ?? 900;
6421
- const count = isFountain ? Math.max(24, Math.min(900, requestedCount)) : Math.max(120, Math.min(6000, requestedCount));
6422
- const radius = Math.max(0.1, effect.radius ?? 1.15);
6423
- const height = Math.max(0.2, effect.height ?? 2.4);
6424
- const intensity = Math.max(0.1, Math.min(1.8, effect.intensity ?? 0.8));
6425
- const materialMode = effect.materialMode ?? "soft-alpha";
6426
- const turbulence = Math.max(0, Math.min(1, effect.turbulence ?? effect.noise ?? 0));
6427
- const fountainLayer = !isFountain
6428
- ? "plume"
6429
- : effect.materialMode === "soft-alpha" || effect.name?.includes("mist")
6430
- ? "mist"
6431
- : effect.name?.includes("splash") || effect.name?.includes("collision")
6432
- ? "splash"
6433
- : "plume";
6434
- const texturedBillboard = effect.texturedBillboard !== false && !isFountain;
6435
- const multicolor = effect.name?.includes("multicolor") || effect.emitter === "swirl";
6436
- const lifetimeColor = isFountain || effect.name?.includes("lifetime");
6437
- const geometry = new THREE.BufferGeometry();
6438
- const positions = new Float32Array(count * 3);
6439
- const colors = new Float32Array(count * 3);
6440
- const baseColor = new THREE.Color(effect.color ?? "#7dfcff");
6441
- const accentColor = new THREE.Color(isFountain ? "#bae6fd" : "#ffd166");
6442
- const defaultLifetimeRamp = isFountain
6443
- ? ["#fff7ad", "#fef08a", "#fb923c", "#60a5fa", "#38bdf8", "#fb7185"]
6444
- : ["#fff7ad", "#ffd166", baseColor.getStyle(), "#ff7ad9", "#60a5fa"];
6445
- const lifetimeRampSource = effect.lifetimeColorRamp?.length ? effect.lifetimeColorRamp : defaultLifetimeRamp;
6446
- const lifetimeRamp = lifetimeRampSource.map((value) => new THREE.Color(value));
6447
- const scratchColor = new THREE.Color();
6448
- const sizeCurve = effect.sizeOverLife ?? [0.35, 1, 0.58];
6449
- const alphaCurve = effect.alphaOverLife ?? [0, 0.92, materialMode === "smoke" || materialMode === "dust" ? 0.18 : 0];
6450
- const writeColor = (index, seconds) => {
6451
- if (lifetimeColor) {
6452
- const life = getParticleLife(index, seconds, effect.emitter ?? "swirl");
6453
- const maxIndex = Math.max(0, lifetimeRamp.length - 1);
6454
- if (isFountain) {
6455
- const rampIndex = Math.min(maxIndex, (index + Math.floor(seededRange(index, 283, 0, 1) * lifetimeRamp.length)) % lifetimeRamp.length);
6456
- const nextIndex = Math.min(maxIndex, (rampIndex + 1) % lifetimeRamp.length);
6457
- scratchColor.copy(lifetimeRamp[rampIndex]).lerp(lifetimeRamp[nextIndex], seededRange(index, 307, 0, 0.16));
6458
- }
6459
- else {
6460
- const colorLife = isFountain
6461
- ? seededRange(index, 283, 0, 0.92) * 0.82 + life * 0.18
6462
- : life;
6463
- const scaledLife = Math.min(maxIndex, Math.max(0, colorLife * maxIndex));
6464
- const rampIndex = Math.min(maxIndex, Math.floor(scaledLife));
6465
- const nextIndex = Math.min(maxIndex, rampIndex + 1);
6466
- scratchColor.copy(lifetimeRamp[rampIndex]).lerp(lifetimeRamp[nextIndex], scaledLife - rampIndex);
6467
- }
6468
- }
6469
- else if (multicolor) {
6470
- scratchColor.setHSL(seededRange(index, 173, 0, 1), 0.84, 0.64);
6471
- }
6472
- else {
6473
- scratchColor.copy(baseColor).lerp(accentColor, seededRange(index, 173, 0, 0.42));
6474
- }
6475
- colors[index * 3] = scratchColor.r;
6476
- colors[index * 3 + 1] = scratchColor.g;
6477
- colors[index * 3 + 2] = scratchColor.b;
6478
- };
6479
- for (let index = 0; index < count; index += 1) {
6480
- writeParticlePosition(positions, index, 0, effect.emitter ?? "swirl", radius, height, index, turbulence, effect.gravity ?? 0, effect.groundCollision ?? false, fountainLayer);
6481
- writeColor(index, 0);
6482
- }
6483
- geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
6484
- geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
6485
- const spriteTexture = texturedBillboard ? createParticleSpriteTexture(THREE, materialMode) : undefined;
6486
- const baseSize = isFountain ? 0.07 + intensity * 0.032 : (multicolor ? 0.042 : 0.028) + intensity * (multicolor ? 0.024 : 0.018);
6487
- const opacity = isFountain
6488
- ? Math.min(0.94, Math.max(0.48, alphaCurve[1] ?? (0.58 + intensity * 0.24)))
6489
- : Math.min(0.98, Math.max(0.12, alphaCurve[1] ?? ((multicolor ? 0.62 : 0.48) + intensity * 0.22)));
6490
- const blending = isFountain || materialMode === "soft-alpha" || materialMode === "smoke" || materialMode === "dust" ? THREE.NormalBlending : THREE.AdditiveBlending;
6491
- if (spriteTexture) {
6492
- const plane = new THREE.PlaneGeometry(1, 1);
6493
- const materialValue = new THREE.MeshBasicMaterial({
6494
- map: spriteTexture,
6495
- vertexColors: true,
6496
- transparent: true,
6497
- opacity,
6498
- depthWrite: false,
6499
- alphaTest: 0.02,
6500
- blending,
6501
- side: THREE.DoubleSide
6502
- });
6503
- const mesh = new THREE.InstancedMesh(plane, materialValue, count);
6504
- const dummy = new THREE.Object3D();
6505
- const instanceColor = new THREE.Color();
6506
- const writeInstance = (index, seconds) => {
6507
- const offset = index * 3;
6508
- const life = getParticleLife(index, seconds, effect.emitter ?? "swirl");
6509
- const sizeLife = life < 0.5
6510
- ? (sizeCurve[0] ?? 0.35) + ((sizeCurve[1] ?? 1) - (sizeCurve[0] ?? 0.35)) * (life / 0.5)
6511
- : (sizeCurve[1] ?? 1) + ((sizeCurve[2] ?? 0.58) - (sizeCurve[1] ?? 1)) * ((life - 0.5) / 0.5);
6512
- const size = baseSize * 3.2 * Math.max(0.22, sizeLife);
6513
- dummy.position.set(positions[offset], positions[offset + 1], positions[offset + 2]);
6514
- dummy.rotation.set(0, 0, seededRange(index, 911, -Math.PI, Math.PI) + seconds * 0.18);
6515
- const elongatedWater = isFountain && materialMode !== "soft-alpha";
6516
- dummy.scale.set(size * (elongatedWater ? 1.05 : 1), size * (elongatedWater ? 1.18 : 1), size);
6517
- dummy.updateMatrix();
6518
- mesh.setMatrixAt(index, dummy.matrix);
6519
- instanceColor.setRGB(colors[offset], colors[offset + 1], colors[offset + 2]);
6520
- mesh.setColorAt(index, instanceColor);
6521
- };
6522
- for (let index = 0; index < count; index += 1)
6523
- writeInstance(index, 0);
6524
- mesh.name = effect.name ?? "aura-instanced-textured-particle-system";
6525
- mesh.instanceMatrix.needsUpdate = true;
6526
- if (mesh.instanceColor)
6527
- mesh.instanceColor.needsUpdate = true;
6528
- mesh.userData.update = (time) => {
6529
- const seconds = time / 1000 * (effect.speed ?? 1);
6530
- for (let index = 0; index < count; index += 1) {
6531
- writeParticlePosition(positions, index, seconds, effect.emitter ?? "swirl", radius, height, index, turbulence, effect.gravity ?? 0, effect.groundCollision ?? false, fountainLayer);
6532
- if (lifetimeColor)
6533
- writeColor(index, seconds);
6534
- writeInstance(index, seconds);
6535
- }
6536
- mesh.instanceMatrix.needsUpdate = true;
6537
- if (mesh.instanceColor)
6538
- mesh.instanceColor.needsUpdate = true;
6539
- };
6540
- return mesh;
6541
- }
6542
- if (isFountain && effect.texturedBillboard === false) {
6543
- const sphere = new THREE.IcosahedronGeometry(1, 1);
6544
- const sphereVertexColors = new Float32Array(sphere.getAttribute("position").count * 3);
6545
- sphereVertexColors.fill(1);
6546
- sphere.setAttribute("color", new THREE.BufferAttribute(sphereVertexColors, 3));
6547
- const materialValue = new THREE.MeshBasicMaterial({
6548
- color: "#ffffff",
6549
- vertexColors: true,
6550
- transparent: fountainLayer === "mist",
6551
- opacity: fountainLayer === "mist" ? 0.2 : 1,
6552
- depthWrite: fountainLayer !== "mist",
6553
- depthTest: true
6554
- });
6555
- materialValue.toneMapped = false;
6556
- const mesh = new THREE.InstancedMesh(sphere, materialValue, count);
6557
- mesh.frustumCulled = false;
6558
- const dummy = new THREE.Object3D();
6559
- const instanceColor = new THREE.Color();
6560
- const writeInstance = (index, seconds) => {
6561
- const offset = index * 3;
6562
- const life = getParticleLife(index, seconds, effect.emitter ?? "swirl");
6563
- const sizeLife = life < 0.5
6564
- ? (sizeCurve[0] ?? 0.35) + ((sizeCurve[1] ?? 1) - (sizeCurve[0] ?? 0.35)) * (life / 0.5)
6565
- : (sizeCurve[1] ?? 1) + ((sizeCurve[2] ?? 0.58) - (sizeCurve[1] ?? 1)) * ((life - 0.5) / 0.5);
6566
- const layerScale = fountainLayer === "mist" ? 0.22 : fountainLayer === "splash" ? 0.38 : 0.44;
6567
- const size = baseSize * layerScale * Math.max(0.24, sizeLife);
6568
- dummy.position.set(positions[offset], positions[offset + 1], positions[offset + 2]);
6569
- dummy.rotation.set(0, seededRange(index, 911, -Math.PI, Math.PI), seededRange(index, 917, -Math.PI, Math.PI));
6570
- dummy.scale.set(size, size, size);
6571
- dummy.updateMatrix();
6572
- mesh.setMatrixAt(index, dummy.matrix);
6573
- instanceColor.setRGB(colors[offset], colors[offset + 1], colors[offset + 2]);
6574
- mesh.setColorAt(index, instanceColor);
6575
- };
6576
- for (let index = 0; index < count; index += 1)
6577
- writeInstance(index, 0);
6578
- mesh.name = effect.name ?? "aura-instanced-fountain-particle-system";
6579
- mesh.instanceMatrix.needsUpdate = true;
6580
- if (mesh.instanceColor)
6581
- mesh.instanceColor.needsUpdate = true;
6582
- mesh.userData.update = (time) => {
6583
- const seconds = time / 1000 * (effect.speed ?? 1);
6584
- for (let index = 0; index < count; index += 1) {
6585
- writeParticlePosition(positions, index, seconds, effect.emitter ?? "swirl", radius, height, index, turbulence, effect.gravity ?? 0, effect.groundCollision ?? false, fountainLayer);
6586
- if (lifetimeColor)
6587
- writeColor(index, seconds);
6588
- writeInstance(index, seconds);
6589
- }
6590
- mesh.instanceMatrix.needsUpdate = true;
6591
- if (mesh.instanceColor)
6592
- mesh.instanceColor.needsUpdate = true;
6593
- };
6594
- return mesh;
6595
- }
6596
- const pointsTexture = isFountain ? createParticleSpriteTexture(THREE, materialMode) : undefined;
6597
- const materialValue = new THREE.PointsMaterial({
6598
- map: pointsTexture,
6599
- size: isFountain ? baseSize * (fountainLayer === "mist" ? 0.44 : fountainLayer === "splash" ? 0.82 : 1.08) * Math.max(0.52, sizeCurve[1] ?? 1) : baseSize * Math.max(0.25, sizeCurve[1] ?? 1),
6600
- vertexColors: true,
6601
- transparent: true,
6602
- opacity: isFountain ? Math.min(fountainLayer === "mist" ? 0.2 : fountainLayer === "splash" ? 0.78 : 0.9, opacity) : opacity,
6603
- depthWrite: false,
6604
- depthTest: true,
6605
- alphaTest: isFountain ? 0.035 : 0,
6606
- blending
6607
- });
6608
- const points = new THREE.Points(geometry, materialValue);
6609
- points.name = effect.name ?? "aura-particle-system";
6610
- points.userData.update = (time) => {
6611
- const seconds = time / 1000 * (effect.speed ?? 1);
6612
- const attribute = geometry.getAttribute("position");
6613
- const values = attribute.array;
6614
- const colorAttribute = geometry.getAttribute("color");
6615
- for (let index = 0; index < count; index += 1) {
6616
- writeParticlePosition(values, index, seconds, effect.emitter ?? "swirl", radius, height, index, turbulence, effect.gravity ?? 0, effect.groundCollision ?? false, fountainLayer);
6617
- if (lifetimeColor)
6618
- writeColor(index, seconds);
6619
- }
6620
- attribute.needsUpdate = true;
6621
- if (lifetimeColor)
6622
- colorAttribute.needsUpdate = true;
6623
- };
6624
- return points;
6625
- }
6626
- function createParticleSpriteTexture(THREE, mode) {
6627
- if (typeof document === "undefined")
6628
- return undefined;
6629
- const canvas = document.createElement("canvas");
6630
- canvas.width = 32;
6631
- canvas.height = 32;
6632
- const context = canvas.getContext("2d");
6633
- if (!context)
6634
- return undefined;
6635
- const gradient = context.createRadialGradient(16, 16, mode === "star" || mode === "spark" ? 1.5 : 0, 16, 16, 15.5);
6636
- const core = mode === "smoke" || mode === "dust" ? "rgba(220,230,240,0.68)" : "rgba(255,255,255,1)";
6637
- const mid = mode === "splash" ? "rgba(125,211,252,0.72)" : mode === "smoke" ? "rgba(148,163,184,0.32)" : "rgba(255,230,150,0.5)";
6638
- gradient.addColorStop(0, core);
6639
- gradient.addColorStop(0.42, mid);
6640
- gradient.addColorStop(1, "rgba(255,255,255,0)");
6641
- context.fillStyle = gradient;
6642
- context.fillRect(0, 0, 32, 32);
6643
- if (mode === "star" || mode === "spark") {
6644
- context.strokeStyle = "rgba(255,255,255,0.82)";
6645
- context.lineWidth = 1.2;
6646
- context.beginPath();
6647
- context.moveTo(16, 3);
6648
- context.lineTo(16, 29);
6649
- context.moveTo(3, 16);
6650
- context.lineTo(29, 16);
6651
- context.stroke();
6652
- }
6653
- const texture = new THREE.CanvasTexture(canvas);
6654
- texture.needsUpdate = true;
6655
- return texture;
6656
- }
6657
4918
  function getParticleLife(seedIndex, seconds, emitter) {
6658
4919
  if (emitter !== "fountain")
6659
4920
  return (seededRange(seedIndex, 181, 0, 1) + seconds * 0.18) % 1;
@@ -6714,23 +4975,6 @@ function writeParticlePosition(positions, index, seconds, emitter, radius, heigh
6714
4975
  positions[index * 3 + 1] = y;
6715
4976
  positions[index * 3 + 2] = z;
6716
4977
  }
6717
- function applyRainInstance(dummy, mesh, index, entry, seconds, length, wind, speed) {
6718
- const fall = ((seconds * speed + entry.phase) % 1) * 3.6;
6719
- const y = 3.15 - ((3.15 - entry.y + fall) % 3.4);
6720
- dummy.position.set(entry.x + wind[0] * fall * 0.06, y, entry.z + wind[2] * fall * 0.08);
6721
- dummy.rotation.set(0, 0, -0.17 + wind[0] * 0.055);
6722
- dummy.scale.set(entry.scale, entry.scale, entry.scale);
6723
- dummy.updateMatrix();
6724
- mesh.setMatrixAt(index, dummy.matrix);
6725
- }
6726
- function applySplashInstance(dummy, mesh, index, entry, seconds) {
6727
- const ripple = 0.35 + (((seconds * 1.8 + entry.phase) % 1) * 1.25);
6728
- dummy.position.set(entry.x, 0.022, entry.z);
6729
- dummy.rotation.set(-Math.PI / 2, 0, seededRange(index, 131, 0, Math.PI));
6730
- dummy.scale.set(entry.scale * ripple, entry.scale * ripple, entry.scale * ripple);
6731
- dummy.updateMatrix();
6732
- mesh.setMatrixAt(index, dummy.matrix);
6733
- }
6734
4978
  function seededRange(index, salt, min, max) {
6735
4979
  const value = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;
6736
4980
  const normalized = value - Math.floor(value);
@@ -6775,32 +5019,11 @@ function resolveCameraEye(snapshot, cameraSpec, time) {
6775
5019
  }
6776
5020
  return eye;
6777
5021
  }
6778
- function updateThreeCamera(THREE, cameraObject, snapshot, canvas, time) {
6779
- const cameraSpec = snapshot.camera;
6780
- const target = resolveCameraTarget(snapshot, cameraSpec);
6781
- const eye = resolveCameraEye(snapshot, cameraSpec, time);
6782
- cameraObject.aspect = canvas.width / Math.max(1, canvas.height);
6783
- cameraObject.fov = cameraSpec.fov ?? 45;
6784
- cameraObject.position.set(eye[0], eye[1], eye[2]);
6785
- cameraObject.lookAt(new THREE.Vector3(target[0], target[1], target[2]));
6786
- cameraObject.updateProjectionMatrix();
6787
- }
6788
- function disposeThreeResource(item) {
6789
- if (!item)
6790
- return;
6791
- if (Array.isArray(item)) {
6792
- for (const entry of item)
6793
- disposeThreeResource(entry);
6794
- return;
6795
- }
6796
- if (typeof item.traverse === "function") {
6797
- item.traverse((child) => {
6798
- disposeThreeResource(child.geometry);
6799
- disposeThreeResource(child.material);
6800
- });
6801
- }
6802
- if (typeof item.dispose === "function")
6803
- item.dispose();
5022
+ function collectRuntimeEffectNodes(snapshot) {
5023
+ return groups.flatten(snapshot.nodes).filter((node) => node.kind === "effect");
5024
+ }
5025
+ function hasRuntimePostProcessEffects(effectNodes) {
5026
+ return effectNodes.some((node) => node.effect === "bloom" || node.effect === "ambient-occlusion" || node.effect === "contact-occlusion");
6804
5027
  }
6805
5028
  async function createWebGLSceneRenderer(canvas, snapshot) {
6806
5029
  const gl = canvas.getContext("webgl2", { antialias: true, preserveDrawingBuffer: true });
@@ -6824,10 +5047,10 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
6824
5047
  const background = colorToClearColor(snapshot.background);
6825
5048
  gl.enable(gl.DEPTH_TEST);
6826
5049
  gl.disable(gl.CULL_FACE);
6827
- const requestedEffectNodes = collectThreeEffectNodes(snapshot);
5050
+ const requestedEffectNodes = collectRuntimeEffectNodes(snapshot);
6828
5051
  const runtimeRendererDiagnostics = createRendererDiagnosticReport(snapshot, {
6829
5052
  mounted: true,
6830
- backend: "webgl2-fallback",
5053
+ backend: "webgl2-agent-runtime",
6831
5054
  postprocess: {
6832
5055
  renderPass: false,
6833
5056
  outputPass: false,
@@ -6836,9 +5059,9 @@ async function createWebGLSceneRenderer(canvas, snapshot) {
6836
5059
  contactOcclusionReceiver: false,
6837
5060
  pixelBacked: false,
6838
5061
  actualPasses: [],
6839
- fallbackPasses: hasThreePostProcessEffects(requestedEffectNodes) ? ["minimal-webgl-direct-render"] : []
5062
+ fallbackPasses: hasRuntimePostProcessEffects(requestedEffectNodes) ? ["webgl2-direct-render"] : []
6840
5063
  },
6841
- warnings: ["Minimal WebGL fallback does not run the Three.js composer, PMREM environment, shadow maps, or GLB animation mixers."]
5064
+ warnings: ["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 until the production renderer adapter covers them."]
6842
5065
  });
6843
5066
  return {
6844
5067
  diagnostics: runtimeRendererDiagnostics,