@fps-games/editor 0.1.4-beta.5 → 0.1.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/build-info.json +16 -16
  2. package/dist/local-editor-harness.d.ts +24 -5
  3. package/dist/local-editor-harness.d.ts.map +1 -1
  4. package/dist/local-editor-harness.js +456 -90
  5. package/dist/local-editor-harness.js.map +1 -1
  6. package/dist/local-editor-performance-stats.d.ts +12 -0
  7. package/dist/local-editor-performance-stats.d.ts.map +1 -0
  8. package/dist/local-editor-performance-stats.js +46 -0
  9. package/dist/local-editor-performance-stats.js.map +1 -0
  10. package/dist/playable-babylon-facade.d.ts +2 -2
  11. package/dist/playable-babylon-facade.d.ts.map +1 -1
  12. package/dist/playable-babylon-facade.js +1 -1
  13. package/dist/playable-babylon-facade.js.map +1 -1
  14. package/dist/playable-babylon-rendering.d.ts +57 -12
  15. package/dist/playable-babylon-rendering.d.ts.map +1 -1
  16. package/dist/playable-babylon-rendering.js +529 -20
  17. package/dist/playable-babylon-rendering.js.map +1 -1
  18. package/dist/playable-local-editor-host.d.ts +10 -9
  19. package/dist/playable-local-editor-host.d.ts.map +1 -1
  20. package/dist/playable-local-editor-host.js.map +1 -1
  21. package/dist/playable-sdk.d.ts +5 -3
  22. package/dist/playable-sdk.d.ts.map +1 -1
  23. package/dist/playable-sdk.js +3 -2
  24. package/dist/playable-sdk.js.map +1 -1
  25. package/dist/static-shadow-cpu-worker.d.ts +7 -0
  26. package/dist/static-shadow-cpu-worker.d.ts.map +1 -0
  27. package/dist/static-shadow-cpu-worker.js +221 -0
  28. package/dist/static-shadow-cpu-worker.js.map +1 -0
  29. package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts +3 -1
  30. package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts.map +1 -1
  31. package/node_modules/@fps-games/babylon-renderer/dist/index.js +3 -1
  32. package/node_modules/@fps-games/babylon-renderer/dist/index.js.map +1 -1
  33. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.d.ts +4 -0
  34. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.d.ts.map +1 -0
  35. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.js +584 -0
  36. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.js.map +1 -0
  37. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-atlas.d.ts +61 -0
  38. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-atlas.d.ts.map +1 -0
  39. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-atlas.js +382 -0
  40. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-atlas.js.map +1 -0
  41. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-bake.d.ts +47 -0
  42. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-bake.d.ts.map +1 -0
  43. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-bake.js +285 -0
  44. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-bake.js.map +1 -0
  45. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-baker.d.ts +88 -0
  46. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-baker.d.ts.map +1 -0
  47. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-baker.js +813 -0
  48. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-baker.js.map +1 -0
  49. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-rendering.d.ts +27 -0
  50. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-rendering.d.ts.map +1 -0
  51. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-rendering.js +220 -0
  52. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-rendering.js.map +1 -0
  53. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.d.ts +35 -3
  54. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.d.ts.map +1 -1
  55. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.js +65 -609
  56. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.js.map +1 -1
  57. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts +169 -14
  58. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts.map +1 -1
  59. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js +9 -3
  60. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js.map +1 -1
  61. package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
  62. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +2 -0
  63. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  64. package/node_modules/@fps-games/editor-babylon/dist/index.js +2 -0
  65. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  66. package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.d.ts +34 -0
  67. package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.d.ts.map +1 -0
  68. package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.js +272 -0
  69. package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.js.map +1 -0
  70. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +6 -0
  71. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  72. package/node_modules/@fps-games/editor-babylon/dist/projection.js +65 -1
  73. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  74. package/node_modules/@fps-games/editor-babylon/dist/rendering.d.ts +19 -0
  75. package/node_modules/@fps-games/editor-babylon/dist/rendering.d.ts.map +1 -0
  76. package/node_modules/@fps-games/editor-babylon/dist/rendering.js +66 -0
  77. package/node_modules/@fps-games/editor-babylon/dist/rendering.js.map +1 -0
  78. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts +5 -2
  79. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -1
  80. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +65 -32
  81. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -1
  82. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts.map +1 -1
  83. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js +19 -2
  84. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js.map +1 -1
  85. package/node_modules/@fps-games/editor-babylon/package.json +5 -5
  86. package/node_modules/@fps-games/editor-browser/dist/index.d.ts +1 -1
  87. package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
  88. package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
  89. package/node_modules/@fps-games/editor-browser/dist/local-editor-rendering-actions.d.ts +8 -0
  90. package/node_modules/@fps-games/editor-browser/dist/local-editor-rendering-actions.d.ts.map +1 -0
  91. package/node_modules/@fps-games/editor-browser/dist/local-editor-rendering-actions.js +14 -0
  92. package/node_modules/@fps-games/editor-browser/dist/local-editor-rendering-actions.js.map +1 -0
  93. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -1
  94. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js +10 -2
  95. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -1
  96. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
  97. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
  98. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +17 -0
  99. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
  100. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  101. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +466 -42
  102. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  103. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +70 -5
  104. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  105. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +1 -1
  106. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  107. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +595 -5
  108. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  109. package/node_modules/@fps-games/editor-browser/package.json +3 -3
  110. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts +14 -1
  111. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts.map +1 -1
  112. package/node_modules/@fps-games/editor-core/dist/editor-session.js +34 -10
  113. package/node_modules/@fps-games/editor-core/dist/editor-session.js.map +1 -1
  114. package/node_modules/@fps-games/editor-core/dist/inspector.d.ts +2 -1
  115. package/node_modules/@fps-games/editor-core/dist/inspector.d.ts.map +1 -1
  116. package/node_modules/@fps-games/editor-core/dist/inspector.js +11 -1
  117. package/node_modules/@fps-games/editor-core/dist/inspector.js.map +1 -1
  118. package/node_modules/@fps-games/editor-core/dist/serialized-object.d.ts +7 -0
  119. package/node_modules/@fps-games/editor-core/dist/serialized-object.d.ts.map +1 -1
  120. package/node_modules/@fps-games/editor-core/dist/serialized-object.js +1 -0
  121. package/node_modules/@fps-games/editor-core/dist/serialized-object.js.map +1 -1
  122. package/node_modules/@fps-games/editor-core/package.json +2 -2
  123. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  124. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts.map +1 -1
  125. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js +10 -5
  126. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js.map +1 -1
  127. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/placement.d.ts.map +1 -1
  128. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/placement.js +3 -1
  129. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/placement.js.map +1 -1
  130. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts +4 -1
  131. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts.map +1 -1
  132. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js +29 -0
  133. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js.map +1 -1
  134. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +10 -0
  135. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
  136. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
  137. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +3 -0
  138. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
  139. package/node_modules/@fps-games/editor-playable-sdk/dist/index.d.ts +1 -0
  140. package/node_modules/@fps-games/editor-playable-sdk/dist/index.d.ts.map +1 -1
  141. package/node_modules/@fps-games/editor-playable-sdk/dist/index.js +1 -0
  142. package/node_modules/@fps-games/editor-playable-sdk/dist/index.js.map +1 -1
  143. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.d.ts +33 -2
  144. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.d.ts.map +1 -1
  145. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js +232 -16
  146. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js.map +1 -1
  147. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.d.ts +1 -1
  148. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.d.ts.map +1 -1
  149. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.js +1 -1
  150. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.js.map +1 -1
  151. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts +12 -1
  152. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
  153. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +153 -0
  154. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
  155. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/index.d.ts +4 -2
  156. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/index.d.ts.map +1 -1
  157. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/index.js +2 -1
  158. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/index.js.map +1 -1
  159. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/reducer.d.ts.map +1 -1
  160. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/reducer.js +4 -1
  161. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/reducer.js.map +1 -1
  162. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/rendering-alpha-index.d.ts +19 -0
  163. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/rendering-alpha-index.d.ts.map +1 -0
  164. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/rendering-alpha-index.js +57 -0
  165. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/rendering-alpha-index.js.map +1 -0
  166. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/types.d.ts +7 -1
  167. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/types.d.ts.map +1 -1
  168. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.d.ts.map +1 -1
  169. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.js +620 -36
  170. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.js.map +1 -1
  171. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.d.ts +8 -3
  172. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.d.ts.map +1 -1
  173. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.js +258 -10
  174. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.js.map +1 -1
  175. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/types.d.ts +110 -3
  176. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/types.d.ts.map +1 -1
  177. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
  178. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +25 -0
  179. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
  180. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +2 -1
  181. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
  182. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/client.d.ts.map +1 -1
  183. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/client.js +4 -0
  184. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/client.js.map +1 -1
  185. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/companion-configs.d.ts +9 -1
  186. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/companion-configs.d.ts.map +1 -1
  187. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/companion-configs.js +12 -1
  188. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/companion-configs.js.map +1 -1
  189. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/types.d.ts +2 -0
  190. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/types.d.ts.map +1 -1
  191. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts +1 -0
  192. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts.map +1 -1
  193. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js +75 -0
  194. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js.map +1 -1
  195. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/index.d.ts +1 -0
  196. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/index.d.ts.map +1 -1
  197. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/index.js +1 -0
  198. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/index.js.map +1 -1
  199. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts +17 -0
  200. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts.map +1 -0
  201. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +262 -0
  202. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js.map +1 -0
  203. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.d.ts.map +1 -1
  204. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js +7 -3
  205. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js.map +1 -1
  206. package/node_modules/@fps-games/editor-playable-sdk/dist/session/identity.d.ts +6 -0
  207. package/node_modules/@fps-games/editor-playable-sdk/dist/session/identity.d.ts.map +1 -1
  208. package/node_modules/@fps-games/editor-playable-sdk/dist/session/identity.js +31 -0
  209. package/node_modules/@fps-games/editor-playable-sdk/dist/session/identity.js.map +1 -1
  210. package/node_modules/@fps-games/editor-playable-sdk/dist/session/index.d.ts +2 -1
  211. package/node_modules/@fps-games/editor-playable-sdk/dist/session/index.d.ts.map +1 -1
  212. package/node_modules/@fps-games/editor-playable-sdk/dist/session/index.js +1 -1
  213. package/node_modules/@fps-games/editor-playable-sdk/dist/session/index.js.map +1 -1
  214. package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.d.ts.map +1 -1
  215. package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.js +21 -9
  216. package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.js.map +1 -1
  217. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.d.ts +151 -0
  218. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.d.ts.map +1 -0
  219. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.js +445 -0
  220. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.js.map +1 -0
  221. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/index.d.ts +2 -0
  222. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/index.d.ts.map +1 -0
  223. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/index.js +2 -0
  224. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/index.js.map +1 -0
  225. package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
  226. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  227. package/package.json +8 -8
