@skenora/runtime 0.1.2

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 (270) hide show
  1. package/README.md +92 -0
  2. package/dist/core/built-in-runtime-capabilities.d.ts +21 -0
  3. package/dist/core/built-in-runtime-capabilities.d.ts.map +1 -0
  4. package/dist/core/built-in-runtime-capabilities.js +175 -0
  5. package/dist/core/built-in-runtime-capabilities.js.map +1 -0
  6. package/dist/core/runtime-capability-registry.d.ts +90 -0
  7. package/dist/core/runtime-capability-registry.d.ts.map +1 -0
  8. package/dist/core/runtime-capability-registry.js +42 -0
  9. package/dist/core/runtime-capability-registry.js.map +1 -0
  10. package/dist/core/runtime-document-coordinator.d.ts +28 -0
  11. package/dist/core/runtime-document-coordinator.d.ts.map +1 -0
  12. package/dist/core/runtime-document-coordinator.js +124 -0
  13. package/dist/core/runtime-document-coordinator.js.map +1 -0
  14. package/dist/core/runtime-events.d.ts +43 -0
  15. package/dist/core/runtime-events.d.ts.map +1 -0
  16. package/dist/core/runtime-events.js +2 -0
  17. package/dist/core/runtime-events.js.map +1 -0
  18. package/dist/core/runtime-session.d.ts +93 -0
  19. package/dist/core/runtime-session.d.ts.map +1 -0
  20. package/dist/core/runtime-session.js +1344 -0
  21. package/dist/core/runtime-session.js.map +1 -0
  22. package/dist/editor-tools/camera-path-editing-controller.d.ts +36 -0
  23. package/dist/editor-tools/camera-path-editing-controller.d.ts.map +1 -0
  24. package/dist/editor-tools/camera-path-editing-controller.js +212 -0
  25. package/dist/editor-tools/camera-path-editing-controller.js.map +1 -0
  26. package/dist/editor-tools/camera-path-visualizer.d.ts +28 -0
  27. package/dist/editor-tools/camera-path-visualizer.d.ts.map +1 -0
  28. package/dist/editor-tools/camera-path-visualizer.js +151 -0
  29. package/dist/editor-tools/camera-path-visualizer.js.map +1 -0
  30. package/dist/editor-tools/drop-preview-controller.d.ts +34 -0
  31. package/dist/editor-tools/drop-preview-controller.d.ts.map +1 -0
  32. package/dist/editor-tools/drop-preview-controller.js +209 -0
  33. package/dist/editor-tools/drop-preview-controller.js.map +1 -0
  34. package/dist/editor-tools/grid-helper.d.ts +18 -0
  35. package/dist/editor-tools/grid-helper.d.ts.map +1 -0
  36. package/dist/editor-tools/grid-helper.js +27 -0
  37. package/dist/editor-tools/grid-helper.js.map +1 -0
  38. package/dist/editor-tools/light-editing-controller.d.ts +25 -0
  39. package/dist/editor-tools/light-editing-controller.d.ts.map +1 -0
  40. package/dist/editor-tools/light-editing-controller.js +207 -0
  41. package/dist/editor-tools/light-editing-controller.js.map +1 -0
  42. package/dist/editor-tools/node-operations.d.ts +51 -0
  43. package/dist/editor-tools/node-operations.d.ts.map +1 -0
  44. package/dist/editor-tools/node-operations.js +182 -0
  45. package/dist/editor-tools/node-operations.js.map +1 -0
  46. package/dist/editor-tools/selection-controller.d.ts +44 -0
  47. package/dist/editor-tools/selection-controller.d.ts.map +1 -0
  48. package/dist/editor-tools/selection-controller.js +299 -0
  49. package/dist/editor-tools/selection-controller.js.map +1 -0
  50. package/dist/editor-tools/shape-drawing-controller.d.ts +45 -0
  51. package/dist/editor-tools/shape-drawing-controller.d.ts.map +1 -0
  52. package/dist/editor-tools/shape-drawing-controller.js +205 -0
  53. package/dist/editor-tools/shape-drawing-controller.js.map +1 -0
  54. package/dist/editor-tools/shape-editing-controller.d.ts +40 -0
  55. package/dist/editor-tools/shape-editing-controller.d.ts.map +1 -0
  56. package/dist/editor-tools/shape-editing-controller.js +340 -0
  57. package/dist/editor-tools/shape-editing-controller.js.map +1 -0
  58. package/dist/editor.d.ts +12 -0
  59. package/dist/editor.d.ts.map +1 -0
  60. package/dist/editor.js +12 -0
  61. package/dist/editor.js.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +2 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/inspection/node-tools.d.ts +51 -0
  67. package/dist/inspection/node-tools.d.ts.map +1 -0
  68. package/dist/inspection/node-tools.js +282 -0
  69. package/dist/inspection/node-tools.js.map +1 -0
  70. package/dist/interaction/runtime-third-person-controller.d.ts +24 -0
  71. package/dist/interaction/runtime-third-person-controller.d.ts.map +1 -0
  72. package/dist/interaction/runtime-third-person-controller.js +71 -0
  73. package/dist/interaction/runtime-third-person-controller.js.map +1 -0
  74. package/dist/interaction/third-person-controller.d.ts +61 -0
  75. package/dist/interaction/third-person-controller.d.ts.map +1 -0
  76. package/dist/interaction/third-person-controller.js +400 -0
  77. package/dist/interaction/third-person-controller.js.map +1 -0
  78. package/dist/interaction.d.ts +3 -0
  79. package/dist/interaction.d.ts.map +1 -0
  80. package/dist/interaction.js +3 -0
  81. package/dist/interaction.js.map +1 -0
  82. package/dist/presentation/scene-renderer.d.ts +75 -0
  83. package/dist/presentation/scene-renderer.d.ts.map +1 -0
  84. package/dist/presentation/scene-renderer.js +324 -0
  85. package/dist/presentation/scene-renderer.js.map +1 -0
  86. package/dist/presentation/viewport-controller.d.ts +13 -0
  87. package/dist/presentation/viewport-controller.d.ts.map +1 -0
  88. package/dist/presentation/viewport-controller.js +186 -0
  89. package/dist/presentation/viewport-controller.js.map +1 -0
  90. package/dist/projection/annotation-projector.d.ts +28 -0
  91. package/dist/projection/annotation-projector.d.ts.map +1 -0
  92. package/dist/projection/annotation-projector.js +98 -0
  93. package/dist/projection/annotation-projector.js.map +1 -0
  94. package/dist/projection/built-in-entity-projectors.d.ts +7 -0
  95. package/dist/projection/built-in-entity-projectors.d.ts.map +1 -0
  96. package/dist/projection/built-in-entity-projectors.js +75 -0
  97. package/dist/projection/built-in-entity-projectors.js.map +1 -0
  98. package/dist/projection/camera-entity-factory.d.ts +9 -0
  99. package/dist/projection/camera-entity-factory.d.ts.map +1 -0
  100. package/dist/projection/camera-entity-factory.js +93 -0
  101. package/dist/projection/camera-entity-factory.js.map +1 -0
  102. package/dist/projection/document-applier.d.ts +38 -0
  103. package/dist/projection/document-applier.d.ts.map +1 -0
  104. package/dist/projection/document-applier.js +219 -0
  105. package/dist/projection/document-applier.js.map +1 -0
  106. package/dist/projection/entity-projection-registry.d.ts +56 -0
  107. package/dist/projection/entity-projection-registry.d.ts.map +1 -0
  108. package/dist/projection/entity-projection-registry.js +121 -0
  109. package/dist/projection/entity-projection-registry.js.map +1 -0
  110. package/dist/projection/model-importer.d.ts +41 -0
  111. package/dist/projection/model-importer.d.ts.map +1 -0
  112. package/dist/projection/model-importer.js +469 -0
  113. package/dist/projection/model-importer.js.map +1 -0
  114. package/dist/projection/particle-capability.d.ts +5 -0
  115. package/dist/projection/particle-capability.d.ts.map +1 -0
  116. package/dist/projection/particle-capability.js +2 -0
  117. package/dist/projection/particle-capability.js.map +1 -0
  118. package/dist/projection/particle-factory.d.ts +18 -0
  119. package/dist/projection/particle-factory.d.ts.map +1 -0
  120. package/dist/projection/particle-factory.js +313 -0
  121. package/dist/projection/particle-factory.js.map +1 -0
  122. package/dist/projection/primitive-factory.d.ts +5 -0
  123. package/dist/projection/primitive-factory.d.ts.map +1 -0
  124. package/dist/projection/primitive-factory.js +55 -0
  125. package/dist/projection/primitive-factory.js.map +1 -0
  126. package/dist/projection/procedural-factory.d.ts +6 -0
  127. package/dist/projection/procedural-factory.d.ts.map +1 -0
  128. package/dist/projection/procedural-factory.js +34 -0
  129. package/dist/projection/procedural-factory.js.map +1 -0
  130. package/dist/projection/runtime-projection-state.d.ts +19 -0
  131. package/dist/projection/runtime-projection-state.d.ts.map +1 -0
  132. package/dist/projection/runtime-projection-state.js +58 -0
  133. package/dist/projection/runtime-projection-state.js.map +1 -0
  134. package/dist/projection/shape-factory.d.ts +5 -0
  135. package/dist/projection/shape-factory.d.ts.map +1 -0
  136. package/dist/projection/shape-factory.js +101 -0
  137. package/dist/projection/shape-factory.js.map +1 -0
  138. package/dist/projection-entry.d.ts +10 -0
  139. package/dist/projection-entry.d.ts.map +1 -0
  140. package/dist/projection-entry.js +8 -0
  141. package/dist/projection-entry.js.map +1 -0
  142. package/dist/simulation/fixed-step-scheduler.d.ts +13 -0
  143. package/dist/simulation/fixed-step-scheduler.d.ts.map +1 -0
  144. package/dist/simulation/fixed-step-scheduler.js +45 -0
  145. package/dist/simulation/fixed-step-scheduler.js.map +1 -0
  146. package/dist/simulation/simulation-runtime-model.d.ts +24 -0
  147. package/dist/simulation/simulation-runtime-model.d.ts.map +1 -0
  148. package/dist/simulation/simulation-runtime-model.js +63 -0
  149. package/dist/simulation/simulation-runtime-model.js.map +1 -0
  150. package/dist/simulation/simulation-wgsl.d.ts +3 -0
  151. package/dist/simulation/simulation-wgsl.d.ts.map +1 -0
  152. package/dist/simulation/simulation-wgsl.js +294 -0
  153. package/dist/simulation/simulation-wgsl.js.map +1 -0
  154. package/dist/simulation/webgpu-simulation-renderer.d.ts +67 -0
  155. package/dist/simulation/webgpu-simulation-renderer.d.ts.map +1 -0
  156. package/dist/simulation/webgpu-simulation-renderer.js +504 -0
  157. package/dist/simulation/webgpu-simulation-renderer.js.map +1 -0
  158. package/dist/simulation/webgpu-types.d.ts +90 -0
  159. package/dist/simulation/webgpu-types.d.ts.map +1 -0
  160. package/dist/simulation/webgpu-types.js +10 -0
  161. package/dist/simulation/webgpu-types.js.map +1 -0
  162. package/dist/simulation.d.ts +4 -0
  163. package/dist/simulation.d.ts.map +1 -0
  164. package/dist/simulation.js +4 -0
  165. package/dist/simulation.js.map +1 -0
  166. package/dist/systems/animation-controller.d.ts +52 -0
  167. package/dist/systems/animation-controller.d.ts.map +1 -0
  168. package/dist/systems/animation-controller.js +146 -0
  169. package/dist/systems/animation-controller.js.map +1 -0
  170. package/dist/systems/camera-controller.d.ts +41 -0
  171. package/dist/systems/camera-controller.d.ts.map +1 -0
  172. package/dist/systems/camera-controller.js +207 -0
  173. package/dist/systems/camera-controller.js.map +1 -0
  174. package/dist/systems/camera-path-controller.d.ts +39 -0
  175. package/dist/systems/camera-path-controller.d.ts.map +1 -0
  176. package/dist/systems/camera-path-controller.js +287 -0
  177. package/dist/systems/camera-path-controller.js.map +1 -0
  178. package/dist/systems/environment-controller.d.ts +18 -0
  179. package/dist/systems/environment-controller.d.ts.map +1 -0
  180. package/dist/systems/environment-controller.js +251 -0
  181. package/dist/systems/environment-controller.js.map +1 -0
  182. package/dist/systems/environment-texture.d.ts +7 -0
  183. package/dist/systems/environment-texture.d.ts.map +1 -0
  184. package/dist/systems/environment-texture.js +57 -0
  185. package/dist/systems/environment-texture.js.map +1 -0
  186. package/dist/systems/fog-controller.d.ts +12 -0
  187. package/dist/systems/fog-controller.d.ts.map +1 -0
  188. package/dist/systems/fog-controller.js +60 -0
  189. package/dist/systems/fog-controller.js.map +1 -0
  190. package/dist/systems/generated-texture-cache.d.ts +33 -0
  191. package/dist/systems/generated-texture-cache.d.ts.map +1 -0
  192. package/dist/systems/generated-texture-cache.js +101 -0
  193. package/dist/systems/generated-texture-cache.js.map +1 -0
  194. package/dist/systems/glass-transmission-controller.d.ts +21 -0
  195. package/dist/systems/glass-transmission-controller.d.ts.map +1 -0
  196. package/dist/systems/glass-transmission-controller.js +119 -0
  197. package/dist/systems/glass-transmission-controller.js.map +1 -0
  198. package/dist/systems/gpu-program-cache.d.ts +19 -0
  199. package/dist/systems/gpu-program-cache.d.ts.map +1 -0
  200. package/dist/systems/gpu-program-cache.js +95 -0
  201. package/dist/systems/gpu-program-cache.js.map +1 -0
  202. package/dist/systems/ground-controller.d.ts +14 -0
  203. package/dist/systems/ground-controller.d.ts.map +1 -0
  204. package/dist/systems/ground-controller.js +121 -0
  205. package/dist/systems/ground-controller.js.map +1 -0
  206. package/dist/systems/lighting-controller.d.ts +23 -0
  207. package/dist/systems/lighting-controller.d.ts.map +1 -0
  208. package/dist/systems/lighting-controller.js +204 -0
  209. package/dist/systems/lighting-controller.js.map +1 -0
  210. package/dist/systems/material-animation-controller.d.ts +26 -0
  211. package/dist/systems/material-animation-controller.d.ts.map +1 -0
  212. package/dist/systems/material-animation-controller.js +221 -0
  213. package/dist/systems/material-animation-controller.js.map +1 -0
  214. package/dist/systems/material-binding-resolver.d.ts +16 -0
  215. package/dist/systems/material-binding-resolver.d.ts.map +1 -0
  216. package/dist/systems/material-binding-resolver.js +54 -0
  217. package/dist/systems/material-binding-resolver.js.map +1 -0
  218. package/dist/systems/material-controller.d.ts +29 -0
  219. package/dist/systems/material-controller.d.ts.map +1 -0
  220. package/dist/systems/material-controller.js +233 -0
  221. package/dist/systems/material-controller.js.map +1 -0
  222. package/dist/systems/material-preparation.d.ts +9 -0
  223. package/dist/systems/material-preparation.d.ts.map +1 -0
  224. package/dist/systems/material-preparation.js +51 -0
  225. package/dist/systems/material-preparation.js.map +1 -0
  226. package/dist/systems/material-slots.d.ts +18 -0
  227. package/dist/systems/material-slots.d.ts.map +1 -0
  228. package/dist/systems/material-slots.js +78 -0
  229. package/dist/systems/material-slots.js.map +1 -0
  230. package/dist/systems/owned-material-controller.d.ts +20 -0
  231. package/dist/systems/owned-material-controller.d.ts.map +1 -0
  232. package/dist/systems/owned-material-controller.js +611 -0
  233. package/dist/systems/owned-material-controller.js.map +1 -0
  234. package/dist/systems/pbr-material.d.ts +22 -0
  235. package/dist/systems/pbr-material.d.ts.map +1 -0
  236. package/dist/systems/pbr-material.js +358 -0
  237. package/dist/systems/pbr-material.js.map +1 -0
  238. package/dist/systems/post-process-controller.d.ts +17 -0
  239. package/dist/systems/post-process-controller.d.ts.map +1 -0
  240. package/dist/systems/post-process-controller.js +246 -0
  241. package/dist/systems/post-process-controller.js.map +1 -0
  242. package/dist/systems/runtime-clock.d.ts +9 -0
  243. package/dist/systems/runtime-clock.d.ts.map +1 -0
  244. package/dist/systems/runtime-clock.js +25 -0
  245. package/dist/systems/runtime-clock.js.map +1 -0
  246. package/dist/systems/skybox-controller.d.ts +15 -0
  247. package/dist/systems/skybox-controller.d.ts.map +1 -0
  248. package/dist/systems/skybox-controller.js +282 -0
  249. package/dist/systems/skybox-controller.js.map +1 -0
  250. package/dist/systems/sprite-sheet-controller.d.ts +52 -0
  251. package/dist/systems/sprite-sheet-controller.d.ts.map +1 -0
  252. package/dist/systems/sprite-sheet-controller.js +187 -0
  253. package/dist/systems/sprite-sheet-controller.js.map +1 -0
  254. package/dist/systems/surface-effect-plugin.d.ts +26 -0
  255. package/dist/systems/surface-effect-plugin.d.ts.map +1 -0
  256. package/dist/systems/surface-effect-plugin.js +204 -0
  257. package/dist/systems/surface-effect-plugin.js.map +1 -0
  258. package/dist/systems/surface-program-plugin.d.ts +29 -0
  259. package/dist/systems/surface-program-plugin.d.ts.map +1 -0
  260. package/dist/systems/surface-program-plugin.js +139 -0
  261. package/dist/systems/surface-program-plugin.js.map +1 -0
  262. package/dist/systems/texture-animation-controller.d.ts +18 -0
  263. package/dist/systems/texture-animation-controller.d.ts.map +1 -0
  264. package/dist/systems/texture-animation-controller.js +147 -0
  265. package/dist/systems/texture-animation-controller.js.map +1 -0
  266. package/dist/systems/weather-controller.d.ts +16 -0
  267. package/dist/systems/weather-controller.d.ts.map +1 -0
  268. package/dist/systems/weather-controller.js +205 -0
  269. package/dist/systems/weather-controller.js.map +1 -0
  270. package/package.json +63 -0
@@ -0,0 +1,1344 @@
1
+ import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
2
+ import { Engine } from "@babylonjs/core/Engines/engine";
3
+ import { PBRMaterial } from "@babylonjs/core/Materials/PBR/pbrMaterial";
4
+ import { Scene } from "@babylonjs/core/scene";
5
+ import { normalizeMaterialExecutionPolicy, SCENE_CAPABILITY_VERSIONS, OWNED_PBR_CAPABILITY_ID, findUnavailableSceneCapabilities, SceneCapabilityUnavailableError, normalizeRuntimeQualityPolicy, applySceneQualityPolicy, restoreRequestedQuality, MATERIAL_EFFECT_CAPABILITY_IDS, isMaterialEffectRuntimeProperty, MaterialAnimationError, MATERIAL_ANIMATION_CAPABILITY_ID, MATERIAL_PROGRAM_CAPABILITY_ID, MATERIAL_PROGRAM_V2_CAPABILITY_ID, MATERIAL_PROGRAM_LIMITS, TEXTURE_PROGRAM_CAPABILITY_ID, TEXTURE_PROGRAM_LIMITS, RENDER_GRAPH_CAPABILITY_ID, RENDER_GRAPH_LIMITS, PROCEDURAL_GEOMETRY_CAPABILITY_ID, PROCEDURAL_GEOMETRY_LIMITS, validateMaterialAnimations, getMaterialEffectSceneConflicts, createSkenoraDiagnostic, finalizeSkenoraDiagnostics, } from "@skenora/contracts";
6
+ import { EventHub } from "@skenora/events";
7
+ import { PluginHost } from "@skenora/plugins";
8
+ import { calculateNodeBounds, CameraController, } from "../systems/camera-controller.js";
9
+ import { AnnotationProjector } from "../projection/annotation-projector.js";
10
+ import { CameraPathController } from "../systems/camera-path-controller.js";
11
+ import { AnimationController } from "../systems/animation-controller.js";
12
+ import { SceneDocumentApplier } from "../projection/document-applier.js";
13
+ import { createBuiltInEntityProjectors } from "../projection/built-in-entity-projectors.js";
14
+ import { EntityProjectionRegistry } from "../projection/entity-projection-registry.js";
15
+ import { RuntimeProjectionState } from "../projection/runtime-projection-state.js";
16
+ import { applyTransform, importModelEntity, isTransformNode, setNodeVisible, readAuthoredGroupState, } from "../projection/model-importer.js";
17
+ import { PARTICLE_PLAYBACK_CAPABILITY, } from "../projection/particle-capability.js";
18
+ import { ViewportController } from "../presentation/viewport-controller.js";
19
+ import { TextureAnimationController } from "../systems/texture-animation-controller.js";
20
+ import { MaterialAnimationController } from "../systems/material-animation-controller.js";
21
+ import { RuntimeClock } from "../systems/runtime-clock.js";
22
+ import { MaterialBindingResolver } from "../systems/material-binding-resolver.js";
23
+ import { setPbrMaterialProperty } from "../systems/pbr-material.js";
24
+ import { listMaterialSlots, MaterialProjectionError, } from "../systems/material-slots.js";
25
+ import { applyCameraEntityProperties, captureCameraEntityProperties, captureCameraEntityTransform, } from "../projection/camera-entity-factory.js";
26
+ import { calculateNodeStatistics, describeNodeTree, RuntimeNodeEffects, } from "../inspection/node-tools.js";
27
+ import { createRuntimeAnimationCapability, createRuntimeCameraCapability, createRuntimeMaterialCapability, createRuntimeNodeCapability, createRuntimeParticleCapability, createRuntimeTextureAnimationCapability, } from "./built-in-runtime-capabilities.js";
28
+ import { RUNTIME_ANIMATIONS_CAPABILITY, RUNTIME_CAMERA_CAPABILITY, RUNTIME_MATERIALS_CAPABILITY, RUNTIME_MATERIAL_ANIMATIONS_CAPABILITY, RUNTIME_NODES_CAPABILITY, RUNTIME_PARTICLES_CAPABILITY, RUNTIME_TEXTURE_ANIMATIONS_CAPABILITY, RuntimeCapabilityRegistry, } from "./runtime-capability-registry.js";
29
+ import { RuntimeDocumentCoordinator, } from "./runtime-document-coordinator.js";
30
+ import { GpuProgramCache } from "../systems/gpu-program-cache.js";
31
+ const ALL_DOCUMENT_SETTINGS = [
32
+ "camera",
33
+ "cameraPaths",
34
+ "environment",
35
+ "ground",
36
+ "fog",
37
+ "weather",
38
+ "lights",
39
+ "materials",
40
+ "textureAnimations",
41
+ "postProcess",
42
+ ];
43
+ export class RuntimeSession {
44
+ canvas;
45
+ engine;
46
+ scene;
47
+ annotations;
48
+ viewport;
49
+ events;
50
+ capabilities;
51
+ executionMode;
52
+ materialPolicy;
53
+ qualityPolicy;
54
+ #adaptRatio;
55
+ #engineRatioLimit;
56
+ #qualityState = null;
57
+ #animations;
58
+ #textureAnimations;
59
+ #materialAnimations;
60
+ #clock = new RuntimeClock();
61
+ #nodeEffects;
62
+ #ownsEvents;
63
+ #entityProjections;
64
+ #projectionState = new RuntimeProjectionState();
65
+ #materialResolver;
66
+ #documents;
67
+ #plugins;
68
+ #gpuPrograms;
69
+ #renderFrame = () => {
70
+ if (this.#contextLost) {
71
+ this.#clock.resetInterval();
72
+ return;
73
+ }
74
+ this.#clock.tick(performance.now());
75
+ this.scene.render();
76
+ };
77
+ #camera = null;
78
+ #cameraPaths = null;
79
+ #applier = null;
80
+ #resizeObserver = null;
81
+ #grid = null;
82
+ #rendering = false;
83
+ #behaviorDocument = null;
84
+ #behaviorsRunning = false;
85
+ #disposed = false;
86
+ #contextLost = false;
87
+ #lastProbeVerdict = null;
88
+ constructor(canvas, options = {}) {
89
+ this.canvas = canvas;
90
+ this.materialPolicy = normalizeMaterialExecutionPolicy(options.materialPolicy);
91
+ this.qualityPolicy = normalizeRuntimeQualityPolicy(options.qualityPolicy);
92
+ this.#adaptRatio =
93
+ options.adaptToDeviceRatio === true ||
94
+ options.engineOptions?.adaptToDeviceRatio === true;
95
+ this.#engineRatioLimit = options.engineOptions?.limitDeviceRatio;
96
+ this.executionMode = options.executionMode ?? "authoring";
97
+ this.events = options.events ?? new EventHub();
98
+ this.#ownsEvents = options.events === undefined;
99
+ this.engine = new Engine(canvas, options.antialias ?? true, options.engineOptions, options.adaptToDeviceRatio ?? false);
100
+ this.#gpuPrograms = new GpuProgramCache(this.engine.webGLVersion >= 2 ? "babylon-webgl2" : "babylon-webgl1", this.materialPolicy.maxInstances);
101
+ this.scene = new Scene(this.engine);
102
+ if (this.qualityPolicy.maxPixelRatio !== undefined) {
103
+ // The host cap owns DPR updates. Babylon's proportional auto-resize would
104
+ // otherwise grow the scale past that cap when the display DPR changes.
105
+ this.engine.adaptToDeviceRatio = false;
106
+ this.#resizeForQuality();
107
+ }
108
+ this.engine.onContextLostObservable.add(() => {
109
+ this.#contextLost = true;
110
+ this.#clock.resetInterval();
111
+ });
112
+ this.engine.onContextRestoredObservable.add(() => {
113
+ this.#contextLost = false;
114
+ this.#clock.resetInterval();
115
+ });
116
+ this.#materialResolver = new MaterialBindingResolver(this.scene);
117
+ this.#entityProjections = new EntityProjectionRegistry(createBuiltInEntityProjectors({
118
+ shouldAutoStartBehaviors: () => this.#behaviorsRunning,
119
+ }));
120
+ this.#animations = new AnimationController(this.scene);
121
+ this.annotations = new AnnotationProjector(this.scene, (entityId) => {
122
+ const node = this.#getNode(entityId);
123
+ return isTransformNode(node) ? node : null;
124
+ });
125
+ this.viewport = new ViewportController(this.scene);
126
+ this.#textureAnimations = new TextureAnimationController(this.scene, (materialId) => this.#getMaterials(materialId), this.#clock, (animationId, error) => this.#reportRuntimeError(error, `textureAnimation:${animationId}`));
127
+ this.#materialAnimations = new MaterialAnimationController(this.#clock, {
128
+ validate: (materialId, animation) => this.#validateMaterialAnimation(materialId, animation),
129
+ write: (materialId, property, value) => this.#writeMaterialProperty(materialId, property, value),
130
+ }, (state) => this.events.publish("runtime.material.animation", state, {
131
+ source: "runtime",
132
+ }), this.materialPolicy.maxActiveAnimations);
133
+ this.#nodeEffects = new RuntimeNodeEffects(this.scene);
134
+ this.capabilities = new RuntimeCapabilityRegistry();
135
+ const nodes = createRuntimeNodeCapability((entityId) => this.#getNode(entityId), this.#nodeEffects);
136
+ this.capabilities.register(RUNTIME_NODES_CAPABILITY, nodes);
137
+ this.capabilities.register(RUNTIME_MATERIAL_ANIMATIONS_CAPABILITY, {
138
+ play: (materialId, animationId, options) => this.#materialAnimations.play(materialId, animationId, options),
139
+ pause: (materialId, animationId) => this.#materialAnimations.pause(materialId, animationId),
140
+ resume: (materialId, animationId) => this.#materialAnimations.resume(materialId, animationId),
141
+ stop: (materialId, animationId) => this.#materialAnimations.stop(materialId, animationId),
142
+ reset: (materialId, animationId) => this.#materialAnimations.reset(materialId, animationId),
143
+ getState: (materialId, animationId) => this.#materialAnimations.getState(materialId, animationId),
144
+ });
145
+ this.capabilities.register(RUNTIME_MATERIALS_CAPABILITY, createRuntimeMaterialCapability((materialId) => this.#getMaterial(materialId), (materialId, property, value) => this.#setMaterialProperty(materialId, property, value)));
146
+ this.capabilities.register(RUNTIME_CAMERA_CAPABILITY, createRuntimeCameraCapability({
147
+ frame: (entityIds) => this.#frame(entityIds),
148
+ activateEntity: (entityId, controls) => this.#activateCameraEntity(entityId, controls),
149
+ restoreViewport: () => this.#restoreViewportCamera(),
150
+ getPaths: () => this.#cameraPaths,
151
+ }));
152
+ this.capabilities.register(RUNTIME_ANIMATIONS_CAPABILITY, createRuntimeAnimationCapability(this.#animations, nodes));
153
+ this.capabilities.register(RUNTIME_PARTICLES_CAPABILITY, createRuntimeParticleCapability((entityId, running) => this.#setParticleRunning(entityId, running)));
154
+ this.capabilities.register(RUNTIME_TEXTURE_ANIMATIONS_CAPABILITY, createRuntimeTextureAnimationCapability(this.#textureAnimations));
155
+ this.#documents = new RuntimeDocumentCoordinator(this.events, () => this.#resetDocumentProjection(), (document, resources, context) => this.#projectDocument(document, resources, context), (document) => this.#assertCapabilities(document));
156
+ this.#plugins = new PluginHost(this);
157
+ this.resume();
158
+ if ((options.autoResize ?? true) && typeof ResizeObserver !== "undefined") {
159
+ this.#resizeObserver = new ResizeObserver(() => this.resize());
160
+ this.#resizeObserver.observe(canvas);
161
+ }
162
+ try {
163
+ for (const plugin of options.plugins ?? [])
164
+ this.install(plugin);
165
+ for (const extension of options.extensions ?? [])
166
+ this.install(extension);
167
+ }
168
+ catch (error) {
169
+ this.dispose();
170
+ throw error;
171
+ }
172
+ this.events.publish("runtime.ready", { scene: this.scene }, { source: "runtime" });
173
+ }
174
+ get viewportCamera() {
175
+ return this.#camera;
176
+ }
177
+ get rendering() {
178
+ return this.#rendering;
179
+ }
180
+ /** Whether document-authored auto behaviors are currently running. */
181
+ get behaviorsRunning() {
182
+ return this.#behaviorsRunning;
183
+ }
184
+ getQualityState() {
185
+ if (!this.#qualityState)
186
+ return null;
187
+ return {
188
+ ...structuredClone(this.#qualityState),
189
+ resolution: {
190
+ requestedPixelRatio: this.#requestedPixelRatio(),
191
+ effectivePixelRatio: 1 / this.engine.getHardwareScalingLevel(),
192
+ },
193
+ };
194
+ }
195
+ /** Serializable observation of the active Babylon backend and device caps. */
196
+ getBackendSnapshot() {
197
+ const unavailable = this.#disposed || this.engine.isDisposed;
198
+ const caps = unavailable
199
+ ? {}
200
+ : this.engine.getCaps();
201
+ const webGlVersion = readFiniteNumber(this.engine.webGLVersion) ?? 0;
202
+ const contextStatus = unavailable
203
+ ? "disposed"
204
+ : this.#contextLost
205
+ ? "lost"
206
+ : webGlVersion > 0
207
+ ? "available"
208
+ : "unavailable";
209
+ const diagnostics = [];
210
+ if (contextStatus !== "available") {
211
+ diagnostics.push(createSkenoraDiagnostic({
212
+ code: `runtime-backend-${contextStatus}`,
213
+ severity: contextStatus === "lost" ? "error" : "warning",
214
+ stage: "environment",
215
+ message: `Runtime backend is ${contextStatus}`,
216
+ recoverable: contextStatus !== "disposed",
217
+ repair: contextStatus === "lost"
218
+ ? "Wait for context restoration or recreate the runtime."
219
+ : "Create an active browser RuntimeSession before probing.",
220
+ }));
221
+ }
222
+ const featureKeys = [
223
+ "depthTextureExtension",
224
+ "drawBuffersExtension",
225
+ "instancedArrays",
226
+ "parallelShaderCompile",
227
+ "standardDerivatives",
228
+ "textureFloat",
229
+ "textureFloatLinearFiltering",
230
+ "textureHalfFloat",
231
+ "textureHalfFloatLinearFiltering",
232
+ "vertexArrayObject",
233
+ ];
234
+ const features = featureKeys.filter((key) => caps[key] === true);
235
+ const limitKeys = [
236
+ "maxCombinedTexturesImageUnits",
237
+ "maxCubemapTextureSize",
238
+ "maxFragmentUniformVectors",
239
+ "maxMSAASamples",
240
+ "maxTextureImageUnits",
241
+ "maxTextureSize",
242
+ "maxVaryingVectors",
243
+ "maxVertexAttribs",
244
+ "maxVertexTextureImageUnits",
245
+ "maxVertexUniformVectors",
246
+ ];
247
+ const limits = {};
248
+ for (const key of limitKeys) {
249
+ const value = readFiniteNumber(caps[key]);
250
+ if (value !== undefined)
251
+ limits[key] = value;
252
+ }
253
+ const quality = unavailable ? null : this.getQualityState();
254
+ return Object.freeze({
255
+ version: 1,
256
+ capturedAt: new Date().toISOString(),
257
+ backend: webGlVersion >= 2
258
+ ? "babylon-webgl2"
259
+ : webGlVersion >= 1
260
+ ? "babylon-webgl1"
261
+ : "unknown",
262
+ shaderLanguage: webGlVersion >= 1 ? "glsl" : "unknown",
263
+ engine: Object.freeze({ name: "Babylon.js Engine" }),
264
+ contextStatus,
265
+ features: Object.freeze([...features].sort()),
266
+ limits: Object.freeze(limits),
267
+ facts: Object.freeze({
268
+ antialias: !unavailable && this.engine.getCreationOptions().antialias === true,
269
+ hardwareScalingLevel: unavailable
270
+ ? 0
271
+ : this.engine.getHardwareScalingLevel(),
272
+ renderHeight: unavailable ? 0 : this.engine.getRenderHeight(),
273
+ renderWidth: unavailable ? 0 : this.engine.getRenderWidth(),
274
+ rendering: this.#rendering,
275
+ webGlVersion,
276
+ ...(this.#lastProbeVerdict === null
277
+ ? {}
278
+ : { lastProbeVerdict: this.#lastProbeVerdict }),
279
+ }),
280
+ capabilities: Object.freeze(this.getCapabilityAvailability().map((entry) => Object.freeze(structuredClone(entry)))),
281
+ ...(quality === null ? {} : { quality: structuredClone(quality) }),
282
+ diagnostics: finalizeSkenoraDiagnostics(diagnostics),
283
+ });
284
+ }
285
+ getGpuProgramCacheSnapshot() {
286
+ return this.#gpuPrograms.snapshot();
287
+ }
288
+ /**
289
+ * Exercises the live backend. A healthy verdict requires both a real scene
290
+ * render and a one-pixel GPU readback; construction alone is never proof.
291
+ */
292
+ async probe() {
293
+ const startedAt = new Date().toISOString();
294
+ const diagnostics = [];
295
+ let renderAttempted = false;
296
+ let renderCompleted = false;
297
+ let readbackAttempted = false;
298
+ let readbackCompleted = false;
299
+ let byteLength;
300
+ if (this.#disposed || this.engine.isDisposed) {
301
+ diagnostics.push(createSkenoraDiagnostic({
302
+ code: "runtime-probe-disposed",
303
+ stage: "render",
304
+ message: "Cannot probe a disposed RuntimeSession",
305
+ recoverable: false,
306
+ repair: "Create a new RuntimeSession and run the probe again.",
307
+ }));
308
+ }
309
+ else if (this.#contextLost) {
310
+ diagnostics.push(createSkenoraDiagnostic({
311
+ code: "runtime-probe-context-lost",
312
+ stage: "render",
313
+ message: "Cannot probe while the graphics context is lost",
314
+ recoverable: true,
315
+ repair: "Wait for context restoration and run the probe again.",
316
+ }));
317
+ }
318
+ else if (!this.scene.activeCamera) {
319
+ diagnostics.push(createSkenoraDiagnostic({
320
+ code: "runtime-probe-no-active-camera",
321
+ severity: "warning",
322
+ stage: "render",
323
+ message: "A real render probe requires an active scene camera",
324
+ recoverable: true,
325
+ repair: "Load a SceneDocument before running the runtime probe.",
326
+ }));
327
+ }
328
+ else {
329
+ try {
330
+ renderAttempted = true;
331
+ this.scene.render();
332
+ renderCompleted = true;
333
+ readbackAttempted = true;
334
+ const pixels = await this.engine.readPixels(0, 0, 1, 1);
335
+ byteLength = pixels.byteLength;
336
+ readbackCompleted = byteLength >= 4;
337
+ if (!readbackCompleted) {
338
+ diagnostics.push(createSkenoraDiagnostic({
339
+ code: "runtime-probe-empty-readback",
340
+ stage: "render",
341
+ message: "GPU readback returned fewer than four bytes",
342
+ recoverable: true,
343
+ repair: "Recreate the runtime and inspect browser GPU diagnostics.",
344
+ details: { byteLength },
345
+ }));
346
+ }
347
+ }
348
+ catch (error) {
349
+ diagnostics.push(createSkenoraDiagnostic({
350
+ code: "runtime-probe-failed",
351
+ stage: "render",
352
+ message: error instanceof Error ? error.message : "Runtime probe failed",
353
+ recoverable: true,
354
+ repair: "Inspect the backend snapshot and browser GPU diagnostics.",
355
+ ...(error instanceof Error ? { causeCode: error.name } : {}),
356
+ }));
357
+ }
358
+ }
359
+ const verdict = renderCompleted && readbackCompleted
360
+ ? "healthy"
361
+ : renderAttempted ||
362
+ readbackAttempted ||
363
+ diagnostics.some((item) => item.severity === "error")
364
+ ? "unhealthy"
365
+ : "unverified";
366
+ this.#lastProbeVerdict = verdict;
367
+ const report = Object.freeze({
368
+ version: 1,
369
+ verdict,
370
+ startedAt,
371
+ finishedAt: new Date().toISOString(),
372
+ backend: this.getBackendSnapshot(),
373
+ evidence: Object.freeze({
374
+ renderAttempted,
375
+ renderCompleted,
376
+ readbackAttempted,
377
+ readbackCompleted,
378
+ ...(byteLength === undefined ? {} : { byteLength }),
379
+ }),
380
+ diagnostics: finalizeSkenoraDiagnostics(diagnostics),
381
+ });
382
+ this.events.publish("runtime.probe.completed", report, {
383
+ source: "runtime",
384
+ });
385
+ return report;
386
+ }
387
+ #qualityDocument(document) {
388
+ const caps = this.engine.getCaps();
389
+ const state = applySceneQualityPolicy(document, this.qualityPolicy, {
390
+ maxTextureSize: Math.max(1, caps.maxTextureSize),
391
+ maxSamples: Math.max(1, caps.maxMSAASamples),
392
+ depthTextures: caps.depthTextureExtension,
393
+ });
394
+ return {
395
+ state,
396
+ document: {
397
+ ...document,
398
+ lights: [...state.effective.lights],
399
+ postProcess: state.effective.postProcess,
400
+ },
401
+ };
402
+ }
403
+ #publishQuality() {
404
+ const state = this.getQualityState();
405
+ if (state)
406
+ this.events.publish("runtime.quality.changed", state, {
407
+ source: "runtime",
408
+ });
409
+ }
410
+ #requestedPixelRatio() {
411
+ if (!this.#adaptRatio)
412
+ return 1;
413
+ const ratio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
414
+ return Math.min(ratio, this.#engineRatioLimit || ratio);
415
+ }
416
+ #resizeForQuality() {
417
+ if (this.qualityPolicy.maxPixelRatio !== undefined) {
418
+ const scale = 1 /
419
+ Math.min(this.#requestedPixelRatio(), this.qualityPolicy.maxPixelRatio);
420
+ if (this.engine.getHardwareScalingLevel() !== scale) {
421
+ this.engine.setHardwareScalingLevel(scale);
422
+ return;
423
+ }
424
+ }
425
+ this.engine.resize();
426
+ }
427
+ #getNode(entityId) {
428
+ return this.#projectionState.getNode(entityId);
429
+ }
430
+ classifyEntityChange(entity, path) {
431
+ return this.#entityProjections.classifyChange(entity.type, path);
432
+ }
433
+ #getMaterial(materialId) {
434
+ const materials = this.#getMaterials(materialId);
435
+ if (materials.length > 1) {
436
+ throw new MaterialProjectionError({
437
+ stage: "binding",
438
+ code: "ambiguous-material-instance",
439
+ materialId,
440
+ message: "This material has multiple independent instances; use a material-wide operation or distinct authored material IDs.",
441
+ });
442
+ }
443
+ return materials[0] ?? null;
444
+ }
445
+ #getMaterials(materialId) {
446
+ const owned = this.#applier?.materials.getOwnedMaterials(materialId);
447
+ if (owned)
448
+ return owned;
449
+ const material = this.#materialResolver.get(materialId);
450
+ return material ? [material] : [];
451
+ }
452
+ describeMaterialSlots(entityId) {
453
+ this.#assertActive();
454
+ return listMaterialSlots(this.scene, entityId, (mesh) => this.#applier
455
+ ? this.#applier.materials.readOriginalMaterial(mesh)
456
+ : mesh.material).map((slot) => ({ ...slot.descriptor }));
457
+ }
458
+ #setMaterialProperty(materialId, property, value) {
459
+ this.#writeMaterialProperty(materialId, property, value);
460
+ this.#materialAnimations.cancelTarget(materialId, property);
461
+ }
462
+ #validateMaterialAnimation(materialId, animation) {
463
+ const material = this.#behaviorDocument?.materials[materialId];
464
+ const instances = this.#applier?.materials.getOwnedMaterials(materialId);
465
+ if (!material?.creation ||
466
+ !instances?.length ||
467
+ ((animation.property === "metallic" ||
468
+ animation.property === "roughness") &&
469
+ instances.some((instance) => instance[animation.property] === null)))
470
+ throw new MaterialAnimationError("material-animation-target", materialId, animation.id);
471
+ if (validateMaterialAnimations({ ...material, animations: [animation] })
472
+ .length)
473
+ throw new MaterialAnimationError("material-animation-invalid", materialId, animation.id);
474
+ }
475
+ #writeMaterialProperty(materialId, property, value) {
476
+ this.#assertActive();
477
+ if (this.#behaviorDocument?.materials[materialId]?.creation &&
478
+ this.#behaviorDocument.materials[materialId]?.effects?.dissolve &&
479
+ ((property === "alpha" && value !== 1) ||
480
+ (property === "transparencyMode" && value !== 0)))
481
+ throw new MaterialProjectionError({
482
+ stage: "prepare",
483
+ code: "material-effect-pass-unsupported",
484
+ materialId,
485
+ message: "Dissolve version 1 requires opaque rendering.",
486
+ });
487
+ if (isMaterialEffectRuntimeProperty(property)) {
488
+ if (!this.#applier)
489
+ throw new Error("Material projection is not active");
490
+ this.#applier.materials.setEffectProperty(materialId, property, value);
491
+ return;
492
+ }
493
+ const materials = this.#getMaterials(materialId);
494
+ if (!materials.length ||
495
+ materials.some((material) => !(material instanceof PBRMaterial))) {
496
+ throw new Error(`PBR material not found: ${materialId}`);
497
+ }
498
+ for (const material of materials)
499
+ setPbrMaterialProperty(material, property, value);
500
+ }
501
+ #activateCameraEntity(entityId, controls = false) {
502
+ this.#assertActive();
503
+ const camera = this.#entityProjections.getNode(entityId);
504
+ if (!(camera instanceof FreeCamera))
505
+ return false;
506
+ this.#camera?.setEnabled(false);
507
+ for (const item of this.#entityProjections.entries("camera")) {
508
+ if (item.node instanceof FreeCamera)
509
+ item.node.detachControl();
510
+ }
511
+ this.scene.activeCamera = camera;
512
+ if (controls)
513
+ camera.attachControl(this.canvas, true);
514
+ this.#applier?.refreshPostProcessCamera();
515
+ this.events.publish("runtime.camera.activated", { entityId, viewport: false }, { source: "runtime" });
516
+ return true;
517
+ }
518
+ #restoreViewportCamera() {
519
+ this.#assertActive();
520
+ for (const item of this.#entityProjections.entries("camera")) {
521
+ if (item.node instanceof FreeCamera)
522
+ item.node.detachControl();
523
+ }
524
+ if (!this.#camera)
525
+ return;
526
+ this.scene.activeCamera = this.#camera.camera;
527
+ this.#camera.setEnabled(true);
528
+ this.#applier?.refreshPostProcessCamera();
529
+ this.events.publish("runtime.camera.activated", { entityId: null, viewport: true }, { source: "runtime" });
530
+ }
531
+ describeModelNodes(entityId) {
532
+ const imported = this.#projectionState.getImported(entityId);
533
+ if (!imported)
534
+ return [];
535
+ const rootSet = new Set(imported.roots);
536
+ const nodeSet = new Set(imported.nodes);
537
+ return imported.nodes.flatMap((node) => {
538
+ if (rootSet.has(node) || !isTransformNode(node))
539
+ return [];
540
+ const parent = node.parent && nodeSet.has(node.parent) ? node.parent : null;
541
+ return [
542
+ {
543
+ nodeId: readSourceNodeId(node),
544
+ name: node.name,
545
+ parentNodeId: parent && !rootSet.has(parent) ? readSourceNodeId(parent) : null,
546
+ type: "getBoundingInfo" in node ? "mesh" : "group",
547
+ transform: {
548
+ position: {
549
+ x: node.position.x,
550
+ y: node.position.y,
551
+ z: node.position.z,
552
+ },
553
+ rotation: {
554
+ x: node.rotation.x,
555
+ y: node.rotation.y,
556
+ z: node.rotation.z,
557
+ },
558
+ scaling: {
559
+ x: node.scaling.x,
560
+ y: node.scaling.y,
561
+ z: node.scaling.z,
562
+ },
563
+ ...(node.rotationQuaternion
564
+ ? {
565
+ rotationQuaternion: {
566
+ x: node.rotationQuaternion.x,
567
+ y: node.rotationQuaternion.y,
568
+ z: node.rotationQuaternion.z,
569
+ w: node.rotationQuaternion.w,
570
+ },
571
+ }
572
+ : {}),
573
+ },
574
+ },
575
+ ];
576
+ });
577
+ }
578
+ describeModelNodeTree(entityId, filter = {}) {
579
+ const imported = this.#projectionState.getImported(entityId);
580
+ return imported ? describeNodeTree(imported.roots, filter) : [];
581
+ }
582
+ getModelStatistics(entityId) {
583
+ const imported = this.#projectionState.getImported(entityId);
584
+ return calculateNodeStatistics(imported?.roots ?? []);
585
+ }
586
+ describeModelMaterials(entityId) {
587
+ const imported = this.#projectionState.getImported(entityId);
588
+ if (!imported)
589
+ return [];
590
+ return imported.container.materials.flatMap((material) => {
591
+ if (!(material instanceof PBRMaterial))
592
+ return [];
593
+ const identity = { id: `${entityId}:${material.id}` };
594
+ const captured = this.#applier
595
+ ? this.#applier.captureMaterial(material, identity)
596
+ : capturePbrMaterial(material, identity);
597
+ return [
598
+ {
599
+ ...captured,
600
+ targetEntityId: entityId,
601
+ targetName: material.name,
602
+ },
603
+ ];
604
+ });
605
+ }
606
+ /** Captures document-owned runtime edits without serializing Babylon internals. */
607
+ captureDocument(source) {
608
+ this.#assertActive();
609
+ const document = structuredClone(source);
610
+ if (this.#camera)
611
+ document.camera = this.#camera.capture();
612
+ if (this.#applier) {
613
+ document.environment = this.#applier.environment.capture(source.environment);
614
+ document.ground = this.#applier.captureGround(source.ground);
615
+ document.fog = this.#applier.captureFog(source.fog);
616
+ document.weather = this.#applier.captureWeather(source.weather);
617
+ document.lights = [...this.#applier.lighting.capture()];
618
+ document.postProcess = this.#applier.capturePostProcess(source.postProcess);
619
+ }
620
+ for (const [entityId, entity] of Object.entries(document.entities)) {
621
+ const node = this.#projectionState.getNode(entityId);
622
+ if (!node)
623
+ continue;
624
+ entity.name = node.name;
625
+ entity.enabled =
626
+ readAuthoredGroupState(node)?.enabled ?? node.isEnabled(false);
627
+ entity.visible = readNodeVisible(node);
628
+ if (!isTransformNode(node))
629
+ continue;
630
+ entity.transform = {
631
+ position: {
632
+ x: node.position.x,
633
+ y: node.position.y,
634
+ z: node.position.z,
635
+ },
636
+ rotation: {
637
+ x: node.rotation.x,
638
+ y: node.rotation.y,
639
+ z: node.rotation.z,
640
+ },
641
+ scaling: {
642
+ x: node.scaling.x,
643
+ y: node.scaling.y,
644
+ z: node.scaling.z,
645
+ },
646
+ ...(node.rotationQuaternion
647
+ ? {
648
+ rotationQuaternion: {
649
+ x: node.rotationQuaternion.x,
650
+ y: node.rotationQuaternion.y,
651
+ z: node.rotationQuaternion.z,
652
+ w: node.rotationQuaternion.w,
653
+ },
654
+ }
655
+ : {}),
656
+ };
657
+ }
658
+ for (const { entityId, node } of this.#entityProjections.entries("camera")) {
659
+ if (!(node instanceof FreeCamera))
660
+ continue;
661
+ const entity = document.entities[entityId];
662
+ if (!entity)
663
+ continue;
664
+ entity.transform = captureCameraEntityTransform(node);
665
+ entity.properties = captureCameraEntityProperties(node);
666
+ }
667
+ if (this.#applier) {
668
+ for (const materialId of Object.keys(document.materials)) {
669
+ const instances = this.#getMaterials(materialId).filter((material) => material instanceof PBRMaterial);
670
+ if (!instances.length)
671
+ continue;
672
+ const captured = instances.map((material) => this.#applier.captureMaterial(material, { id: materialId }));
673
+ if (captured.some((value) => JSON.stringify(value) !== JSON.stringify(captured[0]))) {
674
+ throw new MaterialProjectionError({
675
+ stage: "prepare",
676
+ code: "divergent-material-instances",
677
+ materialId,
678
+ message: "Independent instances differ and cannot be captured as one definition. Use distinct authored material IDs.",
679
+ });
680
+ }
681
+ document.materials[materialId] = {
682
+ ...document.materials[materialId],
683
+ ...captured[0],
684
+ };
685
+ }
686
+ }
687
+ if (this.#qualityState)
688
+ restoreRequestedQuality(document, source, this.#qualityState);
689
+ return document;
690
+ }
691
+ updateEntity(entity) {
692
+ this.#assertActive();
693
+ if (this.#behaviorDocument?.entities[entity.id]) {
694
+ this.#behaviorDocument.entities[entity.id] = structuredClone(entity);
695
+ }
696
+ if (this.#entityProjections.update(entity))
697
+ return;
698
+ const node = this.#projectionState.getNode(entity.id);
699
+ if (!node)
700
+ return;
701
+ node.name = entity.name;
702
+ if (isTransformNode(node))
703
+ applyTransform(node, entity.transform);
704
+ node.setEnabled(entity.enabled);
705
+ setNodeVisible(node, entity.visible);
706
+ }
707
+ async applyDocumentSettings(document, settings) {
708
+ this.#assertActive();
709
+ this.#assertCapabilities(document);
710
+ this.#behaviorDocument = structuredClone(document);
711
+ const quality = this.#qualityDocument(document);
712
+ const effective = quality.document;
713
+ const selected = new Set(settings ?? ALL_DOCUMENT_SETTINGS);
714
+ if (selected.has("camera"))
715
+ this.#camera?.apply(document.camera);
716
+ if (selected.has("cameraPaths")) {
717
+ this.#cameraPaths?.setPaths(document.cameraPaths);
718
+ }
719
+ if (selected.has("materials")) {
720
+ this.#materialAnimations.stopAll();
721
+ this.#textureAnimations.stopAll();
722
+ this.#nodeEffects.clear();
723
+ this.#materialResolver.setBindings(document.materialBindings);
724
+ }
725
+ const applier = this.#applier;
726
+ if (applier) {
727
+ if (settings === undefined) {
728
+ await applier.apply(effective);
729
+ }
730
+ else {
731
+ if (selected.has("environment")) {
732
+ await applier.applyEnvironment(document);
733
+ }
734
+ if (selected.has("ground"))
735
+ await applier.applyGround(document);
736
+ if (selected.has("fog"))
737
+ await applier.applyFog(document);
738
+ if (selected.has("weather"))
739
+ await applier.applyWeather(document);
740
+ if (selected.has("lights"))
741
+ applier.applyLights(effective);
742
+ if (selected.has("postProcess")) {
743
+ await applier.applyPostProcess(effective);
744
+ }
745
+ if (selected.has("materials"))
746
+ await applier.applyMaterials(document);
747
+ }
748
+ }
749
+ if (selected.has("textureAnimations") || selected.has("materials")) {
750
+ this.#textureAnimations.setAnimations(document.textureAnimations);
751
+ if (this.#behaviorsRunning) {
752
+ this.#textureAnimations.startAuto();
753
+ }
754
+ }
755
+ if (selected.has("materials")) {
756
+ this.#materialAnimations.setMaterials(document.materials);
757
+ if (this.#behaviorsRunning)
758
+ this.#materialAnimations.startAuto();
759
+ }
760
+ this.#qualityState = quality.state;
761
+ this.#publishQuality();
762
+ }
763
+ /** Data-only observation of this composition; does not load optional chunks. */
764
+ getCapabilityAvailability() {
765
+ const compatibility = this.#disposed || this.engine.isDisposed
766
+ ? "unsupported"
767
+ : this.#contextLost
768
+ ? "unknown"
769
+ : "supported";
770
+ return Object.entries(SCENE_CAPABILITY_VERSIONS).map(([id, version]) => {
771
+ const effect = Object.keys(MATERIAL_EFFECT_CAPABILITY_IDS).find((kind) => MATERIAL_EFFECT_CAPABILITY_IDS[kind] === id);
772
+ const entityType = id.startsWith("skenora.entity.")
773
+ ? id.slice("skenora.entity.".length)
774
+ : null;
775
+ const installed = entityType && entityType !== "model"
776
+ ? this.#entityProjections.supports(entityType)
777
+ : id === MATERIAL_ANIMATION_CAPABILITY_ID
778
+ ? this.capabilities.has(RUNTIME_MATERIAL_ANIMATIONS_CAPABILITY)
779
+ : true;
780
+ return {
781
+ id,
782
+ version,
783
+ installed,
784
+ enabled: !this.#disposed &&
785
+ ((id !== OWNED_PBR_CAPABILITY_ID &&
786
+ id !== MATERIAL_ANIMATION_CAPABILITY_ID &&
787
+ id !== MATERIAL_PROGRAM_CAPABILITY_ID &&
788
+ id !== MATERIAL_PROGRAM_V2_CAPABILITY_ID &&
789
+ id !== TEXTURE_PROGRAM_CAPABILITY_ID &&
790
+ id !== RENDER_GRAPH_CAPABILITY_ID) ||
791
+ this.materialPolicy.enabled) &&
792
+ (!effect ||
793
+ (this.materialPolicy.enabled &&
794
+ !this.materialPolicy.disabledEffects.includes(effect))),
795
+ compatibility,
796
+ ...(id === OWNED_PBR_CAPABILITY_ID
797
+ ? {
798
+ limits: {
799
+ maxInstances: this.materialPolicy.maxInstances,
800
+ maxTextures: this.materialPolicy.maxTextures,
801
+ maxActiveAnimations: this.materialPolicy.maxActiveAnimations,
802
+ preparationTimeoutMs: this.materialPolicy.preparationTimeoutMs,
803
+ },
804
+ }
805
+ : {}),
806
+ ...(id === MATERIAL_ANIMATION_CAPABILITY_ID
807
+ ? {
808
+ limits: {
809
+ maxActiveAnimations: this.materialPolicy.maxActiveAnimations,
810
+ maxTracksPerMaterial: 64,
811
+ },
812
+ }
813
+ : {}),
814
+ ...(id === PROCEDURAL_GEOMETRY_CAPABILITY_ID
815
+ ? { limits: { ...PROCEDURAL_GEOMETRY_LIMITS } }
816
+ : {}),
817
+ ...(id === MATERIAL_PROGRAM_CAPABILITY_ID ||
818
+ id === MATERIAL_PROGRAM_V2_CAPABILITY_ID
819
+ ? { limits: { ...MATERIAL_PROGRAM_LIMITS } }
820
+ : {}),
821
+ ...(id === TEXTURE_PROGRAM_CAPABILITY_ID
822
+ ? { limits: { ...TEXTURE_PROGRAM_LIMITS } }
823
+ : {}),
824
+ ...(id === RENDER_GRAPH_CAPABILITY_ID
825
+ ? { limits: { ...RENDER_GRAPH_LIMITS } }
826
+ : {}),
827
+ };
828
+ });
829
+ }
830
+ #assertCapabilities(document) {
831
+ const conflict = getMaterialEffectSceneConflicts(document)[0];
832
+ if (conflict)
833
+ throw new MaterialProjectionError({
834
+ stage: "prepare",
835
+ code: conflict.code,
836
+ materialId: conflict.materialId,
837
+ message: conflict.message,
838
+ });
839
+ const automatic = Object.values(document.materials).flatMap((material) => material.creation
840
+ ? (material.animations ?? [])
841
+ .filter((animation) => animation.autoStart)
842
+ .map((animation) => ({
843
+ materialId: material.id,
844
+ animationId: animation.id,
845
+ }))
846
+ : []);
847
+ if (automatic.length > this.materialPolicy.maxActiveAnimations)
848
+ throw new MaterialAnimationError("material-animation-budget-exceeded", automatic[0].materialId, automatic[0].animationId);
849
+ const unavailable = findUnavailableSceneCapabilities(document, this.getCapabilityAvailability());
850
+ if (unavailable[0])
851
+ throw new SceneCapabilityUnavailableError(unavailable[0]);
852
+ }
853
+ async loadDocument(document, resources, options = {}) {
854
+ this.#assertActive();
855
+ const loading = this.#documents.load(document, resources, options);
856
+ const generation = this.#documents.generation;
857
+ await loading;
858
+ if (generation !== this.#documents.generation || this.#disposed)
859
+ return;
860
+ if (this.executionMode === "playback" && this.#behaviorDocument) {
861
+ this.startDocumentBehaviors();
862
+ }
863
+ }
864
+ /** Starts document-authored auto behaviors without enabling editor tools. */
865
+ startDocumentBehaviors() {
866
+ this.#assertActive();
867
+ if (this.#behaviorsRunning)
868
+ return;
869
+ const document = this.#behaviorDocument;
870
+ if (!document) {
871
+ throw new Error("A document must be loaded before starting behaviors");
872
+ }
873
+ this.#behaviorsRunning = true;
874
+ try {
875
+ this.#textureAnimations.startAuto();
876
+ this.#materialAnimations.startAuto();
877
+ for (const entity of Object.values(document.entities)) {
878
+ if (entity.type !== "particle")
879
+ continue;
880
+ const config = (entity.properties ??
881
+ {});
882
+ this.#setParticleRunning(entity.id, entity.enabled && entity.visible && config.autoStart !== false);
883
+ }
884
+ }
885
+ catch (error) {
886
+ this.#behaviorsRunning = false;
887
+ this.#textureAnimations.stopAll();
888
+ this.#materialAnimations.stopAll();
889
+ for (const { entityId } of this.#entityProjections.entries("particle")) {
890
+ this.#setParticleRunning(entityId, false);
891
+ }
892
+ throw error;
893
+ }
894
+ }
895
+ /** Stops document-authored auto behaviors while keeping the scene loaded. */
896
+ stopDocumentBehaviors() {
897
+ this.#assertActive();
898
+ this.#behaviorsRunning = false;
899
+ this.#textureAnimations.stopAll();
900
+ this.#materialAnimations.stopAll();
901
+ for (const { entityId } of this.#entityProjections.entries("particle")) {
902
+ this.#setParticleRunning(entityId, false);
903
+ }
904
+ }
905
+ #frame(entityIds) {
906
+ const ids = entityIds ?? this.#projectionState.rootEntityIds;
907
+ const nodes = ids.flatMap((id) => this.#projectionState.frameNodes(id));
908
+ if (!entityIds && nodes.length === 0) {
909
+ nodes.push(...this.#projectionState.importedRoots());
910
+ }
911
+ this.#camera?.frame(nodes);
912
+ }
913
+ centerImportedModelsAtOrigin() {
914
+ this.#assertActive();
915
+ const roots = [...new Set(this.#projectionState.importedRoots())];
916
+ const bounds = calculateNodeBounds(roots);
917
+ if (!bounds)
918
+ return false;
919
+ const rootSet = new Set(roots);
920
+ const topLevelRoots = roots.filter((root) => !root.parent || !rootSet.has(root.parent));
921
+ for (const root of topLevelRoots) {
922
+ if (!isTransformNode(root))
923
+ continue;
924
+ root.position.subtractInPlace(bounds.center);
925
+ root.computeWorldMatrix(true);
926
+ }
927
+ return true;
928
+ }
929
+ resize() {
930
+ this.#assertActive();
931
+ this.#resizeForQuality();
932
+ for (const { node } of this.#entityProjections.entries("camera")) {
933
+ if (!(node instanceof FreeCamera))
934
+ continue;
935
+ applyCameraEntityProperties(node, captureCameraEntityProperties(node), this.scene);
936
+ }
937
+ this.#publishQuality();
938
+ }
939
+ pause() {
940
+ this.#assertActive();
941
+ if (!this.#rendering)
942
+ return;
943
+ this.#disposeOperation("stopRenderLoop", () => this.engine.stopRenderLoop(this.#renderFrame));
944
+ this.#rendering = false;
945
+ this.#clock.resetInterval();
946
+ }
947
+ resume() {
948
+ this.#assertActive();
949
+ if (this.#rendering)
950
+ return;
951
+ this.#clock.resetInterval();
952
+ this.engine.runRenderLoop(this.#renderFrame);
953
+ this.#rendering = true;
954
+ }
955
+ install(plugin) {
956
+ this.#assertActive();
957
+ return this.#plugins.install(plugin);
958
+ }
959
+ async showGrid(visible) {
960
+ this.#assertActive();
961
+ if (!this.#grid) {
962
+ const { GridHelper } = await import("../editor-tools/grid-helper.js");
963
+ this.#assertActive();
964
+ this.#grid = new GridHelper(this.scene);
965
+ }
966
+ this.#grid.setEnabled(visible);
967
+ }
968
+ #setParticleRunning(entityId, running) {
969
+ const playback = this.#entityProjections.getCapability(entityId, PARTICLE_PLAYBACK_CAPABILITY);
970
+ if (!playback)
971
+ return false;
972
+ playback.setRunning(running);
973
+ return true;
974
+ }
975
+ get backgroundVisible() {
976
+ return this.#applier?.environment.backgroundVisible ?? true;
977
+ }
978
+ setBackgroundVisible(visible) {
979
+ this.#assertActive();
980
+ this.#applier?.environment.setBackgroundVisible(visible);
981
+ }
982
+ dispose() {
983
+ if (this.#disposed)
984
+ return;
985
+ this.#disposed = true;
986
+ this.#documents.dispose();
987
+ this.engine.stopRenderLoop(this.#renderFrame);
988
+ this.#rendering = false;
989
+ try {
990
+ this.#plugins.dispose();
991
+ }
992
+ catch (error) {
993
+ this.#reportRuntimeError(error, "disposePlugins");
994
+ }
995
+ this.capabilities.clear();
996
+ this.#disposeOperation("disconnectResizeObserver", () => this.#resizeObserver?.disconnect());
997
+ this.#disposeOperation("disposeGrid", () => this.#grid?.dispose());
998
+ this.#disposeOperation("resetDocumentProjection", () => this.#resetDocumentProjection());
999
+ this.#disposeOperation("disposeViewport", () => this.viewport.dispose());
1000
+ this.#disposeOperation("disposeTextureAnimations", () => this.#textureAnimations.dispose());
1001
+ this.#disposeOperation("disposeMaterialAnimations", () => this.#materialAnimations.dispose());
1002
+ this.#clock.dispose();
1003
+ this.#disposeOperation("disposeNodeEffects", () => this.#nodeEffects.dispose());
1004
+ this.#disposeOperation("disposeGpuProgramCache", () => this.#gpuPrograms.dispose());
1005
+ this.#disposeOperation("disposeScene", () => this.scene.dispose());
1006
+ this.#disposeOperation("disposeEngine", () => this.engine.dispose());
1007
+ if (this.#ownsEvents) {
1008
+ try {
1009
+ this.events.dispose();
1010
+ }
1011
+ catch {
1012
+ // All owned runtime resources have already been released.
1013
+ }
1014
+ }
1015
+ }
1016
+ async #projectDocument(document, resources, context) {
1017
+ this.#camera = new CameraController(this.scene, this.canvas, document.camera);
1018
+ this.#cameraPaths = new CameraPathController(this.#camera, document.cameraPaths);
1019
+ const applier = new SceneDocumentApplier(this.scene, resources, (binding) => this.#materialResolver.resolve(binding), {
1020
+ policy: this.materialPolicy,
1021
+ onPreparation: (event) => this.events.publish("runtime.material.preparation", event, {
1022
+ source: "runtime",
1023
+ }),
1024
+ gpuProgramCache: this.#gpuPrograms,
1025
+ onGpuProgramPreparation: (event) => this.events.publish("runtime.gpu-program.preparation", event, {
1026
+ source: "runtime",
1027
+ }),
1028
+ });
1029
+ this.#applier = applier;
1030
+ this.#materialResolver.setBindings(document.materialBindings);
1031
+ this.#projectionState.setRootEntityIds(document.rootEntityIds);
1032
+ const modelEntities = Object.values(document.entities).filter((entity) => entity.type === "model" && entity.assetId);
1033
+ const modelResults = await Promise.allSettled(modelEntities.map(async (entity) => {
1034
+ throwIfAborted(context.signal);
1035
+ if (!entity.assetId) {
1036
+ throw new Error(`Model entity has no asset: ${entity.id}`);
1037
+ }
1038
+ const asset = document.assets[entity.assetId];
1039
+ if (!asset) {
1040
+ throw new Error(`Model asset is missing: ${entity.assetId}`);
1041
+ }
1042
+ const imported = await importModelEntity(this.scene, resources, asset, entity, context.signal);
1043
+ return { entity, imported };
1044
+ }));
1045
+ if (!context.isCurrent() || context.signal?.aborted) {
1046
+ disposeImportedResults(modelResults);
1047
+ throwIfAborted(context.signal);
1048
+ return false;
1049
+ }
1050
+ const rejectedModels = modelResults.flatMap((result, index) => result.status === "rejected"
1051
+ ? [{ entity: modelEntities[index], error: result.reason }]
1052
+ : []);
1053
+ const abortError = rejectedModels
1054
+ .map((item) => item.error)
1055
+ .find(isAbortError);
1056
+ const successfulModels = modelResults.length - rejectedModels.length;
1057
+ if (abortError ||
1058
+ (rejectedModels.length > 0 &&
1059
+ (context.modelLoadPolicy === "strict" || successfulModels === 0))) {
1060
+ for (const failure of rejectedModels) {
1061
+ if (!failure.entity)
1062
+ continue;
1063
+ this.events.publish("runtime.model.failed", { entityId: failure.entity.id, error: failure.error }, { source: "runtime" });
1064
+ }
1065
+ try {
1066
+ disposeImportedResults(modelResults);
1067
+ }
1068
+ catch (cleanupError) {
1069
+ throw new AggregateError([...rejectedModels.map((item) => item.error), cleanupError], "Model loading failed and successful imports could not be disposed");
1070
+ }
1071
+ if (abortError)
1072
+ throw abortError;
1073
+ const errors = rejectedModels.map((item) => item.error);
1074
+ throw errors.length === 1
1075
+ ? errors[0]
1076
+ : new AggregateError(errors, "Multiple model entities failed to load");
1077
+ }
1078
+ for (const result of modelResults) {
1079
+ if (result.status !== "fulfilled")
1080
+ continue;
1081
+ this.#projectionState.addImported(result.value.imported);
1082
+ this.#projectionState.setNode(result.value.entity.id, result.value.imported.root);
1083
+ }
1084
+ let loaded = 0;
1085
+ let completed = 0;
1086
+ modelResults.forEach((result, index) => {
1087
+ const entity = modelEntities[index];
1088
+ if (!entity)
1089
+ return;
1090
+ completed += 1;
1091
+ if (result.status === "fulfilled") {
1092
+ const { imported } = result.value;
1093
+ this.#bindSubEntities(document, entity.id, imported);
1094
+ this.events.publish("runtime.model.loaded", { entityId: entity.id, roots: [imported.root] }, { source: "runtime" });
1095
+ loaded += 1;
1096
+ this.events.publish("runtime.loading.progress", {
1097
+ loaded,
1098
+ completed,
1099
+ total: modelEntities.length,
1100
+ entityId: entity.id,
1101
+ status: "loaded",
1102
+ }, { source: "runtime" });
1103
+ return;
1104
+ }
1105
+ this.events.publish("runtime.model.failed", { entityId: entity.id, error: result.reason }, { source: "runtime" });
1106
+ this.events.publish("runtime.loading.progress", {
1107
+ loaded,
1108
+ completed,
1109
+ total: modelEntities.length,
1110
+ entityId: entity.id,
1111
+ status: "failed",
1112
+ }, { source: "runtime" });
1113
+ });
1114
+ const projectionContext = {
1115
+ scene: this.scene,
1116
+ document,
1117
+ resources,
1118
+ ...(context.signal ? { signal: context.signal } : {}),
1119
+ };
1120
+ for (const entity of Object.values(document.entities)) {
1121
+ if (!this.#entityProjections.supports(entity.type))
1122
+ continue;
1123
+ if (entity.type === "group" && entity.sourceNode !== undefined)
1124
+ continue;
1125
+ throwIfAborted(context.signal);
1126
+ if (!context.isCurrent())
1127
+ return false;
1128
+ const projection = await this.#entityProjections.create(projectionContext, entity);
1129
+ if (!context.isCurrent() || context.signal?.aborted) {
1130
+ this.#entityProjections.dispose(entity.id);
1131
+ throwIfAborted(context.signal);
1132
+ return false;
1133
+ }
1134
+ if (projection) {
1135
+ this.#projectionState.setNode(entity.id, projection.node);
1136
+ }
1137
+ }
1138
+ for (const entity of Object.values(document.entities)) {
1139
+ if (!entity.parentId)
1140
+ continue;
1141
+ const parentEntity = document.entities[entity.parentId];
1142
+ if (entity.sourceNode && parentEntity?.type === "model") {
1143
+ // Imported source roots remain below their asset-alignment root.
1144
+ continue;
1145
+ }
1146
+ const node = this.#projectionState.getNode(entity.id);
1147
+ const parent = this.#projectionState.getNode(entity.parentId);
1148
+ if (node && parent)
1149
+ node.parent = parent;
1150
+ }
1151
+ if (!context.isCurrent())
1152
+ return false;
1153
+ const quality = this.#qualityDocument(document);
1154
+ await applier.apply(quality.document, context.signal);
1155
+ if (!context.isCurrent() || context.signal?.aborted) {
1156
+ applier.dispose();
1157
+ throwIfAborted(context.signal);
1158
+ return false;
1159
+ }
1160
+ this.#textureAnimations.setAnimations(document.textureAnimations);
1161
+ this.#materialAnimations.setMaterials(document.materials);
1162
+ this.#behaviorDocument = structuredClone(document);
1163
+ this.#qualityState = quality.state;
1164
+ this.#publishQuality();
1165
+ return true;
1166
+ }
1167
+ #clearProjectionState() {
1168
+ const errors = [];
1169
+ attempt(errors, () => this.#nodeEffects.clear());
1170
+ attempt(errors, () => this.#entityProjections.clear());
1171
+ attempt(errors, () => this.#materialResolver.clear());
1172
+ attempt(errors, () => this.#projectionState.clear());
1173
+ if (errors.length > 0) {
1174
+ throw new AggregateError(errors, "Runtime projection state could not be cleared");
1175
+ }
1176
+ }
1177
+ #resetDocumentProjection() {
1178
+ const errors = [];
1179
+ this.#behaviorsRunning = false;
1180
+ this.#behaviorDocument = null;
1181
+ this.#qualityState = null;
1182
+ attempt(errors, () => this.#textureAnimations.setAnimations({}));
1183
+ attempt(errors, () => this.#materialAnimations.setMaterials({}));
1184
+ this.#clock.resetInterval();
1185
+ attempt(errors, () => this.#applier?.dispose());
1186
+ this.#applier = null;
1187
+ attempt(errors, () => this.#cameraPaths?.dispose());
1188
+ this.#cameraPaths = null;
1189
+ attempt(errors, () => this.#camera?.dispose());
1190
+ this.#camera = null;
1191
+ attempt(errors, () => this.#clearProjectionState());
1192
+ if (errors.length > 0) {
1193
+ throw new AggregateError(errors, "Runtime document projection could not be reset");
1194
+ }
1195
+ }
1196
+ #bindSubEntities(document, modelEntityId, imported) {
1197
+ for (const entity of Object.values(document.entities)) {
1198
+ if (!entity.sourceNode ||
1199
+ !isDescendantOf(document, entity.id, modelEntityId)) {
1200
+ continue;
1201
+ }
1202
+ const node = imported.nodes.find((candidate) => readSourceNodeId(candidate) === entity.sourceNode ||
1203
+ candidate.name === entity.sourceNode);
1204
+ if (!node)
1205
+ continue;
1206
+ const metadata = node.metadata;
1207
+ const skenora = metadata?.skenora;
1208
+ node.metadata = {
1209
+ ...metadata,
1210
+ skenora: {
1211
+ ...skenora,
1212
+ entityId: entity.id,
1213
+ assetId: document.entities[modelEntityId]?.assetId,
1214
+ },
1215
+ };
1216
+ this.#projectionState.setNode(entity.id, node);
1217
+ this.updateEntity(entity);
1218
+ }
1219
+ }
1220
+ #assertActive() {
1221
+ if (this.#disposed)
1222
+ throw new Error("RuntimeSession has been disposed");
1223
+ }
1224
+ #disposeOperation(operation, action) {
1225
+ try {
1226
+ action();
1227
+ }
1228
+ catch (error) {
1229
+ this.#reportRuntimeError(error, operation);
1230
+ }
1231
+ }
1232
+ #reportRuntimeError(error, operation) {
1233
+ try {
1234
+ this.events.publish("runtime.error", { error, operation }, { source: "runtime" });
1235
+ }
1236
+ catch {
1237
+ // Disposal continues even when an externally owned event hub is gone.
1238
+ }
1239
+ }
1240
+ }
1241
+ function throwIfAborted(signal) {
1242
+ if (!signal?.aborted)
1243
+ return;
1244
+ throw new DOMException("The runtime load was aborted", "AbortError");
1245
+ }
1246
+ function readFiniteNumber(value) {
1247
+ return typeof value === "number" && Number.isFinite(value)
1248
+ ? value
1249
+ : undefined;
1250
+ }
1251
+ function isAbortError(error) {
1252
+ return ((error instanceof DOMException && error.name === "AbortError") ||
1253
+ (!!error &&
1254
+ typeof error === "object" &&
1255
+ "name" in error &&
1256
+ error.name === "AbortError"));
1257
+ }
1258
+ function disposeImportedResults(results) {
1259
+ const errors = [];
1260
+ for (const result of results) {
1261
+ if (result.status !== "fulfilled")
1262
+ continue;
1263
+ try {
1264
+ result.value.imported.container.dispose();
1265
+ }
1266
+ catch (error) {
1267
+ errors.push(error);
1268
+ }
1269
+ }
1270
+ if (errors.length > 0) {
1271
+ throw new AggregateError(errors, "Cancelled model imports could not be disposed");
1272
+ }
1273
+ }
1274
+ function isDescendantOf(document, entityId, ancestorId) {
1275
+ let current = document.entities[entityId];
1276
+ const visited = new Set();
1277
+ while (current?.parentId) {
1278
+ if (current.parentId === ancestorId)
1279
+ return true;
1280
+ if (visited.has(current.parentId))
1281
+ return false;
1282
+ visited.add(current.parentId);
1283
+ current = document.entities[current.parentId];
1284
+ }
1285
+ return false;
1286
+ }
1287
+ function readSourceNodeId(node) {
1288
+ const metadata = node.metadata;
1289
+ return typeof metadata?.skenora?.sourceNodeId === "string"
1290
+ ? metadata.skenora.sourceNodeId
1291
+ : node.id;
1292
+ }
1293
+ function capturePbrMaterial(material, identity) {
1294
+ return {
1295
+ ...identity,
1296
+ workflow: material.metallic === null && material.roughness === null
1297
+ ? "specular-glossiness"
1298
+ : "metallic-roughness",
1299
+ baseColor: {
1300
+ r: material.albedoColor.r,
1301
+ g: material.albedoColor.g,
1302
+ b: material.albedoColor.b,
1303
+ },
1304
+ reflectivityColor: {
1305
+ r: material.reflectivityColor.r,
1306
+ g: material.reflectivityColor.g,
1307
+ b: material.reflectivityColor.b,
1308
+ },
1309
+ metallic: material.metallic,
1310
+ roughness: material.roughness,
1311
+ microSurface: material.microSurface,
1312
+ metallicF0Factor: material.metallicF0Factor,
1313
+ alpha: material.alpha,
1314
+ emissiveColor: {
1315
+ r: material.emissiveColor.r,
1316
+ g: material.emissiveColor.g,
1317
+ b: material.emissiveColor.b,
1318
+ },
1319
+ emissiveIntensity: material.emissiveIntensity,
1320
+ doubleSided: !material.backFaceCulling,
1321
+ };
1322
+ }
1323
+ function readNodeVisible(node) {
1324
+ const metadata = node.metadata;
1325
+ if (typeof metadata?.skenora?.entityVisible === "boolean") {
1326
+ return metadata.skenora.entityVisible;
1327
+ }
1328
+ const meshes = node.getChildMeshes(false);
1329
+ if ("visibility" in node) {
1330
+ const visibility = node.visibility;
1331
+ if (visibility > 0)
1332
+ return true;
1333
+ }
1334
+ return meshes.length === 0 || meshes.some((mesh) => mesh.visibility > 0);
1335
+ }
1336
+ function attempt(errors, action) {
1337
+ try {
1338
+ action();
1339
+ }
1340
+ catch (error) {
1341
+ errors.push(error);
1342
+ }
1343
+ }
1344
+ //# sourceMappingURL=runtime-session.js.map