@@ -1,14 +1,18 @@
1
1
  import { createEditorSession, createInspectorRegistry, DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_VIEWPORT_TOOL_STATE, createEmptyEditorViewportSpatialOverlayState, cloneEditorViewportToolState, cloneEditorViewportSpatialOverlayState, aggregateInspectorObjectsToMultiObject, createInspectorEditPayload, compareInspectorSections, computeEditorTransformActionTargets, mergeInspectorSections, normalizeEditorTransformConstraint, resolveEditorSelectionCommand, serializedMultiObjectToInspectorObject, serializedObjectToInspectorObject, validateSceneGraphDelete, validateSceneGraphDrop, validateSceneGraphGroupSelection, validateSceneGraphMove, validateSceneGraphRename, } from '@fps-games/editor-core';
2
2
  import { createLocalEditorBrowserUi, toTransformOperationPolicyStatusLabel, } from '@fps-games/editor-browser';
3
3
  import { createBabylonEnvironmentTextureController, createBabylonDefaultPostProcessPipelineController, } from '@fps-games/babylon-renderer';
4
- import { createBabylonEditorProjection, createBabylonEditorWorld, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonEditorShadowPreviewController, createBabylonTransformGizmoController, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
4
+ import { createBabylonEditorProjection, createBabylonEditorWorld, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonEditorShadowPreviewController, createBabylonTransformGizmoController, createBabylonEditorPerformanceMonitor, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
5
5
  import { createLocalEditorSceneRenderScheduler, } from './local-editor-scene-render-scheduler.js';
6
6
  import { createPlayableBabylonOutlineInspectorSection, createPlayableBabylonOutlineInspectorSeedPatchValue, isPlayableBabylonOutlineInspectorPatchPath, } from './playable-babylon-outline-inspector.js';
7
7
  import { createLocalEditorViewportRenderCoordinator, } from './local-editor-viewport-render-coordinator.js';
8
8
  import { createLocalEditorSceneViewInteractionRuntime, } from './local-editor-scene-view-interaction-runtime.js';
9
+ import { cloneLocalEditorPerformanceStats, countLocalEditorAuthoredObjects, createLocalEditorPerformanceStats, LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON, } from './local-editor-performance-stats.js';
9
10
  const BATCH_EDIT_REQUIRES_MULTI_PATCH_REASON = 'Batch edit requires multi-property patch support.';
11
+ const LARGE_SELECTION_THRESHOLD = 200;
12
+ const LARGE_SHADOW_MODE_BATCH_THRESHOLD = 200;
10
13
  const EDITOR_PROJECTION_IDLE_BEFORE_RUN_GAME_TIMEOUT_MS = 1500;
11
14
  const EDITOR_WORLD_DISPOSE_BOUNDARY_DELAY_MS = 500;
15
+ const RENDERING_ACTION_PROGRESS_MIN_VISIBLE_MS = 1000;
12
16
  export function createLocalEditorHarness(options) {
13
17
  const root = options.root ?? document.body;
14
18
  const state = {
@@ -31,6 +35,7 @@ export function createLocalEditorHarness(options) {
31
35
  environmentTexture: null,
32
36
  postProcess: null,
33
37
  projectionShadowPreviewRebuildFrame: null,
38
+ projectionShadowPreviewRefreshFrame: null,
34
39
  gizmo: null,
35
40
  sceneCameraPreview: null,
36
41
  sceneCameraPreviewEnabled: false,
@@ -43,6 +48,10 @@ export function createLocalEditorHarness(options) {
43
48
  sceneRenderScheduler: null,
44
49
  viewportRenderCoordinator: null,
45
50
  sceneFrameStats: null,
51
+ performanceMonitor: null,
52
+ performanceMonitorOpen: false,
53
+ performanceObjectCount: null,
54
+ performanceStats: null,
46
55
  worldAppearanceKey: '',
47
56
  worldRenderingKey: '',
48
57
  boxSelection: null,
@@ -61,6 +70,7 @@ export function createLocalEditorHarness(options) {
61
70
  statusTone: 'default',
62
71
  statusToneStatus: 'Game running',
63
72
  statusDetails: '',
73
+ renderingAction: null,
64
74
  summary: '',
65
75
  };
66
76
  let harness;
@@ -180,10 +190,11 @@ export function createLocalEditorHarness(options) {
180
190
  : undefined,
181
191
  onRenderingAction: options.documentAdapter.onRenderingAction
182
192
  ? (input) => {
183
- void applyRenderingAction(state, options, input)
193
+ return applyRenderingAction(state, options, input, harness.render)
184
194
  .then((changed) => {
185
195
  if (changed)
186
196
  harness.render();
197
+ return changed;
187
198
  });
188
199
  }
189
200
  : undefined,
@@ -284,6 +295,10 @@ export function createLocalEditorHarness(options) {
284
295
  if (setGridVisible(state, visible))
285
296
  harness.render();
286
297
  },
298
+ onPerformanceMonitorOpenChange: (open) => {
299
+ setLocalEditorPerformanceMonitorOpen(state, open);
300
+ updateLocalEditorPerformanceStats(state, stats => ui.updatePerformanceStats?.(stats));
301
+ },
287
302
  onFocusSelection: () => {
288
303
  if (focusSelectedProjection(state))
289
304
  harness.render();
@@ -408,7 +423,7 @@ export function createLocalEditorHarness(options) {
408
423
  ui.updatePreview(previewState);
409
424
  else
410
425
  ui.update(createUiState(state, options));
411
- }, stats => ui.updateSceneFrameStats?.(stats));
426
+ }, stats => ui.updateSceneFrameStats?.(stats), stats => ui.updatePerformanceStats?.(stats));
412
427
  state.mode = 'editor';
413
428
  state.summary = loadedSource?.summary ?? summarizeDocument(options, preparedDocument, source);
414
429
  state.status = `GameWorld disposed; EditorWorld active; assets=${assets.length}`;
@@ -788,7 +803,7 @@ function validateTransformActionSelection(state, action) {
788
803
  ? { ok: true }
789
804
  : { ok: false, message: 'Distribute needs at least 3 selected objects' };
790
805
  }
791
- async function createEditorWorld(state, options, renderUi, renderPreviewUi, updateSceneFrameStats) {
806
+ async function createEditorWorld(state, options, renderUi, renderPreviewUi, updateSceneFrameStats, updatePerformanceStats) {
792
807
  disposeEditorWorld(state);
793
808
  const render = (reason = 'editor-world-update') => {
794
809
  renderUi();
@@ -818,6 +833,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
818
833
  useRightHandedSystem: options.world?.useRightHandedSystem,
819
834
  enableDefaultCameraControls: false,
820
835
  });
836
+ const performanceMonitor = createBabylonEditorPerformanceMonitor(world.scene);
821
837
  const sceneRenderScheduler = createLocalEditorSceneRenderScheduler((frame) => {
822
838
  state.sceneViewInteraction?.updateFrame(frame);
823
839
  world.render();
@@ -825,6 +841,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
825
841
  onStatsChange(stats) {
826
842
  state.sceneFrameStats = stats;
827
843
  updateSceneFrameStats(stats);
844
+ updateLocalEditorPerformanceStats(state, updatePerformanceStats);
828
845
  },
829
846
  });
830
847
  const viewportRenderCoordinator = createLocalEditorViewportRenderCoordinator({
@@ -1080,6 +1097,9 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1080
1097
  scene: world.scene,
1081
1098
  projection,
1082
1099
  options: initialRendering.shadowPreview,
1100
+ onStaticProjectedShadowTextureReady(event) {
1101
+ viewportRenderCoordinator.invalidateScene(`static-shadow-texture-ready-${event.chunkId}`);
1102
+ },
1083
1103
  });
1084
1104
  const environmentTexture = createBabylonEnvironmentTextureController(world.scene, initialRendering.environment ?? null);
1085
1105
  const postProcess = createBabylonDefaultPostProcessPipelineController(world.scene, [world.camera], initialRendering.postProcess ?? null, { name: 'editorDefaultPostProcessPipeline' });
@@ -1106,11 +1126,14 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1106
1126
  state.sceneRenderScheduler = sceneRenderScheduler;
1107
1127
  state.viewportRenderCoordinator = viewportRenderCoordinator;
1108
1128
  state.sceneFrameStats = sceneRenderScheduler.getStats();
1129
+ state.performanceMonitor = performanceMonitor;
1130
+ updateLocalEditorPerformanceStats(state, updatePerformanceStats);
1109
1131
  state.worldAppearanceKey = initialAppearanceKey;
1110
1132
  state.worldRenderingKey = initialRenderingKey;
1111
1133
  state.resizeHandler = resize;
1112
1134
  if (document) {
1113
1135
  projection.projectNodes(options.documentAdapter.getProjectionNodes(document));
1136
+ scheduleProjectionShadowPreviewRebuild(state, 'editor-world-initial-projection-shadow');
1114
1137
  }
1115
1138
  syncCurrentSelectionToSceneArtifacts(state, 'editor-world-initial-selection');
1116
1139
  viewportRenderCoordinator.requestFrame('editor-world-created');
@@ -1136,6 +1159,14 @@ function disposeEditorWorld(state) {
1136
1159
  state.sceneViewInput = null;
1137
1160
  state.selectionController?.dispose();
1138
1161
  state.selectionController = null;
1162
+ if (state.performanceMonitorOpen) {
1163
+ state.viewportRenderCoordinator?.endContinuous(LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON);
1164
+ }
1165
+ state.performanceMonitorOpen = false;
1166
+ state.performanceMonitor?.dispose();
1167
+ state.performanceMonitor = null;
1168
+ state.performanceStats = null;
1169
+ state.performanceObjectCount = null;
1139
1170
  state.viewportRenderCoordinator?.dispose();
1140
1171
  state.viewportRenderCoordinator = null;
1141
1172
  state.sceneRenderScheduler?.dispose();
@@ -1154,6 +1185,10 @@ function disposeEditorWorld(state) {
1154
1185
  window.cancelAnimationFrame(state.projectionShadowPreviewRebuildFrame);
1155
1186
  state.projectionShadowPreviewRebuildFrame = null;
1156
1187
  }
1188
+ if (state.projectionShadowPreviewRefreshFrame !== null) {
1189
+ window.cancelAnimationFrame(state.projectionShadowPreviewRefreshFrame);
1190
+ state.projectionShadowPreviewRefreshFrame = null;
1191
+ }
1157
1192
  state.shadowPreview?.dispose();
1158
1193
  state.shadowPreview = null;
1159
1194
  state.environmentTexture?.dispose();
@@ -1265,6 +1300,21 @@ function scheduleProjectionShadowPreviewRebuild(state, reason) {
1265
1300
  state.viewportRenderCoordinator?.invalidateScene(reason);
1266
1301
  });
1267
1302
  }
1303
+ function scheduleProjectionShadowPreviewRefresh(state, reason) {
1304
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
1305
+ state.shadowPreview?.refresh();
1306
+ state.viewportRenderCoordinator?.invalidateScene(reason);
1307
+ return;
1308
+ }
1309
+ if (state.projectionShadowPreviewRefreshFrame !== null) {
1310
+ window.cancelAnimationFrame(state.projectionShadowPreviewRefreshFrame);
1311
+ }
1312
+ state.projectionShadowPreviewRefreshFrame = window.requestAnimationFrame(() => {
1313
+ state.projectionShadowPreviewRefreshFrame = null;
1314
+ state.shadowPreview?.refresh();
1315
+ state.viewportRenderCoordinator?.invalidateScene(reason);
1316
+ });
1317
+ }
1268
1318
  async function runExclusive(state, render, action) {
1269
1319
  if (state.busy)
1270
1320
  return;
@@ -1290,7 +1340,7 @@ function selectItem(state, options, input, renderUi) {
1290
1340
  return false;
1291
1341
  if (!isDocumentNodeSelectable(state, options, input.id))
1292
1342
  return false;
1293
- const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
1343
+ const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
1294
1344
  const command = resolveEditorSelectionCommand({
1295
1345
  selection,
1296
1346
  targetIds: [input.id],
@@ -1390,7 +1440,7 @@ function handleContextAction(state, options, action) {
1390
1440
  return runHierarchyCustomContextAction(state, options, action);
1391
1441
  if (action.action === 'focus') {
1392
1442
  const activeId = action.activeId ?? action.targetIds[action.targetIds.length - 1] ?? null;
1393
- const selectionChanged = activeId && !state.session?.getState().selection.selectedIds.includes(activeId)
1443
+ const selectionChanged = activeId && !state.session?.getState({ cloneDocuments: false }).selection.selectedIds.includes(activeId)
1394
1444
  ? dispatchSelectionCommand(state, options, {
1395
1445
  type: 'selection.replace',
1396
1446
  selectedIds: [activeId],
@@ -1633,15 +1683,13 @@ function duplicateSceneGraphNodes(state, options, intent) {
1633
1683
  state.status = 'Duplicate rejected';
1634
1684
  return true;
1635
1685
  }
1686
+ const projectionImpact = resolveProjectionImpact(patch, createDuplicateProjectionImpact(patch));
1636
1687
  const result = state.session.dispatch({
1637
1688
  type: 'document.patch',
1638
1689
  label: patch.label ?? `Duplicate ${targetIds.length} object(s)`,
1639
1690
  patch: patch.patch,
1640
1691
  targetId: patch.activeId ?? patch.createdIds[patch.createdIds.length - 1] ?? undefined,
1641
- ...withProjectionImpact(resolveProjectionImpact(patch, createCreatedReprojectProjectionImpact({
1642
- createdNodeIds: patch.createdIds,
1643
- reprojectIds: patch.reprojectIds,
1644
- }))),
1692
+ ...withProjectionImpact(projectionImpact),
1645
1693
  });
1646
1694
  if (!result.documentChanged) {
1647
1695
  state.status = 'Duplicate unchanged';
@@ -1659,11 +1707,14 @@ function duplicateSceneGraphNodes(state, options, intent) {
1659
1707
  label: 'Select Duplicated Nodes',
1660
1708
  }).selection
1661
1709
  : sanitizeSelection(state, options, result.workingDocument, result.selection) ?? result.selection;
1662
- rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
1663
- if (patch.reprojectIds?.length)
1664
- reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
1665
- else
1666
- syncProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds ?? createdIds);
1710
+ if (!applyProjectionImpactToDocument(state, options, {
1711
+ document: result.workingDocument,
1712
+ selection,
1713
+ impact: projectionImpact,
1714
+ reason: 'duplicate-impact',
1715
+ })) {
1716
+ rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
1717
+ }
1667
1718
  state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
1668
1719
  state.status = patch.label ?? `Duplicated ${createdIds.length} object(s)`;
1669
1720
  return true;
@@ -1827,7 +1878,7 @@ function commitEditorStateChange(state, renderUi, reason, mutate, options = {})
1827
1878
  renderUi();
1828
1879
  if (options.syncSelectionArtifacts) {
1829
1880
  const selection = typeof options.syncSelectionArtifacts === 'boolean'
1830
- ? state.session?.getState().selection ?? { selectedIds: [], activeId: null }
1881
+ ? state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }
1831
1882
  : options.syncSelectionArtifacts;
1832
1883
  syncSelectionToProjection(state, selection, reason, revision);
1833
1884
  }
@@ -1847,7 +1898,7 @@ function syncSelectionToProjection(state, selection, reason = 'projection-select
1847
1898
  syncTransformGizmoToSceneArtifacts(state, reason, revision, false);
1848
1899
  }
1849
1900
  function syncCurrentSelectionToSceneArtifacts(state, reason = 'projection-selection-current', revision = state.editorStateRevision) {
1850
- syncSelectionToProjection(state, state.session?.getState().selection ?? { selectedIds: [], activeId: null }, reason, revision);
1901
+ syncSelectionToProjection(state, state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }, reason, revision);
1851
1902
  }
1852
1903
  function syncTransformGizmoToSceneArtifacts(state, reason, revision = state.editorStateRevision, beginSync = true) {
1853
1904
  if (beginSync && !beginSceneArtifactSync(state, reason, revision))
@@ -2299,7 +2350,8 @@ function patchSerializedProperty(state, options, input) {
2299
2350
  cancelActiveOperation(state);
2300
2351
  if (!state.session)
2301
2352
  return false;
2302
- const document = state.session.getState().workingDocument;
2353
+ const sessionState = state.session.getState({ cloneDocuments: false });
2354
+ const document = sessionState.workingDocument;
2303
2355
  const targetIds = input.targetIds && input.targetIds.length > 0 ? input.targetIds : [input.targetId];
2304
2356
  const transaction = createInspectorEditTransaction(state, options, document, input, targetIds);
2305
2357
  if (!transaction.ok) {
@@ -2312,7 +2364,7 @@ function patchSerializedProperty(state, options, input) {
2312
2364
  const patch = options.documentAdapter.createSerializedMultiPropertyPatch?.({
2313
2365
  document,
2314
2366
  targetIds,
2315
- activeId: state.session.getState().selection.activeId,
2367
+ activeId: sessionState.selection.activeId,
2316
2368
  path: payload.path,
2317
2369
  value: payload.value,
2318
2370
  control: payload.control,
@@ -2324,11 +2376,15 @@ function patchSerializedProperty(state, options, input) {
2324
2376
  if (!patch)
2325
2377
  return false;
2326
2378
  const changedIds = patch.changedIds ?? targetIds;
2379
+ const projectionMetadataPatch = isSerializedProjectionMetadataPath(payload.path);
2380
+ const largeProjectionMetadataPatch = projectionMetadataPatch
2381
+ && changedIds.length >= LARGE_SHADOW_MODE_BATCH_THRESHOLD;
2382
+ const invalidateStaticShadowArtifact = projectionMetadataPatch && payload.path === 'shadowMode';
2327
2383
  const result = state.session.dispatch({
2328
2384
  type: 'document.patch',
2329
2385
  label: patch.label ?? `Patch ${payload.path} on ${targetIds.length} objects`,
2330
2386
  patch: patch.patch,
2331
- targetId: state.session.getState().selection.activeId ?? undefined,
2387
+ targetId: sessionState.selection.activeId ?? undefined,
2332
2388
  ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2333
2389
  ? createReprojectProjectionImpact(patch.reprojectIds)
2334
2390
  : shouldReprojectInspectorEdit
@@ -2336,7 +2392,14 @@ function patchSerializedProperty(state, options, input) {
2336
2392
  : isSerializedTransformPath(payload.path)
2337
2393
  ? createTransformProjectionImpact(targetIds)
2338
2394
  : undefined)),
2339
- });
2395
+ }, largeProjectionMetadataPatch
2396
+ ? {
2397
+ assumeDocumentChanged: true,
2398
+ assumeDirty: true,
2399
+ clonePatchInput: false,
2400
+ cloneResultDocument: false,
2401
+ }
2402
+ : undefined);
2340
2403
  if (!result.documentChanged)
2341
2404
  return false;
2342
2405
  const projectionSyncIds = isSerializedTransformPath(payload.path) ? targetIds : changedIds;
@@ -2345,6 +2408,12 @@ function patchSerializedProperty(state, options, input) {
2345
2408
  reprojectProjectionForChangedIds(state, options, workingDocument, patch.reprojectIds);
2346
2409
  else if (shouldReprojectInspectorEdit)
2347
2410
  reprojectProjectionForChangedIds(state, options, workingDocument, changedIds);
2411
+ else if (isSerializedProjectionMetadataPath(payload.path)) {
2412
+ syncProjectionMetadataForChangedIds(state, options, workingDocument, projectionSyncIds, {
2413
+ deferShadowPreviewRefresh: largeProjectionMetadataPatch,
2414
+ invalidateStaticShadowArtifact,
2415
+ });
2416
+ }
2348
2417
  else
2349
2418
  syncProjectionForChangedIds(state, options, workingDocument, projectionSyncIds);
2350
2419
  state.summary = summarizeDocument(options, workingDocument, state.session.getSource());
@@ -2382,6 +2451,11 @@ function patchSerializedProperty(state, options, input) {
2382
2451
  else if (shouldReprojectInspectorEdit) {
2383
2452
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds ?? [patch.changedId ?? payload.targetId]);
2384
2453
  }
2454
+ else if (isSerializedProjectionMetadataPath(payload.path)) {
2455
+ syncProjectionMetadataForChangedIds(state, options, result.workingDocument, [patch.changedId ?? payload.targetId], {
2456
+ invalidateStaticShadowArtifact: payload.path === 'shadowMode',
2457
+ });
2458
+ }
2385
2459
  else if (isSerializedTransformPath(payload.path))
2386
2460
  syncProjectionForChangedIds(state, options, result.workingDocument, [patch.changedId ?? payload.targetId]);
2387
2461
  else if (patch.changedIds)
@@ -2423,13 +2497,26 @@ async function applyRenderingPropertyChange(state, options, input) {
2423
2497
  source: input.source,
2424
2498
  });
2425
2499
  const resultObject = typeof result === 'object' && result != null ? result : null;
2426
- const changed = result === true || resultObject?.changed === true || resultObject?.refreshWorldRendering === true;
2500
+ const documentPatchResult = resultObject?.documentPatch
2501
+ ? applyRenderingDocumentPatch(state, options, document, resultObject.documentPatch, `Rendering document update: ${input.path}`)
2502
+ : null;
2503
+ const syncDocument = documentPatchResult?.workingDocument ?? document;
2504
+ const changed = result === true
2505
+ || resultObject?.changed === true
2506
+ || resultObject?.refreshWorldRendering === true
2507
+ || documentPatchResult?.changed === true;
2427
2508
  if (resultObject?.status) {
2428
2509
  state.status = resultObject.status;
2429
2510
  state.statusTone = resultObject.statusTone ?? 'default';
2430
2511
  state.statusToneStatus = state.status;
2431
2512
  state.statusDetails = resultObject.statusDetails ?? '';
2432
2513
  }
2514
+ else if (documentPatchResult?.changed) {
2515
+ state.status = documentPatchResult.label;
2516
+ state.statusTone = 'success';
2517
+ state.statusToneStatus = state.status;
2518
+ state.statusDetails = '';
2519
+ }
2433
2520
  else if (changed) {
2434
2521
  state.status = `Updated rendering: ${input.path}`;
2435
2522
  state.statusTone = 'success';
@@ -2437,7 +2524,7 @@ async function applyRenderingPropertyChange(state, options, input) {
2437
2524
  state.statusDetails = '';
2438
2525
  }
2439
2526
  if (changed) {
2440
- syncEditorWorldRenderingFromDocument(state, options, document, 'rendering-panel-change', true);
2527
+ syncEditorWorldRenderingFromDocument(state, options, syncDocument, 'rendering-panel-change', true);
2441
2528
  requestEditorSceneFrame(state, 'rendering-panel-change');
2442
2529
  }
2443
2530
  return changed || !!resultObject?.status;
@@ -2451,25 +2538,78 @@ async function applyRenderingPropertyChange(state, options, input) {
2451
2538
  return true;
2452
2539
  }
2453
2540
  }
2454
- async function applyRenderingAction(state, options, input) {
2541
+ async function applyRenderingAction(state, options, input, render) {
2455
2542
  if (state.mode !== 'editor')
2456
2543
  return false;
2457
2544
  if (!state.session || !options.documentAdapter.onRenderingAction)
2458
2545
  return false;
2546
+ if (state.renderingAction) {
2547
+ state.status = state.renderingAction.status ?? `Rendering action already running: ${state.renderingAction.actionId}`;
2548
+ state.statusTone = state.renderingAction.statusTone ?? 'default';
2549
+ state.statusToneStatus = state.status;
2550
+ state.statusDetails = state.renderingAction.statusDetails ?? '';
2551
+ render();
2552
+ return true;
2553
+ }
2459
2554
  const document = state.session.getState().workingDocument;
2555
+ const progressStartedAt = Date.now();
2556
+ state.renderingAction = {
2557
+ actionId: input.actionId,
2558
+ label: input.actionId === 'bake-static-shadows' ? 'Baking static shadows' : input.actionId,
2559
+ status: input.actionId === 'bake-static-shadows' ? 'Baking static shadows...' : `Rendering action: ${input.actionId}`,
2560
+ statusTone: 'default',
2561
+ indeterminate: true,
2562
+ };
2563
+ state.status = state.renderingAction.status ?? state.status;
2564
+ state.statusTone = state.renderingAction.statusTone ?? 'default';
2565
+ state.statusToneStatus = state.status;
2566
+ state.statusDetails = '';
2567
+ render();
2460
2568
  try {
2569
+ await state.projection?.waitForIdle?.();
2461
2570
  const result = await options.documentAdapter.onRenderingAction({
2571
+ ...input,
2462
2572
  document,
2463
2573
  actionId: input.actionId,
2574
+ scene: state.world?.scene ?? null,
2575
+ projection: state.projection,
2576
+ reportProgress: async (progress) => {
2577
+ state.renderingAction = {
2578
+ ...state.renderingAction,
2579
+ ...progress,
2580
+ actionId: progress.actionId ?? state.renderingAction?.actionId ?? input.actionId,
2581
+ };
2582
+ if (progress.status) {
2583
+ state.status = progress.status;
2584
+ state.statusTone = progress.statusTone ?? 'default';
2585
+ state.statusToneStatus = state.status;
2586
+ state.statusDetails = progress.statusDetails ?? '';
2587
+ }
2588
+ render();
2589
+ await waitForRenderingProgressFrame();
2590
+ },
2464
2591
  });
2465
2592
  const resultObject = typeof result === 'object' && result != null ? result : null;
2466
- const changed = result === true || resultObject?.changed === true || resultObject?.refreshWorldRendering === true;
2593
+ const documentPatchResult = resultObject?.documentPatch
2594
+ ? applyRenderingDocumentPatch(state, options, document, resultObject.documentPatch, `Rendering action document update: ${input.actionId}`)
2595
+ : null;
2596
+ const syncDocument = documentPatchResult?.workingDocument ?? document;
2597
+ const changed = result === true
2598
+ || resultObject?.changed === true
2599
+ || resultObject?.refreshWorldRendering === true
2600
+ || documentPatchResult?.changed === true;
2467
2601
  if (resultObject?.status) {
2468
2602
  state.status = resultObject.status;
2469
2603
  state.statusTone = resultObject.statusTone ?? 'default';
2470
2604
  state.statusToneStatus = state.status;
2471
2605
  state.statusDetails = resultObject.statusDetails ?? '';
2472
2606
  }
2607
+ else if (documentPatchResult?.changed) {
2608
+ state.status = documentPatchResult.label;
2609
+ state.statusTone = 'success';
2610
+ state.statusToneStatus = state.status;
2611
+ state.statusDetails = '';
2612
+ }
2473
2613
  else if (changed) {
2474
2614
  state.status = `Rendering action: ${input.actionId}`;
2475
2615
  state.statusTone = 'success';
@@ -2477,12 +2617,14 @@ async function applyRenderingAction(state, options, input) {
2477
2617
  state.statusDetails = '';
2478
2618
  }
2479
2619
  if (changed) {
2480
- syncEditorWorldRenderingFromDocument(state, options, document, 'rendering-panel-action', true);
2481
- requestEditorSceneFrame(state, 'rendering-panel-action');
2620
+ syncEditorWorldRenderingFromDocument(state, options, syncDocument, 'rendering-panel-action', true);
2621
+ invalidateEditorScene(state, 'rendering-panel-action', options);
2482
2622
  }
2623
+ clearRenderingActionAfterMinimumDuration(state, progressStartedAt, input.actionId, render);
2483
2624
  return changed || !!resultObject?.status;
2484
2625
  }
2485
2626
  catch (error) {
2627
+ state.renderingAction = null;
2486
2628
  state.status = error instanceof Error ? error.message : String(error);
2487
2629
  state.statusTone = 'error';
2488
2630
  state.statusToneStatus = state.status;
@@ -2491,6 +2633,70 @@ async function applyRenderingAction(state, options, input) {
2491
2633
  return true;
2492
2634
  }
2493
2635
  }
2636
+ function clearRenderingActionAfterMinimumDuration(state, startedAt, actionId, render) {
2637
+ void waitForRenderingProgressMinimumDuration(startedAt).then(() => {
2638
+ if (state.renderingAction?.actionId !== actionId)
2639
+ return;
2640
+ state.renderingAction = null;
2641
+ render();
2642
+ });
2643
+ }
2644
+ function waitForRenderingProgressFrame() {
2645
+ return new Promise(resolve => {
2646
+ if (typeof requestAnimationFrame === 'function') {
2647
+ requestAnimationFrame(() => resolve());
2648
+ }
2649
+ else {
2650
+ setTimeout(resolve, 0);
2651
+ }
2652
+ });
2653
+ }
2654
+ function waitForRenderingProgressMinimumDuration(startedAt) {
2655
+ const elapsed = Date.now() - startedAt;
2656
+ const remaining = RENDERING_ACTION_PROGRESS_MIN_VISIBLE_MS - elapsed;
2657
+ if (remaining <= 0)
2658
+ return Promise.resolve();
2659
+ return new Promise(resolve => setTimeout(resolve, remaining));
2660
+ }
2661
+ function applyRenderingDocumentPatch(state, options, document, patch, fallbackLabel) {
2662
+ if (state.mode !== 'editor' || !state.session) {
2663
+ return { changed: false, workingDocument: document, label: patch.label ?? fallbackLabel };
2664
+ }
2665
+ const label = patch.label ?? fallbackLabel;
2666
+ const defaultImpact = patch.reprojectIds?.length
2667
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2668
+ : patch.changedIds?.length
2669
+ ? createReprojectProjectionImpact(patch.changedIds)
2670
+ : patch.changedId
2671
+ ? createReprojectProjectionImpact([patch.changedId])
2672
+ : undefined;
2673
+ const projectionImpact = resolveProjectionImpact(patch, defaultImpact);
2674
+ const result = state.session.dispatch({
2675
+ type: 'document.patch',
2676
+ label,
2677
+ patch: patch.patch,
2678
+ targetId: patch.changedId,
2679
+ ...withProjectionImpact(projectionImpact),
2680
+ });
2681
+ if (!result.documentChanged) {
2682
+ return { changed: false, workingDocument: result.workingDocument, label };
2683
+ }
2684
+ if (projectionImpact) {
2685
+ const applied = applyProjectionImpactToDocument(state, options, {
2686
+ document: result.workingDocument,
2687
+ selection: result.selection,
2688
+ impact: projectionImpact,
2689
+ reason: 'rendering-document-patch-impact',
2690
+ });
2691
+ if (!applied)
2692
+ rebuildProjectionFromDocument(state, options, result.workingDocument, result.selection);
2693
+ }
2694
+ else {
2695
+ syncProjectionForDispatchResult(state, options, result, patch.changedId);
2696
+ }
2697
+ state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2698
+ return { changed: true, workingDocument: result.workingDocument, label };
2699
+ }
2494
2700
  function createInspectorEditTransaction(state, options, document, input, targetIds) {
2495
2701
  const property = findInspectorPropertyForEdit(state, options, document, input, targetIds);
2496
2702
  if (!property) {
@@ -2578,6 +2784,9 @@ function isSerializedTransformPath(path) {
2578
2784
  || path.startsWith('transform.rotation.')
2579
2785
  || path.startsWith('transform.scale.');
2580
2786
  }
2787
+ function isSerializedProjectionMetadataPath(path) {
2788
+ return path === 'shadowMode';
2789
+ }
2581
2790
  function beginDuplicateDrag(state, options, input) {
2582
2791
  if (state.mode !== 'editor' || !state.session)
2583
2792
  return null;
@@ -2595,15 +2804,13 @@ function beginDuplicateDrag(state, options, input) {
2595
2804
  return null;
2596
2805
  }
2597
2806
  const originalSelection = state.session.getSelection();
2807
+ const projectionImpact = resolveProjectionImpact(patch, createDuplicateProjectionImpact(patch));
2598
2808
  const result = state.session.dispatch({
2599
2809
  type: 'document.patch',
2600
2810
  label: patch.label ?? `Duplicate ${input.targetIds.length} object(s)`,
2601
2811
  patch: patch.patch,
2602
2812
  targetId: patch.activeId ?? patch.createdIds[patch.createdIds.length - 1] ?? undefined,
2603
- ...withProjectionImpact(resolveProjectionImpact(patch, createCreatedReprojectProjectionImpact({
2604
- createdNodeIds: patch.createdIds,
2605
- reprojectIds: patch.reprojectIds,
2606
- }))),
2813
+ ...withProjectionImpact(projectionImpact),
2607
2814
  });
2608
2815
  if (!result.documentChanged) {
2609
2816
  state.status = 'Duplicate drag unchanged';
@@ -2631,11 +2838,14 @@ function beginDuplicateDrag(state, options, input) {
2631
2838
  createdIds,
2632
2839
  activeId,
2633
2840
  };
2634
- rebuildProjectionFromDocument(state, options, result.workingDocument, selectionResult.selection);
2635
- if (patch.reprojectIds?.length)
2636
- reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
2637
- else
2638
- syncProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds ?? createdIds);
2841
+ if (!applyProjectionImpactToDocument(state, options, {
2842
+ document: result.workingDocument,
2843
+ selection: selectionResult.selection,
2844
+ impact: projectionImpact,
2845
+ reason: 'duplicate-drag-impact',
2846
+ })) {
2847
+ rebuildProjectionFromDocument(state, options, result.workingDocument, selectionResult.selection);
2848
+ }
2639
2849
  state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2640
2850
  state.status = patch.label ?? `Duplicated ${createdIds.length} object(s)`;
2641
2851
  return {
@@ -2862,6 +3072,19 @@ function createKnownCreatedReprojectProjectionImpact(input) {
2862
3072
  reprojectIds: input.reprojectIds,
2863
3073
  });
2864
3074
  }
3075
+ function createDuplicateProjectionImpact(patch) {
3076
+ const createdNodeIds = uniqueProjectionIds(patch.createdIds);
3077
+ const reprojectIds = uniqueProjectionIds(patch.reprojectIds);
3078
+ const createdNodeIdSet = new Set(createdNodeIds);
3079
+ const reprojectIdSet = new Set(reprojectIds);
3080
+ const syncTransformIds = uniqueProjectionIds(patch.changedIds)
3081
+ .filter(id => !createdNodeIdSet.has(id) && !reprojectIdSet.has(id));
3082
+ return createIncrementalProjectionImpact({
3083
+ createdNodeIds,
3084
+ reprojectIds,
3085
+ syncTransformIds,
3086
+ });
3087
+ }
2865
3088
  function createIncrementalProjectionImpact(input) {
2866
3089
  const syncTransformIds = uniqueProjectionIds(input.syncTransformIds);
2867
3090
  const reprojectIds = uniqueProjectionIds(input.reprojectIds);
@@ -2965,56 +3188,85 @@ function applyHistoryProjectionImpact(state, options, result, operation) {
2965
3188
  if (!applied)
2966
3189
  rebuildProjection(state, options, result);
2967
3190
  }
3191
+ function applyProjectionImpactToDocument(state, options, input) {
3192
+ if (!input.impact || input.impact.type === 'rebuild')
3193
+ return false;
3194
+ return applyIncrementalProjectionImpactToDocument(state, options, {
3195
+ document: input.document,
3196
+ selection: input.selection,
3197
+ syncTransformIds: input.impact.syncTransformIds,
3198
+ reprojectIds: input.impact.reprojectIds,
3199
+ projectNodeIds: input.impact.createdNodeIds,
3200
+ removeNodeIds: input.impact.deletedNodeIds,
3201
+ reason: input.reason,
3202
+ });
3203
+ }
2968
3204
  function rebuildProjection(state, options, result) {
2969
3205
  rebuildProjectionFromDocument(state, options, result.workingDocument, result.selection);
2970
3206
  }
2971
3207
  function applyIncrementalHistoryProjectionImpact(state, options, result, operation, impact) {
2972
- if (!state.projection)
2973
- return false;
2974
- const syncTransformIds = uniqueProjectionIds(impact.syncTransformIds);
2975
- const reprojectIds = uniqueProjectionIds(impact.reprojectIds);
2976
3208
  const projectNodeIds = operation === 'redo'
2977
3209
  ? uniqueProjectionIds(impact.createdNodeIds)
2978
3210
  : uniqueProjectionIds(impact.deletedNodeIds);
2979
3211
  const removeNodeIds = operation === 'redo'
2980
3212
  ? uniqueProjectionIds(impact.deletedNodeIds)
2981
3213
  : uniqueProjectionIds(impact.createdNodeIds);
3214
+ return applyIncrementalProjectionImpactToDocument(state, options, {
3215
+ document: result.workingDocument,
3216
+ selection: result.selection,
3217
+ syncTransformIds: impact.syncTransformIds,
3218
+ reprojectIds: impact.reprojectIds,
3219
+ projectNodeIds,
3220
+ removeNodeIds,
3221
+ reason: 'history-impact',
3222
+ });
3223
+ }
3224
+ function applyIncrementalProjectionImpactToDocument(state, options, input) {
3225
+ if (!state.projection)
3226
+ return false;
3227
+ const syncTransformIds = uniqueProjectionIds(input.syncTransformIds);
3228
+ const reprojectIds = uniqueProjectionIds(input.reprojectIds);
3229
+ const projectNodeIds = uniqueProjectionIds(input.projectNodeIds);
3230
+ const removeNodeIds = uniqueProjectionIds(input.removeNodeIds);
2982
3231
  const touched = syncTransformIds.length > 0
2983
3232
  || reprojectIds.length > 0
2984
3233
  || projectNodeIds.length > 0
2985
3234
  || removeNodeIds.length > 0;
2986
3235
  if (!touched)
2987
3236
  return false;
3237
+ const projectNodes = resolveProjectionNodes(options, input.document, projectNodeIds);
3238
+ const reprojectNodes = resolveProjectionNodes(options, input.document, reprojectIds);
3239
+ const syncTransformNodes = resolveProjectionNodes(options, input.document, syncTransformIds);
3240
+ if (!projectNodes || !reprojectNodes || !syncTransformNodes)
3241
+ return false;
2988
3242
  for (const nodeId of removeNodeIds) {
2989
3243
  state.projection.removeNode(nodeId);
2990
3244
  }
2991
- for (const nodeId of projectNodeIds) {
2992
- const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, nodeId);
2993
- if (!projectedNode)
2994
- return false;
3245
+ for (const projectedNode of projectNodes) {
2995
3246
  state.projection.projectNode(projectedNode);
2996
3247
  }
2997
- for (const nodeId of reprojectIds) {
2998
- const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, nodeId);
2999
- if (!projectedNode)
3000
- return false;
3248
+ for (const projectedNode of reprojectNodes) {
3001
3249
  state.projection.projectNode(projectedNode);
3002
3250
  }
3003
- for (const nodeId of syncTransformIds) {
3004
- const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, nodeId);
3005
- if (!projectedNode)
3006
- return false;
3251
+ for (const projectedNode of syncTransformNodes) {
3007
3252
  state.projection.syncNodeTransform(projectedNode);
3008
3253
  }
3009
- syncProjectionAfterHistoryIncrementalImpact(state, options, result.workingDocument, result.selection);
3254
+ const sanitized = sanitizeSelection(state, options, input.document, input.selection);
3255
+ syncSelectionToProjection(state, sanitized ?? input.selection);
3256
+ syncEditorWorldAppearanceFromDocument(state, options, input.document, `world-appearance-${input.reason}`);
3257
+ syncEditorWorldRenderingFromDocument(state, options, input.document, `world-rendering-${input.reason}`, true);
3258
+ invalidateEditorScene(state, `projection-${input.reason}`, options);
3010
3259
  return true;
3011
3260
  }
3012
- function syncProjectionAfterHistoryIncrementalImpact(state, options, document, selection) {
3013
- const sanitized = sanitizeSelection(state, options, document, selection);
3014
- syncSelectionToProjection(state, sanitized ?? selection);
3015
- syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-history-impact');
3016
- syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-history-impact', true);
3017
- invalidateEditorScene(state, 'projection-history-impact', options);
3261
+ function resolveProjectionNodes(options, document, ids) {
3262
+ const projectedNodes = [];
3263
+ for (const id of ids) {
3264
+ const projectedNode = options.documentAdapter.getProjectionNode(document, id);
3265
+ if (!projectedNode)
3266
+ return null;
3267
+ projectedNodes.push(projectedNode);
3268
+ }
3269
+ return projectedNodes;
3018
3270
  }
3019
3271
  function rebuildProjectionFromDocument(state, options, document, selection) {
3020
3272
  state.projection?.rebuild(options.documentAdapter.getProjectionNodes(document));
@@ -3034,7 +3286,7 @@ function syncProjectionForDispatchResult(state, options, result, changedId) {
3034
3286
  syncSelectionToProjection(state, result.selection ?? { selectedIds: [], activeId: null });
3035
3287
  }
3036
3288
  else if (result.documentChanged) {
3037
- const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
3289
+ const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3038
3290
  const sanitized = result.workingDocument ? sanitizeSelection(state, options, result.workingDocument, selection) : null;
3039
3291
  syncSelectionToProjection(state, sanitized ?? selection);
3040
3292
  }
@@ -3052,12 +3304,36 @@ function syncProjectionForChangedIds(state, options, document, changedIds) {
3052
3304
  if (projectedNode)
3053
3305
  state.projection?.syncNodeTransform(projectedNode);
3054
3306
  }
3055
- const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
3307
+ const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3056
3308
  syncSelectionToProjection(state, selection);
3057
3309
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-sync-changed-ids');
3058
3310
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-sync-changed-ids', true);
3059
3311
  invalidateEditorScene(state, 'projection-sync-changed-ids');
3060
3312
  }
3313
+ function syncProjectionMetadataForChangedIds(state, options, document, changedIds, syncOptions = {}) {
3314
+ for (const changedId of changedIds) {
3315
+ const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3316
+ if (projectedNode) {
3317
+ state.projection?.syncNodeMetadata?.(projectedNode);
3318
+ }
3319
+ }
3320
+ const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3321
+ syncSelectionToProjection(state, selection);
3322
+ syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-sync-metadata-ids');
3323
+ if (syncOptions.invalidateStaticShadowArtifact) {
3324
+ state.shadowPreview?.invalidateStaticProjectedArtifact?.();
3325
+ }
3326
+ else {
3327
+ syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-sync-metadata-ids', true);
3328
+ }
3329
+ if (syncOptions.deferShadowPreviewRefresh) {
3330
+ scheduleProjectionShadowPreviewRefresh(state, 'projection-sync-metadata-ids-shadow-preview');
3331
+ }
3332
+ else {
3333
+ state.shadowPreview?.refresh();
3334
+ }
3335
+ invalidateEditorScene(state, 'projection-sync-metadata-ids');
3336
+ }
3061
3337
  function reprojectProjectionForChangedIds(state, options, document, changedIds) {
3062
3338
  for (const changedId of changedIds) {
3063
3339
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
@@ -3234,21 +3510,24 @@ function radiansToInspectorDegrees(value) {
3234
3510
  return (value * 180) / Math.PI;
3235
3511
  }
3236
3512
  function createUiState(state, options) {
3237
- const sessionState = state.session?.getState();
3513
+ const sessionState = state.session?.getState({ cloneDocuments: false });
3238
3514
  const document = sessionState?.workingDocument ?? null;
3239
3515
  const selectedIds = sessionState?.selection.selectedIds ?? [];
3240
3516
  const activeId = sessionState?.selection.activeId ?? null;
3517
+ const largeSelection = isLargeSelection(selectedIds);
3241
3518
  const serializedObject = document && activeId && selectedIds.length === 1
3242
3519
  ? options.documentAdapter.getSerializedObject(document, activeId)
3243
3520
  : null;
3244
- const serializedMultiObject = document && selectedIds.length > 1
3521
+ const serializedMultiObject = document && selectedIds.length > 1 && !largeSelection
3245
3522
  ? options.documentAdapter.getSerializedMultiObject?.(document, selectedIds, activeId) ?? null
3246
3523
  : null;
3247
3524
  const inspectorObjectBase = document && activeId && selectedIds.length === 1
3248
3525
  ? options.documentAdapter.getInspectorObject?.(document, activeId) ?? (serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null)
3249
3526
  : null;
3250
3527
  const inspectorMultiObjectBase = document && selectedIds.length > 1
3251
- ? createInspectorMultiObjectBase(state, options, document, selectedIds, activeId, serializedMultiObject)
3528
+ ? createInspectorMultiObjectBase(state, options, document, selectedIds, activeId, serializedMultiObject, {
3529
+ largeSelection,
3530
+ })
3252
3531
  : null;
3253
3532
  const inspectorObject = document && inspectorObjectBase
3254
3533
  ? withRuntimeInspectorSections(state, options, document, inspectorObjectBase)
@@ -3261,6 +3540,13 @@ function createUiState(state, options) {
3261
3540
  const documentAssetItems = document
3262
3541
  ? options.documentAdapter.getBrowserAssetItems?.(document) ?? []
3263
3542
  : [];
3543
+ const hierarchy = document ? options.documentAdapter.getHierarchyItems(document) : [];
3544
+ state.performanceObjectCount = document ? countLocalEditorAuthoredObjects(hierarchy) : null;
3545
+ state.performanceStats = createLocalEditorPerformanceStats({
3546
+ frameStats: state.sceneFrameStats,
3547
+ runtimeStats: state.performanceMonitor?.readSnapshot() ?? null,
3548
+ objectCount: state.performanceObjectCount,
3549
+ });
3264
3550
  const assets = dedupeLocalEditorBrowserAssetItems([
3265
3551
  ...state.assets.map(asset => toBrowserAssetItem(options, asset)),
3266
3552
  ...documentAssetItems,
@@ -3276,7 +3562,7 @@ function createUiState(state, options) {
3276
3562
  assets,
3277
3563
  selectedAssetId: state.selectedAssetId,
3278
3564
  assetCountLabel: `${assets.length} assets`,
3279
- hierarchy: document ? options.documentAdapter.getHierarchyItems(document) : [],
3565
+ hierarchy,
3280
3566
  selectedIds,
3281
3567
  activeId,
3282
3568
  selectionSummary: {
@@ -3287,7 +3573,13 @@ function createUiState(state, options) {
3287
3573
  serializedMultiObject,
3288
3574
  inspectorObject: previewInspectorObject,
3289
3575
  inspectorMultiObject: previewInspectorMultiObject,
3290
- renderingPanel: document ? options.documentAdapter.getRenderingPanelState?.(document) ?? null : null,
3576
+ renderingPanel: document
3577
+ ? options.documentAdapter.getRenderingPanelState?.(document, {
3578
+ scene: state.world?.scene ?? null,
3579
+ projection: state.projection,
3580
+ renderingAction: state.renderingAction,
3581
+ }) ?? null
3582
+ : null,
3291
3583
  boxSelection: state.boxSelection,
3292
3584
  coordinateAxes: options.world?.coordinateAxes === false
3293
3585
  ? null
@@ -3303,8 +3595,8 @@ function createUiState(state, options) {
3303
3595
  settings: cloneTransformOperationSettings(state.transformOperationSettings),
3304
3596
  selectedCount: selectedIds.length,
3305
3597
  activeId,
3306
- canAlign: selectedIds.length >= 2 && activeId != null,
3307
- canDistribute: selectedIds.length >= 3,
3598
+ canAlign: !largeSelection && selectedIds.length >= 2 && activeId != null,
3599
+ canDistribute: !largeSelection && selectedIds.length >= 3,
3308
3600
  },
3309
3601
  viewportTools: cloneEditorViewportToolState(state.viewportTools),
3310
3602
  viewportMeasurement: cloneViewportMeasurement(state.viewportMeasurement),
@@ -3312,6 +3604,7 @@ function createUiState(state, options) {
3312
3604
  sceneFrameStats: state.sceneFrameStats
3313
3605
  ? { ...state.sceneFrameStats, activeReasons: [...state.sceneFrameStats.activeReasons] }
3314
3606
  : null,
3607
+ performanceStats: cloneLocalEditorPerformanceStats(state.performanceStats),
3315
3608
  sceneCameraPreview: {
3316
3609
  enabled: state.sceneCameraPreviewEnabled,
3317
3610
  available: hasSceneCameraPreviewRig(state, options),
@@ -3332,10 +3625,12 @@ function createUiState(state, options) {
3332
3625
  };
3333
3626
  }
3334
3627
  function createUiPreviewState(state, options) {
3335
- const selection = state.session?.getState().selection;
3336
- const document = state.session?.getState().workingDocument ?? null;
3628
+ const sessionState = state.session?.getState({ cloneDocuments: false });
3629
+ const selection = sessionState?.selection;
3630
+ const document = sessionState?.workingDocument ?? null;
3337
3631
  const selectedIds = selection?.selectedIds ?? [];
3338
3632
  const activeId = selection?.activeId ?? null;
3633
+ const largeSelection = isLargeSelection(selectedIds);
3339
3634
  const shouldPreviewInspector = !!state.activeTransformPreview && document && activeId && selectedIds.length === 1;
3340
3635
  const inspectorObjectBase = shouldPreviewInspector
3341
3636
  ? options.documentAdapter.getInspectorObject?.(document, activeId)
@@ -3367,8 +3662,8 @@ function createUiPreviewState(state, options) {
3367
3662
  settings: cloneTransformOperationSettings(state.transformOperationSettings),
3368
3663
  selectedCount: selectedIds.length,
3369
3664
  activeId,
3370
- canAlign: selectedIds.length >= 2 && activeId != null,
3371
- canDistribute: selectedIds.length >= 3,
3665
+ canAlign: !largeSelection && selectedIds.length >= 2 && activeId != null,
3666
+ canDistribute: !largeSelection && selectedIds.length >= 3,
3372
3667
  },
3373
3668
  viewportTools: cloneEditorViewportToolState(state.viewportTools),
3374
3669
  viewportMeasurement: cloneViewportMeasurement(state.viewportMeasurement),
@@ -3376,8 +3671,28 @@ function createUiPreviewState(state, options) {
3376
3671
  sceneFrameStats: state.sceneFrameStats
3377
3672
  ? { ...state.sceneFrameStats, activeReasons: [...state.sceneFrameStats.activeReasons] }
3378
3673
  : null,
3674
+ performanceStats: cloneLocalEditorPerformanceStats(state.performanceStats),
3379
3675
  };
3380
3676
  }
3677
+ function setLocalEditorPerformanceMonitorOpen(state, open) {
3678
+ if (state.performanceMonitorOpen === open)
3679
+ return;
3680
+ state.performanceMonitorOpen = open;
3681
+ if (open) {
3682
+ state.viewportRenderCoordinator?.beginContinuous(LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON);
3683
+ }
3684
+ else {
3685
+ state.viewportRenderCoordinator?.endContinuous(LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON);
3686
+ }
3687
+ }
3688
+ function updateLocalEditorPerformanceStats(state, updatePerformanceStats) {
3689
+ state.performanceStats = createLocalEditorPerformanceStats({
3690
+ frameStats: state.sceneFrameStats,
3691
+ runtimeStats: state.performanceMonitor?.readSnapshot() ?? null,
3692
+ objectCount: state.performanceObjectCount,
3693
+ });
3694
+ updatePerformanceStats(cloneLocalEditorPerformanceStats(state.performanceStats));
3695
+ }
3381
3696
  function createSceneViewCoordinateAxesState(state) {
3382
3697
  if (state.mode !== 'editor')
3383
3698
  return null;
@@ -3471,36 +3786,84 @@ function clampNumber(value, min, max) {
3471
3786
  return min;
3472
3787
  return Math.min(max, Math.max(min, value));
3473
3788
  }
3474
- function createInspectorMultiObjectBase(state, options, document, selectedIds, activeId, serializedMultiObject) {
3789
+ function isLargeSelection(selectedIds) {
3790
+ return selectedIds.length > LARGE_SELECTION_THRESHOLD;
3791
+ }
3792
+ function createInspectorMultiObjectBase(state, options, document, selectedIds, activeId, serializedMultiObject, multiOptions = {}) {
3475
3793
  const targetIds = [...selectedIds];
3476
3794
  if (targetIds.length <= 1)
3477
3795
  return null;
3478
3796
  const adapterMultiObject = options.documentAdapter.getInspectorMultiObject?.(document, targetIds, activeId) ?? null;
3797
+ if (multiOptions.largeSelection) {
3798
+ return applyMultiInspectorBatchCapabilityGate(options, document, adapterMultiObject ?? createLargeSelectionInspectorObject(document, targetIds, activeId));
3799
+ }
3479
3800
  const serializedMulti = serializedMultiObject !== undefined
3480
3801
  ? serializedMultiObject
3481
3802
  : options.documentAdapter.getSerializedMultiObject?.(document, targetIds, activeId) ?? null;
3482
3803
  const serializedInspector = serializedMulti ? serializedMultiObjectToInspectorObject(serializedMulti, document) : null;
3483
3804
  const baseInspector = adapterMultiObject ?? serializedInspector;
3484
3805
  const aggregatedInspector = createAggregatedInspectorMultiObjectBase(state, options, document, targetIds, activeId);
3485
- if (!baseInspector)
3486
- return aggregatedInspector;
3487
- if (!aggregatedInspector)
3488
- return baseInspector;
3806
+ let inspectorObject = baseInspector ?? aggregatedInspector;
3807
+ if (baseInspector && aggregatedInspector) {
3808
+ inspectorObject = {
3809
+ ...baseInspector,
3810
+ targetIds,
3811
+ activeId,
3812
+ label: baseInspector.label ?? aggregatedInspector.label,
3813
+ document,
3814
+ selection: {
3815
+ ...baseInspector.selection,
3816
+ targetIds,
3817
+ activeId,
3818
+ document,
3819
+ },
3820
+ sections: mergeInspectorSections(baseInspector.sections, aggregatedInspector.sections, {
3821
+ propertyConflict: 'ignore',
3822
+ }),
3823
+ };
3824
+ }
3825
+ return applyMultiInspectorBatchCapabilityGate(options, document, inspectorObject);
3826
+ }
3827
+ function createLargeSelectionInspectorObject(document, targetIds, activeId) {
3489
3828
  return {
3490
- ...baseInspector,
3491
- targetIds,
3829
+ targetIds: [...targetIds],
3492
3830
  activeId,
3493
- label: baseInspector.label ?? aggregatedInspector.label,
3831
+ label: `${targetIds.length} objects`,
3494
3832
  document,
3495
3833
  selection: {
3496
- ...baseInspector.selection,
3497
- targetIds,
3834
+ targetIds: [...targetIds],
3498
3835
  activeId,
3499
3836
  document,
3500
3837
  },
3501
- sections: mergeInspectorSections(baseInspector.sections, aggregatedInspector.sections, {
3502
- propertyConflict: 'ignore',
3503
- }),
3838
+ sections: [{
3839
+ id: 'large-selection',
3840
+ title: 'Large Selection',
3841
+ order: 10,
3842
+ placement: 'body',
3843
+ persistence: 'readonly',
3844
+ properties: [
3845
+ {
3846
+ path: 'selection.count',
3847
+ label: 'Selected',
3848
+ valueType: 'number',
3849
+ control: 'readonly',
3850
+ value: targetIds.length,
3851
+ readOnly: true,
3852
+ persistence: 'readonly',
3853
+ commitMode: 'immediate',
3854
+ },
3855
+ {
3856
+ path: 'selection.activeId',
3857
+ label: 'Active ID',
3858
+ valueType: 'string',
3859
+ control: 'readonly',
3860
+ value: activeId ?? 'None',
3861
+ readOnly: true,
3862
+ persistence: 'readonly',
3863
+ commitMode: 'immediate',
3864
+ },
3865
+ ],
3866
+ }],
3504
3867
  };
3505
3868
  }
3506
3869
  function createAggregatedInspectorMultiObjectBase(_state, options, document, selectedIds, activeId) {
@@ -3514,9 +3877,7 @@ function createAggregatedInspectorMultiObjectBase(_state, options, document, sel
3514
3877
  label: `${selectedIds.length} objects`,
3515
3878
  document,
3516
3879
  });
3517
- return options.documentAdapter.createSerializedMultiPropertyPatch
3518
- ? applyAggregatedBatchInspectorCapabilityGate(options, document, aggregatedInspector)
3519
- : disableAggregatedBatchInspectorObject(aggregatedInspector);
3880
+ return aggregatedInspector;
3520
3881
  }
3521
3882
  function createSingleInspectorObjectBase(options, document, targetId) {
3522
3883
  const inspectorObject = options.documentAdapter.getInspectorObject?.(document, targetId) ?? null;
@@ -3525,6 +3886,11 @@ function createSingleInspectorObjectBase(options, document, targetId) {
3525
3886
  const serializedObject = options.documentAdapter.getSerializedObject(document, targetId);
3526
3887
  return serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null;
3527
3888
  }
3889
+ function applyMultiInspectorBatchCapabilityGate(options, document, inspectorObject) {
3890
+ return options.documentAdapter.createSerializedMultiPropertyPatch
3891
+ ? applyAggregatedBatchInspectorCapabilityGate(options, document, inspectorObject)
3892
+ : disableAggregatedBatchInspectorObject(inspectorObject);
3893
+ }
3528
3894
  function disableAggregatedBatchInspectorObject(inspectorObject) {
3529
3895
  if (!inspectorObject)
3530
3896
  return null;
@@ -3836,13 +4202,13 @@ async function saveDocumentFallback(options, document) {
3836
4202
  return options.persistenceAdapter.saveDocument(document);
3837
4203
  }
3838
4204
  function isDocumentNodeSelectable(state, options, id) {
3839
- const document = state.session?.getState().workingDocument;
4205
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument;
3840
4206
  if (!document)
3841
4207
  return false;
3842
4208
  return isNodeSelectableInDocument(options, document, id);
3843
4209
  }
3844
4210
  function isDocumentNodeLocked(state, options, id) {
3845
- const document = state.session?.getState().workingDocument;
4211
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument;
3846
4212
  if (!document)
3847
4213
  return false;
3848
4214
  return options.documentAdapter.isLocked?.(document, id) ?? false;