@babylonjs/inspector 9.26.0 → 9.26.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 (36) hide show
  1. package/lib/browser-CANgtOiM.js.map +1 -1
  2. package/lib/components/properties/boundProperty.d.ts +16 -0
  3. package/lib/components/properties/metadataProperties.d.ts +5 -1
  4. package/lib/{extensionsListService-DVpZa3gV.js → extensionsListService-2sqR5MRD.js} +3 -4
  5. package/lib/{extensionsListService-DVpZa3gV.js.map → extensionsListService-2sqR5MRD.js.map} +1 -1
  6. package/lib/gif-5DieAkSF.js.map +1 -1
  7. package/lib/{index-C5ggNiyY.js → index-DvyGGbVg.js} +244 -90
  8. package/lib/index-DvyGGbVg.js.map +1 -0
  9. package/lib/index.js +3 -4
  10. package/lib/index.js.map +1 -1
  11. package/lib/lite/components/properties/cameraProperties.d.ts +11 -0
  12. package/lib/lite/components/properties/lightProperties.d.ts +33 -0
  13. package/lib/lite/components/properties/sceneNodeProperties.d.ts +8 -0
  14. package/lib/lite/{extensionsListService-xgITNk9y.js → extensionsListService-BP2ToO_O.js} +2 -4
  15. package/lib/lite/{extensionsListService-xgITNk9y.js.map → extensionsListService-BP2ToO_O.js.map} +1 -1
  16. package/lib/lite/{index-DmQN3QdU.js → index-6eEEtyAL.js} +1363 -194
  17. package/lib/lite/index-6eEEtyAL.js.map +1 -0
  18. package/lib/lite/index.js +2 -4
  19. package/lib/lite/index.js.map +1 -1
  20. package/lib/lite/sceneEntityUtils.d.ts +19 -0
  21. package/lib/lite/services/panes/properties/cameraPropertiesService.d.ts +3 -0
  22. package/lib/lite/services/panes/properties/lightPropertiesService.d.ts +3 -0
  23. package/lib/lite/services/panes/properties/sceneNodePropertiesService.d.ts +3 -0
  24. package/lib/lite/services/panes/properties/shadowGeneratorPropertiesService.d.ts +4 -0
  25. package/lib/lite/services/panes/scene/cameraExplorerService.d.ts +7 -0
  26. package/lib/lite/services/panes/scene/lightExplorerService.d.ts +6 -0
  27. package/lib/lite/services/panes/scene/meshExplorerService.d.ts +4 -1
  28. package/lib/lite/services/panes/scene/sceneSelectionUtils.d.ts +3 -0
  29. package/lib/lite/services/panes/scene/shadowGeneratorExplorerService.d.ts +6 -0
  30. package/lib/{quickCreateToolsService-0rVa_CjZ.js → quickCreateToolsService-B0RAjMcI.js} +4 -5
  31. package/lib/{quickCreateToolsService-0rVa_CjZ.js.map → quickCreateToolsService-B0RAjMcI.js.map} +1 -1
  32. package/lib/{reflectorService-pyDncbYA.js → reflectorService-CUFxJIMt.js} +4 -5
  33. package/lib/{reflectorService-pyDncbYA.js.map → reflectorService-CUFxJIMt.js.map} +1 -1
  34. package/package.json +1 -1
  35. package/lib/index-C5ggNiyY.js.map +0 -1
  36. package/lib/lite/index-DmQN3QdU.js.map +0 -1
@@ -1,18 +1,16 @@
1
1
  import { Observable } from '@babylonjs/core/Misc/observable.js';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { createContext, forwardRef, useContext, useState, useCallback, Component, useEffect, useMemo, useReducer, useRef, Children, isValidElement, useLayoutEffect, useImperativeHandle, cloneElement, createElement, Suspense, lazy } from 'react';
4
- import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, mergeClasses, createLightTheme, createDarkTheme, createDOMRenderer, FluentProvider, RendererProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, useId, useToastController, Toast, ToastTitle, Toaster, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Link as Link$1, Caption1, Body1, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, Switch as Switch$1, Dropdown as Dropdown$1, Option, useMergedRefs, Input, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, useComboboxFilter, Combobox, MessageBarTitle, Popover as Popover$1, PopoverTrigger, PopoverSurface, Field, Textarea as Textarea$1, PresenceBadge, treeItemLevelToken, typographyStyles, FlatTree, FlatTreeItem, MenuDivider, TreeItemLayout, MenuItemCheckbox } from '@fluentui/react-components';
5
- import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, DocumentTextRegular, ArrowBidirectionalUpDownFilled, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, InfoRegular, WarningRegular, CheckmarkCircleRegular, DeleteFilled, LinkDismissRegular, LinkEditRegular, AddRegular, DeleteRegular, ArrowUploadRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, ChevronRightRegular, CubeTreeRegular, EngineRegular, WindowRegular, TextFieldRegular, PersonSquareRegular, TextTRegular, ImageRegular } from '@fluentui/react-icons';
4
+ import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, mergeClasses, createLightTheme, createDarkTheme, createDOMRenderer, FluentProvider, RendererProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, useId, useToastController, Toast, ToastTitle, Toaster, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Link as Link$1, Caption1, Body1, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, Switch as Switch$1, Dropdown as Dropdown$1, Option, useMergedRefs, Input, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, useComboboxFilter, Combobox, MessageBarTitle, Popover as Popover$1, PopoverTrigger, PopoverSurface, Field, Textarea as Textarea$1, PresenceBadge, treeItemLevelToken, typographyStyles, FlatTree, FlatTreeItem, MenuDivider, TreeItemLayout, MenuItemCheckbox, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch } from '@fluentui/react-components';
5
+ import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, DocumentTextRegular, ArrowBidirectionalUpDownFilled, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, InfoRegular, WarningRegular, CheckmarkCircleRegular, DeleteFilled, LinkDismissRegular, LinkEditRegular, AddRegular, DeleteRegular, ArrowUploadRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, ChevronRightRegular, CubeTreeRegular, EngineRegular, WindowRegular, TextFieldRegular, PersonSquareRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CameraRegular, LightbulbRegular, EyeRegular, MyLocationRegular, TextTRegular, ImageRegular } from '@fluentui/react-icons';
6
6
  import { DataStorage } from '@babylonjs/core/Misc/dataStorage.js';
7
- import { Logger } from '@babylonjs/core/Misc/logger.js';
8
7
  import { AsyncLock } from '@babylonjs/core/Misc/asyncLock.js';
9
8
  import { Deferred } from '@babylonjs/core/Misc/deferred.js';
10
9
  import { useResizeHandle } from '@fluentui-contrib/react-resize-handle';
11
10
  import { Collapse as Collapse$1, Fade } from '@fluentui/react-motion-components-preview';
12
- import { GetClassName } from '@babylonjs/core/Misc/typeStore.js';
13
- import { Clamp } from '@babylonjs/core/Maths/math.scalar.functions.js';
11
+ import { Logger } from '@babylonjs/core/Misc/logger.js';
14
12
  import { createRoot } from 'react-dom/client';
15
- import { getRenderingContexts, getRenderingContextKind, getMaterialFamily, getMaterialTextures, resizeEngine } from '@babylonjs/lite';
13
+ import { getRenderingContexts, getRenderingContextKind, quatToEulerXYZ, eulerToQuat, getMaterialFamily, setSubtreeVisible, removeFromScene, getMaterialTextures, setParent, resizeEngine } from '@babylonjs/lite';
16
14
  import { VirtualizerScrollView } from '@fluentui-contrib/react-virtualizer';
17
15
  import { UniqueIdGenerator } from '@babylonjs/core/Misc/uniqueIdGenerator.js';
18
16
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
@@ -263,7 +261,7 @@ const Button = forwardRef((props, ref) => {
263
261
  });
264
262
  Button.displayName = "Button";
265
263
 
266
- const useStyles$l = makeStyles({
264
+ const useStyles$n = makeStyles({
267
265
  root: {
268
266
  display: "flex",
269
267
  flexDirection: "column",
@@ -347,7 +345,7 @@ class ErrorBoundary extends Component {
347
345
  }
348
346
  }
349
347
  function ErrorFallback({ error, onRetry }) {
350
- const styles = useStyles$l();
348
+ const styles = useStyles$n();
351
349
  return (jsxs("div", { className: styles.root, children: [jsx(ErrorCircleRegular, { className: styles.icon }), jsx("div", { className: styles.title, children: "Something went wrong" }), jsx("div", { className: styles.message, children: "An error occurred in this component. You can try again or continue using other parts of the tool." }), jsx(Button, { label: "Try Again", appearance: "primary", onClick: onRetry }), error && jsx("div", { className: styles.details, children: error.message })] }));
352
350
  }
353
351
 
@@ -416,9 +414,32 @@ function useKeyState(key, options) {
416
414
  // Storage Helpers
417
415
  // ============================================================================
418
416
  const STORAGE_KEY_ROOT = "Babylon/Accordion";
417
+ const InMemoryStorage = new Map();
418
+ // eslint-disable-next-line no-console
419
+ const Warn = (message) => globalThis.console?.warn?.(message);
420
+ const ReadStoredString = (key) => {
421
+ try {
422
+ return globalThis.localStorage?.getItem(key) ?? InMemoryStorage.get(key) ?? "";
423
+ }
424
+ catch {
425
+ return InMemoryStorage.get(key) ?? "";
426
+ }
427
+ };
428
+ const WriteStoredString = (key, value) => {
429
+ try {
430
+ if (globalThis.localStorage) {
431
+ globalThis.localStorage.setItem(key, value);
432
+ return;
433
+ }
434
+ }
435
+ catch {
436
+ // Fall through to in-memory persistence when localStorage is unavailable.
437
+ }
438
+ InMemoryStorage.set(key, value);
439
+ };
419
440
  const ReadFromStorage = (path, initial) => {
420
441
  try {
421
- const stored = DataStorage.ReadString(`${STORAGE_KEY_ROOT}/${path}`, "");
442
+ const stored = ReadStoredString(`${STORAGE_KEY_ROOT}/${path}`);
422
443
  return stored ? JSON.parse(stored) : initial;
423
444
  }
424
445
  catch {
@@ -426,7 +447,7 @@ const ReadFromStorage = (path, initial) => {
426
447
  }
427
448
  };
428
449
  const WriteToStorage = (path, data) => {
429
- DataStorage.WriteString(`${STORAGE_KEY_ROOT}/${path}`, JSON.stringify(data));
450
+ WriteStoredString(`${STORAGE_KEY_ROOT}/${path}`, JSON.stringify(data));
430
451
  };
431
452
  // ============================================================================
432
453
  // Reducer
@@ -584,7 +605,7 @@ function useAccordionSectionItemState(props) {
584
605
  const prevItemIdRef = useRef(itemId);
585
606
  useEffect(() => {
586
607
  if (accordionCtx && prevItemIdRef.current !== itemId) {
587
- Logger.Warn(`Accordion: The uniqueId "${itemId}" in section "${sectionCtx?.sectionId}" has changed from "${prevItemIdRef.current}". ` +
608
+ Warn(`Accordion: The uniqueId "${itemId}" in section "${sectionCtx?.sectionId}" has changed from "${prevItemIdRef.current}". ` +
588
609
  `Each item must have a unique, stable ID for pin/hide persistence to work correctly.`);
589
610
  }
590
611
  prevItemIdRef.current = itemId;
@@ -596,7 +617,7 @@ function useAccordionSectionItemState(props) {
596
617
  }
597
618
  const { registeredItemIds } = accordionCtx;
598
619
  if (registeredItemIds.has(itemUniqueId)) {
599
- Logger.Warn(`Accordion: Duplicate uniqueId "${itemId}" detected in section "${sectionCtx?.sectionId}". ` +
620
+ Warn(`Accordion: Duplicate uniqueId "${itemId}" detected in section "${sectionCtx?.sectionId}". ` +
600
621
  `Each item must have a unique ID within its section for pin/hide persistence to work correctly.`);
601
622
  }
602
623
  registeredItemIds.set(itemUniqueId, itemLabel ?? itemId);
@@ -669,7 +690,7 @@ function useIsSectionEmpty(sectionId) {
669
690
  return hasItems;
670
691
  }
671
692
 
672
- const useStyles$k = makeStyles({
693
+ const useStyles$m = makeStyles({
673
694
  accordion: {
674
695
  display: "flex",
675
696
  flexDirection: "column",
@@ -761,7 +782,7 @@ const useStyles$k = makeStyles({
761
782
  */
762
783
  const AccordionMenuBar = () => {
763
784
  AccordionMenuBar.displayName = "AccordionMenuBar";
764
- const classes = useStyles$k();
785
+ const classes = useStyles$m();
765
786
  const accordionCtx = useContext(AccordionContext);
766
787
  if (!accordionCtx) {
767
788
  return null;
@@ -785,7 +806,7 @@ const AccordionMenuBar = () => {
785
806
  const AccordionSectionBlock = (props) => {
786
807
  AccordionSectionBlock.displayName = "AccordionSectionBlock";
787
808
  const { children, sectionId } = props;
788
- const classes = useStyles$k();
809
+ const classes = useStyles$m();
789
810
  const accordionCtx = useContext(AccordionContext);
790
811
  const { context: sectionContext, isEmpty } = useAccordionSectionBlockContext(props);
791
812
  if (accordionCtx) {
@@ -805,7 +826,7 @@ const AccordionSectionBlock = (props) => {
805
826
  const AccordionSectionItem = (props) => {
806
827
  AccordionSectionItem.displayName = "AccordionSectionItem";
807
828
  const { children, staticItem } = props;
808
- const classes = useStyles$k();
829
+ const classes = useStyles$m();
809
830
  const accordionCtx = useContext(AccordionContext);
810
831
  const itemState = useAccordionSectionItemState(props);
811
832
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -845,7 +866,7 @@ const AccordionSectionItem = (props) => {
845
866
  */
846
867
  const AccordionPinnedContainer = () => {
847
868
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
848
- const classes = useStyles$k();
869
+ const classes = useStyles$m();
849
870
  const accordionCtx = useContext(AccordionContext);
850
871
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
851
872
  };
@@ -856,7 +877,7 @@ const AccordionPinnedContainer = () => {
856
877
  */
857
878
  const AccordionSearchBox = () => {
858
879
  AccordionSearchBox.displayName = "AccordionSearchBox";
859
- const classes = useStyles$k();
880
+ const classes = useStyles$m();
860
881
  const accordionCtx = useContext(AccordionContext);
861
882
  if (!accordionCtx?.features.search) {
862
883
  return null;
@@ -872,7 +893,7 @@ const AccordionSearchBox = () => {
872
893
  */
873
894
  const AccordionSection = (props) => {
874
895
  AccordionSection.displayName = "AccordionSection";
875
- const classes = useStyles$k();
896
+ const classes = useStyles$m();
876
897
  return jsx("div", { className: classes.panelDiv, children: props.children });
877
898
  };
878
899
  const StringAccordion = Accordion$1;
@@ -880,7 +901,7 @@ const Accordion = forwardRef((props, ref) => {
880
901
  Accordion.displayName = "Accordion";
881
902
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
882
903
  const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
883
- const classes = useStyles$k();
904
+ const classes = useStyles$m();
884
905
  const { size } = useContext(ToolContext);
885
906
  const accordionCtx = useAccordionContext(props);
886
907
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1106,7 +1127,7 @@ const UXContextProvider = (props) => {
1106
1127
  function AsReadonlyArray(array) {
1107
1128
  return array;
1108
1129
  }
1109
- const useStyles$j = makeStyles({
1130
+ const useStyles$l = makeStyles({
1110
1131
  rootDiv: {
1111
1132
  flex: 1,
1112
1133
  overflow: "hidden",
@@ -1121,7 +1142,7 @@ const useStyles$j = makeStyles({
1121
1142
  * @returns The extensible accordion component.
1122
1143
  */
1123
1144
  function ExtensibleAccordion(props) {
1124
- const classes = useStyles$j();
1145
+ const classes = useStyles$l();
1125
1146
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
1126
1147
  const defaultSections = useMemo(() => {
1127
1148
  const defaultSections = [];
@@ -1246,7 +1267,7 @@ function ExtensibleAccordion(props) {
1246
1267
  })] }) })) }));
1247
1268
  }
1248
1269
 
1249
- const useStyles$i = makeStyles({
1270
+ const useStyles$k = makeStyles({
1250
1271
  paneRootDiv: {
1251
1272
  display: "flex",
1252
1273
  flex: 1,
@@ -1259,7 +1280,7 @@ const useStyles$i = makeStyles({
1259
1280
  */
1260
1281
  const SidePaneContainer = forwardRef((props, ref) => {
1261
1282
  const { className, ...rest } = props;
1262
- const classes = useStyles$i();
1283
+ const classes = useStyles$k();
1263
1284
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
1264
1285
  });
1265
1286
 
@@ -1530,7 +1551,7 @@ function useTheme(invert = false) {
1530
1551
  }
1531
1552
 
1532
1553
  // Fluent doesn't apply styling to scrollbars by default, so provide our own reasonable default.
1533
- const useStyles$h = makeStyles({
1554
+ const useStyles$j = makeStyles({
1534
1555
  root: {
1535
1556
  scrollbarColor: `${tokens.colorNeutralForeground3} ${tokens.colorTransparentBackground}`,
1536
1557
  },
@@ -1551,7 +1572,7 @@ const Theme = (props) => {
1551
1572
  // break any UI within the portal. Therefore, default to false.
1552
1573
  const { invert = false, applyStylesToPortals = false, className, targetDocument: explicitTargetDocument, ...rest } = props;
1553
1574
  const theme = useTheme(invert);
1554
- const classes = useStyles$h();
1575
+ const classes = useStyles$j();
1555
1576
  // Resolve the target document from the explicit prop or fall back to the ambient Fluent context.
1556
1577
  // This makes nested <Theme> components automatically inherit cross-window targeting from a
1557
1578
  // top-level <Theme targetDocument={popupDocument}> wrapper.
@@ -1573,7 +1594,7 @@ const Theme = (props) => {
1573
1594
  return fluent;
1574
1595
  };
1575
1596
 
1576
- const useStyles$g = makeStyles({
1597
+ const useStyles$i = makeStyles({
1577
1598
  extensionTeachingPopover: {
1578
1599
  maxWidth: "320px",
1579
1600
  },
@@ -1584,7 +1605,7 @@ const useStyles$g = makeStyles({
1584
1605
  * @returns The teaching moment popover.
1585
1606
  */
1586
1607
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
1587
- const classes = useStyles$g();
1608
+ const classes = useStyles$i();
1588
1609
  return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
1589
1610
  };
1590
1611
 
@@ -1953,6 +1974,10 @@ const UseDegreesSettingDescriptor = {
1953
1974
  key: "UseDegrees",
1954
1975
  defaultValue: true,
1955
1976
  };
1977
+ const UseEulerSettingDescriptor = {
1978
+ key: "UseEuler",
1979
+ defaultValue: true,
1980
+ };
1956
1981
 
1957
1982
  const RadiansToDegrees = 180 / Math.PI;
1958
1983
  function WrapAngle(angle) {
@@ -2416,7 +2441,7 @@ const RootComponentServiceIdentity = Symbol("RootComponent");
2416
2441
  * The unique identity symbol for the shell service.
2417
2442
  */
2418
2443
  const ShellServiceIdentity = Symbol("ShellService");
2419
- const useStyles$f = makeStyles({
2444
+ const useStyles$h = makeStyles({
2420
2445
  mainView: {
2421
2446
  flex: 1,
2422
2447
  display: "flex",
@@ -2638,14 +2663,14 @@ const DockMenu = (props) => {
2638
2663
  };
2639
2664
  const PaneHeader = (props) => {
2640
2665
  const { id, title, dockOptions } = props;
2641
- const classes = useStyles$f();
2666
+ const classes = useStyles$h();
2642
2667
  return (jsxs("div", { className: classes.paneHeaderDiv, children: [props.icon && (jsx("div", { className: classes.paneHeaderIcon, children: jsx(props.icon, {}) })), jsx(Subtitle2Stronger, { className: mergeClasses(classes.paneHeaderText, !props.icon && classes.paneHeaderTextNoIcon), children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }));
2643
2668
  };
2644
2669
  // This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
2645
2670
  const ToolbarItem = (props) => {
2646
2671
  // eslint-disable-next-line @typescript-eslint/naming-convention
2647
2672
  const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
2648
- const classes = useStyles$f();
2673
+ const classes = useStyles$h();
2649
2674
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
2650
2675
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
2651
2676
  const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
@@ -2655,7 +2680,7 @@ const ToolbarItem = (props) => {
2655
2680
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
2656
2681
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
2657
2682
  const Toolbar = ({ location, components }) => {
2658
- const classes = useStyles$f();
2683
+ const classes = useStyles$h();
2659
2684
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
2660
2685
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
2661
2686
  return (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) })] }));
@@ -2665,7 +2690,7 @@ const SidePaneTab = (props) => {
2665
2690
  const { location, id, isSelected, isFirst, isLast, dockOptions,
2666
2691
  // eslint-disable-next-line @typescript-eslint/naming-convention
2667
2692
  icon: Icon, title, } = props;
2668
- const classes = useStyles$f();
2693
+ const classes = useStyles$h();
2669
2694
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
2670
2695
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
2671
2696
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
@@ -2677,7 +2702,7 @@ const SidePaneTab = (props) => {
2677
2702
  // In "compact" mode, the tab list is integrated into the pane itself.
2678
2703
  // In "full" mode, the returned tab list is later injected into the toolbar.
2679
2704
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
2680
- const classes = useStyles$f();
2705
+ const classes = useStyles$h();
2681
2706
  const [topSelectedTab, setTopSelectedTab] = useState();
2682
2707
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
2683
2708
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -2919,7 +2944,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
2919
2944
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
2920
2945
  };
2921
2946
  const rootComponent = () => {
2922
- const classes = useStyles$f();
2947
+ const classes = useStyles$h();
2923
2948
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
2924
2949
  // This function returns a promise that resolves after the dock change takes effect so that
2925
2950
  // we can then select the re-docked pane.
@@ -3202,7 +3227,11 @@ function copyCommandToClipboard(strCommand) {
3202
3227
  // babylonNamespace is either "" (ES6) or "BABYLON."
3203
3228
  // eslint-disable-next-line @typescript-eslint/naming-convention
3204
3229
  function getClassNameWithNamespace(obj) {
3205
- let className = GetClassName(obj);
3230
+ if ((typeof obj !== "object" && typeof obj !== "function") || obj === null) {
3231
+ return { className: "Unknown", babylonNamespace: "" };
3232
+ }
3233
+ const candidate = obj;
3234
+ let className = candidate.getClassName?.() ?? candidate.constructor?.name ?? "Unknown";
3206
3235
  if (className.includes("BABYLON.")) {
3207
3236
  className = className.split("BABYLON.")[1];
3208
3237
  }
@@ -3217,7 +3246,7 @@ const Link = forwardRef((props, ref) => {
3217
3246
  });
3218
3247
  Link.displayName = "Link";
3219
3248
 
3220
- const useStyles$e = makeStyles({
3249
+ const useStyles$g = makeStyles({
3221
3250
  button: {
3222
3251
  display: "flex",
3223
3252
  alignItems: "center",
@@ -3235,7 +3264,7 @@ const ToggleButton = (props) => {
3235
3264
  ToggleButton.displayName = "ToggleButton";
3236
3265
  const { value, onChange, title, appearance = "subtle", ariaLabel } = props;
3237
3266
  const { size } = useContext(ToolContext);
3238
- const classes = useStyles$e();
3267
+ const classes = useStyles$g();
3239
3268
  const [checked, setChecked] = useState(value);
3240
3269
  const toggle = useCallback(() => {
3241
3270
  setChecked((prevChecked) => {
@@ -3558,13 +3587,13 @@ function useImpulse() {
3558
3587
  return [value, pulse];
3559
3588
  }
3560
3589
 
3561
- const useStyles$d = makeStyles({
3590
+ const useStyles$f = makeStyles({
3562
3591
  placeholderDiv: {
3563
3592
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
3564
3593
  },
3565
3594
  });
3566
3595
  const PropertiesPane = (props) => {
3567
- const classes = useStyles$d();
3596
+ const classes = useStyles$f();
3568
3597
  const entity = props.context;
3569
3598
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
3570
3599
  };
@@ -3694,7 +3723,7 @@ const Dropdown = (props) => {
3694
3723
  const NumberDropdown = Dropdown;
3695
3724
  const StringDropdown = Dropdown;
3696
3725
 
3697
- const useStyles$c = makeStyles({
3726
+ const useStyles$e = makeStyles({
3698
3727
  dropdown: {
3699
3728
  ...UniformWidthStyling,
3700
3729
  },
@@ -3706,7 +3735,7 @@ const useStyles$c = makeStyles({
3706
3735
  */
3707
3736
  const DropdownPropertyLine = forwardRef((props, ref) => {
3708
3737
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
3709
- const classes = useStyles$c();
3738
+ const classes = useStyles$e();
3710
3739
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
3711
3740
  });
3712
3741
  /**
@@ -3753,7 +3782,7 @@ function EvaluateExpression(rawValue) {
3753
3782
  return NaN;
3754
3783
  }
3755
3784
  }
3756
- const useStyles$b = makeStyles({
3785
+ const useStyles$d = makeStyles({
3757
3786
  icon: {
3758
3787
  "&:hover": {
3759
3788
  color: tokens.colorBrandForeground1,
@@ -3767,7 +3796,7 @@ const useStyles$b = makeStyles({
3767
3796
  const SpinButton = forwardRef((props, ref) => {
3768
3797
  SpinButton.displayName = "SpinButton2";
3769
3798
  const inputClasses = useInputStyles$1();
3770
- const classes = useStyles$b();
3799
+ const classes = useStyles$d();
3771
3800
  const { size } = useContext(ToolContext);
3772
3801
  const { min, max } = props;
3773
3802
  const baseStep = props.step ?? 1;
@@ -3815,7 +3844,7 @@ const SpinButton = forwardRef((props, ref) => {
3815
3844
  return !invalid;
3816
3845
  }, [min, max, props.validator, props.forceInt]);
3817
3846
  // Constrain a value to the valid range by clamping to [min, max].
3818
- const constrainValue = useCallback((v) => Clamp(v, min ?? -Infinity, max ?? Infinity), [min, max]);
3847
+ const constrainValue = useCallback((v) => Math.min(max ?? Infinity, Math.max(min ?? -Infinity, v)), [min, max]);
3819
3848
  const tryCommitValue = useCallback((currVal) => {
3820
3849
  if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
3821
3850
  lastCommittedValue.current = currVal;
@@ -4083,7 +4112,7 @@ const SyncedSliderInput = (props) => {
4083
4112
  return (jsxs("div", { className: mergeClasses(classes.container, props.className), children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (jsx(Slider, { className: getSliderClassName(), value: value, onChange: handleSliderChange, min: props.min, max: props.max, step: props.step, disabled: props.disabled, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: useCompactSizing ? classes.compactSpinButton : classes.spinButton, inputClassName: useCompactSizing ? classes.compactSpinButtonInput : classes.spinButtonInput, value: value, onChange: handleInputChange, step: props.step, disabled: props.disabled, disableDragButton: true })] })] }));
4084
4113
  };
4085
4114
 
4086
- const useStyles$a = makeStyles({
4115
+ const useStyles$c = makeStyles({
4087
4116
  uniformWidth: {
4088
4117
  ...UniformWidthStyling,
4089
4118
  },
@@ -4095,7 +4124,7 @@ const useStyles$a = makeStyles({
4095
4124
  */
4096
4125
  const SyncedSliderPropertyLine = forwardRef((props, ref) => {
4097
4126
  SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
4098
- const classes = useStyles$a();
4127
+ const classes = useStyles$c();
4099
4128
  const { label, description, ...sliderProps } = props;
4100
4129
  return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps, className: mergeClasses(classes.uniformWidth, props.className) }) }));
4101
4130
  });
@@ -4896,7 +4925,7 @@ function useExtensionManager() {
4896
4925
  return useContext(ExtensionManagerContext)?.extensionManager;
4897
4926
  }
4898
4927
 
4899
- const useStyles$9 = makeStyles({
4928
+ const useStyles$b = makeStyles({
4900
4929
  themeButton: {
4901
4930
  margin: 0,
4902
4931
  },
@@ -4915,7 +4944,7 @@ const ThemeSelectorServiceDefinition = {
4915
4944
  teachingMoment: false,
4916
4945
  order: -300,
4917
4946
  component: () => {
4918
- const classes = useStyles$9();
4947
+ const classes = useStyles$b();
4919
4948
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
4920
4949
  const onSelectedThemeChange = useCallback((e, data) => {
4921
4950
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -4932,7 +4961,7 @@ const ThemeSelectorServiceDefinition = {
4932
4961
  },
4933
4962
  };
4934
4963
 
4935
- const useStyles$8 = makeStyles({
4964
+ const useStyles$a = makeStyles({
4936
4965
  app: {
4937
4966
  colorScheme: "light dark",
4938
4967
  flexGrow: 1,
@@ -4996,7 +5025,7 @@ function MakeModularTool(options) {
4996
5025
  // This deferred resolves once the React effect cleanup (which disposes the ServiceContainer) is complete.
4997
5026
  const disposeDeferred = new Deferred();
4998
5027
  const modularToolRootComponent = () => {
4999
- const classes = useStyles$8();
5028
+ const classes = useStyles$a();
5000
5029
  const [requiredExtensions, setRequiredExtensions] = useState();
5001
5030
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
5002
5031
  const [extensionInstallError, setExtensionInstallError] = useState();
@@ -5108,7 +5137,7 @@ function MakeModularTool(options) {
5108
5137
  }
5109
5138
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
5110
5139
  if (extensionFeeds.length > 0) {
5111
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-xgITNk9y.js');
5140
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-BP2ToO_O.js');
5112
5141
  serviceContainer.addService(ExtensionListServiceDefinition);
5113
5142
  }
5114
5143
  // Register all external services (that make up a unique tool).
@@ -5508,7 +5537,7 @@ const Checkbox = (props) => {
5508
5537
  return jsx(Checkbox$1, { checked: checked, onChange: onChange, indicator: { className: classes.indicator } });
5509
5538
  };
5510
5539
 
5511
- const useStyles$7 = makeStyles({
5540
+ const useStyles$9 = makeStyles({
5512
5541
  root: {
5513
5542
  display: "grid",
5514
5543
  gridTemplateRows: "repeat(1fr)",
@@ -5538,7 +5567,7 @@ const useStyles$7 = makeStyles({
5538
5567
  const ComboBox = forwardRef((props, ref) => {
5539
5568
  ComboBox.displayName = "ComboBox";
5540
5569
  const comboId = useId();
5541
- const styles = useStyles$7();
5570
+ const styles = useStyles$9();
5542
5571
  const { size } = useContext(ToolContext);
5543
5572
  // Find the label for the current value
5544
5573
  const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
@@ -5615,7 +5644,7 @@ const DraggableLine = (props) => {
5615
5644
  }, children: [props.label, props.onDelete && jsx(DeleteFilled, { className: classes.icon, onClick: props.onDelete })] }));
5616
5645
  };
5617
5646
 
5618
- const useStyles$6 = makeStyles({
5647
+ const useStyles$8 = makeStyles({
5619
5648
  linkDiv: {
5620
5649
  display: "flex",
5621
5650
  flexDirection: "row",
@@ -5640,7 +5669,7 @@ const useStyles$6 = makeStyles({
5640
5669
  function EntitySelector(props) {
5641
5670
  const { value, onLink, getEntities, getName, filter, defaultValue } = props;
5642
5671
  const onChange = props.onChange;
5643
- const classes = useStyles$6();
5672
+ const classes = useStyles$8();
5644
5673
  const comboBoxRef = useRef(null);
5645
5674
  // Build options with uniqueId as key
5646
5675
  const options = useMemo(() => {
@@ -5767,7 +5796,7 @@ const MessageBar = (props) => {
5767
5796
  return (jsx(AccordionSectionItem, { uniqueId: title ?? message, staticItem: staticItem ?? true, children: jsx("div", { className: classes.container, children: jsx(MessageBar$1, { intent: intent, layout: "multiline", children: jsxs(MessageBarBody, { children: [title && jsx(MessageBarTitle, { children: title }), message, docLink && (jsxs(Fragment, { children: [" - ", jsx(Link, { url: docLink, value: "Learn More" })] }))] }) }) }) }));
5768
5797
  };
5769
5798
 
5770
- const useStyles$5 = makeStyles({
5799
+ const useStyles$7 = makeStyles({
5771
5800
  surface: {
5772
5801
  maxWidth: "400px",
5773
5802
  },
@@ -5782,7 +5811,7 @@ const useStyles$5 = makeStyles({
5782
5811
  const Popover = forwardRef((props, ref) => {
5783
5812
  const { children, open: controlledOpen, onOpenChange, positioning, surfaceClassName } = props;
5784
5813
  const [internalOpen, setInternalOpen] = useState(false);
5785
- const classes = useStyles$5();
5814
+ const classes = useStyles$7();
5786
5815
  const isControlled = controlledOpen !== undefined;
5787
5816
  const popoverOpen = isControlled ? controlledOpen : internalOpen;
5788
5817
  const handleOpenChange = (_, data) => {
@@ -5828,14 +5857,14 @@ const PositionedPopover = (props) => {
5828
5857
  } }) }), jsx(PopoverSurface, { children: props.children })] }));
5829
5858
  };
5830
5859
 
5831
- const useStyles$4 = makeStyles({
5860
+ const useStyles$6 = makeStyles({
5832
5861
  search: {
5833
5862
  minWidth: "50px",
5834
5863
  },
5835
5864
  });
5836
5865
  const SearchBar = forwardRef((props, ref) => {
5837
5866
  SearchBar.displayName = "SearchBar";
5838
- const classes = useStyles$4();
5867
+ const classes = useStyles$6();
5839
5868
  const onChange = (_, data) => {
5840
5869
  props.onChange(data.value);
5841
5870
  };
@@ -6051,7 +6080,7 @@ const FileUploadLine = ({ onClick, label, accept, ...buttonProps }) => {
6051
6080
  return (jsx(LineContainer, { uniqueId: `${label}_upload`, label: label, children: jsx(UploadButton, { onUpload: onClick, accept: accept, label: label, ...buttonProps }) }));
6052
6081
  };
6053
6082
 
6054
- const useStyles$3 = makeStyles({
6083
+ const useStyles$5 = makeStyles({
6055
6084
  rootDiv: {
6056
6085
  flex: 1,
6057
6086
  overflow: "hidden",
@@ -6071,7 +6100,7 @@ const useStyles$3 = makeStyles({
6071
6100
  },
6072
6101
  });
6073
6102
  const Pane = (props) => {
6074
- const classes = useStyles$3();
6103
+ const classes = useStyles$5();
6075
6104
  return (jsxs("div", { className: classes.rootDiv, children: [jsxs("div", { className: classes.header, children: [props.icon ? (jsx(props.icon, { className: classes.icon })) : (jsx("img", { className: classes.icon, id: "logo", src: "https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" })), jsx(Body1Strong, { id: "title", children: props.title })] }), props.children] }));
6076
6105
  };
6077
6106
 
@@ -6155,7 +6184,7 @@ const HexPropertyLine = (props) => {
6155
6184
  return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, validator: MaskValidatorFn, value: hexVal, onChange: onStrValChange, validateOnlyOnBlur: true }) }));
6156
6185
  };
6157
6186
 
6158
- const useStyles$2 = makeStyles({
6187
+ const useStyles$4 = makeStyles({
6159
6188
  uniformWidth: {
6160
6189
  ...UniformWidthStyling,
6161
6190
  },
@@ -6167,7 +6196,7 @@ const useStyles$2 = makeStyles({
6167
6196
  */
6168
6197
  const TextInputPropertyLine = (props) => {
6169
6198
  TextInputPropertyLine.displayName = "TextInputPropertyLine";
6170
- const classes = useStyles$2();
6199
+ const classes = useStyles$4();
6171
6200
  return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
6172
6201
  };
6173
6202
  /**
@@ -6178,7 +6207,7 @@ const TextInputPropertyLine = (props) => {
6178
6207
  */
6179
6208
  const NumberInputPropertyLine = (props) => {
6180
6209
  NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
6181
- const classes = useStyles$2();
6210
+ const classes = useStyles$4();
6182
6211
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
6183
6212
  };
6184
6213
 
@@ -6192,14 +6221,14 @@ const LinkPropertyLine = (props) => {
6192
6221
  return (jsx(PropertyLine, { ...props, children: jsx(Link, { ...props }) }));
6193
6222
  };
6194
6223
 
6195
- const useStyles$1 = makeStyles({
6224
+ const useStyles$3 = makeStyles({
6196
6225
  uniformWidth: {
6197
6226
  ...UniformWidthStyling,
6198
6227
  },
6199
6228
  });
6200
6229
  const SpinButtonPropertyLine = (props) => {
6201
6230
  SpinButtonPropertyLine.displayName = "SpinButtonPropertyLine";
6202
- const classes = useStyles$1();
6231
+ const classes = useStyles$3();
6203
6232
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
6204
6233
  };
6205
6234
 
@@ -6838,7 +6867,7 @@ function useExplorerDragDrop(options) {
6838
6867
  };
6839
6868
  }
6840
6869
 
6841
- const useStyles = makeStyles({
6870
+ const useStyles$2 = makeStyles({
6842
6871
  rootDiv: {
6843
6872
  flex: 1,
6844
6873
  overflow: "hidden",
@@ -6978,7 +7007,7 @@ function useBranchIconInteraction(enabled) {
6978
7007
  }
6979
7008
  const ExplorerTreeItemLayout = (props) => {
6980
7009
  const { isBranch, isOpen, icon, showChevron, select, ...layoutProps } = props;
6981
- const classes = useStyles();
7010
+ const classes = useStyles$2();
6982
7011
  const { dir } = useFluent();
6983
7012
  const hasIcon = !!icon;
6984
7013
  const chevron = (jsx("div", { className: mergeClasses(classes.leadingChevron, isOpen ? classes.leadingChevronOpen : dir === "rtl" && classes.leadingChevronClosedRtl), children: jsx(ChevronRightRegular, {}) }));
@@ -7114,7 +7143,7 @@ function useNodeDisplayInfo(node) {
7114
7143
  }
7115
7144
  const RootTreeItem = (props) => {
7116
7145
  const { node, isSelected, isOpen, select, isFiltering, expandAll, collapseAll } = props;
7117
- const classes = useStyles();
7146
+ const classes = useStyles$2();
7118
7147
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
7119
7148
  const treeItemLayoutClass = mergeClasses(classes.rootTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
7120
7149
  const displayInfo = useNodeDisplayInfo(node);
@@ -7141,7 +7170,7 @@ const RootTreeItem = (props) => {
7141
7170
  };
7142
7171
  const GroupTreeItem = (props) => {
7143
7172
  const { node, isFiltering, commandProviders, expandAll, collapseAll, isSelected, isOpen, select, isDropTarget, ...dropProps } = props;
7144
- const classes = useStyles();
7173
+ const classes = useStyles$2();
7145
7174
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
7146
7175
  const displayInfo = useNodeDisplayInfo(node);
7147
7176
  const name = useObservableState(() => displayInfo.name, displayInfo.onChange);
@@ -7165,7 +7194,7 @@ const GroupTreeItem = (props) => {
7165
7194
  };
7166
7195
  const ItemTreeItem = (props) => {
7167
7196
  const { node, entity, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, isOpen, ...dragProps } = props;
7168
- const classes = useStyles();
7197
+ const classes = useStyles$2();
7169
7198
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
7170
7199
  const hasChildren = node.children.length > 0;
7171
7200
  const isBranch = !isFiltering && hasChildren;
@@ -7287,7 +7316,7 @@ const ItemTreeItem = (props) => {
7287
7316
  * @returns The explorer tree component.
7288
7317
  */
7289
7318
  const Explorer = (props) => {
7290
- const classes = useStyles();
7319
+ const classes = useStyles$2();
7291
7320
  const { getNodes, itemCommandProviders, groupCommandProviders, selectedEntity = null, ariaLabel = "Scene Explorer Tree" } = props;
7292
7321
  const [treeVersion, setTreeVersion] = useState(0);
7293
7322
  const tree = useMemo(() => BuildExplorerTree(getNodes()), [getNodes, treeVersion]);
@@ -7944,11 +7973,11 @@ function GenerateCopyString(value) {
7944
7973
  }
7945
7974
  // Color3, Color4 — HEX is a direct 0-1→0-255 conversion of the stored values (property-space)
7946
7975
  if (className === "Color3") {
7947
- const hexString = ColorToHex(obj.r, obj.g, obj.b, 1);
7976
+ const hexString = ColorToHex$1(obj.r, obj.g, obj.b, 1);
7948
7977
  return `new ${babylonNamespace}Color3(${obj.r}, ${obj.g}, ${obj.b}) // (HEX: ${hexString})`;
7949
7978
  }
7950
7979
  if (className === "Color4") {
7951
- const hexString = ColorToHex(obj.r, obj.g, obj.b, obj.a);
7980
+ const hexString = ColorToHex$1(obj.r, obj.g, obj.b, obj.a);
7952
7981
  return `new ${babylonNamespace}Color4(${obj.r}, ${obj.g}, ${obj.b}, ${obj.a}) // (HEX: ${hexString})`;
7953
7982
  }
7954
7983
  // Matrix - output as array
@@ -7981,7 +8010,7 @@ function GenerateCopyString(value) {
7981
8010
  * @param a - Alpha component (0-1)
7982
8011
  * @returns Hex string in format #RRGGBBAA
7983
8012
  */
7984
- function ColorToHex(r, g, b, a) {
8013
+ function ColorToHex$1(r, g, b, a) {
7985
8014
  const toByte = (c) => Math.round(Math.min(255, Math.max(0, c * 255)));
7986
8015
  const rHex = toByte(r).toString(16).padStart(2, "0").toUpperCase();
7987
8016
  const gHex = toByte(g).toString(16).padStart(2, "0").toUpperCase();
@@ -8080,6 +8109,22 @@ function ComputedProperty(props) {
8080
8109
  const value = useWatchedValue(target, getValue);
8081
8110
  return jsx(Component, { ...{ ...rest, value } });
8082
8111
  }
8112
+ /**
8113
+ * Renders an editable property-line component for a value derived from a target.
8114
+ *
8115
+ * This is useful for method-backed values, readonly object references, or values whose UI
8116
+ * representation differs from their stored representation. The getter follows the Preferred
8117
+ * Watch Mode, while the setter owns the runtime-specific write-back.
8118
+ * @param props The target, derived getter, write-back function, and property-line component props.
8119
+ * @returns The editable derived property-line component.
8120
+ */
8121
+ function DerivedProperty(props) {
8122
+ const {
8123
+ // eslint-disable-next-line @typescript-eslint/naming-convention
8124
+ component: Component, target, getValue, setValue, ...rest } = props;
8125
+ const value = useWatchedValue(target, getValue);
8126
+ return jsx(Component, { ...{ ...rest, value, onChange: (changedValue) => setValue(target, changedValue) } });
8127
+ }
8083
8128
  function GetOnCopyString(value, propertyPath) {
8084
8129
  const valueStr = GenerateCopyString(value);
8085
8130
  return `globalThis.debugNode.${propertyPath} = ${valueStr};`;
@@ -8158,103 +8203,731 @@ const EnginePropertiesServiceDefinition = {
8158
8203
  },
8159
8204
  };
8160
8205
 
8161
- function IsMaterial(entity) {
8162
- return typeof entity === "object" && entity !== null && getMaterialFamily(entity) !== undefined;
8206
+ function GetSceneContexts(engine) {
8207
+ return engine.surfaces.flatMap((surface) => getRenderingContexts(surface).filter((context) => getRenderingContextKind(context) === "scene"));
8163
8208
  }
8164
- const MaterialProperties = (props) => {
8165
- const { material } = props;
8166
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: material, propertyKey: "name", ignoreNullable: true, defaultValue: "" }), jsx(TextPropertyLine, { label: "Family", value: getMaterialFamily(material) ?? "Unknown" })] }));
8209
+ function IsSceneNode(entity) {
8210
+ return (typeof entity === "object" &&
8211
+ entity !== null &&
8212
+ "name" in entity &&
8213
+ "children" in entity &&
8214
+ "position" in entity &&
8215
+ "rotationQuaternion" in entity &&
8216
+ "scaling" in entity &&
8217
+ "parent" in entity);
8218
+ }
8219
+ function IsMesh(entity) {
8220
+ return IsSceneNode(entity) && "material" in entity && "receiveShadows" in entity;
8221
+ }
8222
+ function IsCamera(entity) {
8223
+ return typeof entity === "object" && entity !== null && "fov" in entity && "nearPlane" in entity && "farPlane" in entity && "children" in entity && "worldMatrix" in entity;
8224
+ }
8225
+ function IsArcRotateCamera(entity) {
8226
+ return IsCamera(entity) && "alpha" in entity && "beta" in entity && "radius" in entity && "target" in entity;
8227
+ }
8228
+ function IsFreeCamera(entity) {
8229
+ return IsCamera(entity) && "position" in entity && "target" in entity && "speed" in entity;
8230
+ }
8231
+ function IsLight(entity) {
8232
+ return typeof entity === "object" && entity !== null && "lightType" in entity && "children" in entity && "parent" in entity && "worldMatrix" in entity;
8233
+ }
8234
+ function IsDirectionalLight(entity) {
8235
+ return IsLight(entity) && entity.lightType === "directional";
8236
+ }
8237
+ function IsPointLight(entity) {
8238
+ return IsLight(entity) && entity.lightType === "point";
8239
+ }
8240
+ function IsSpotLight(entity) {
8241
+ return IsLight(entity) && entity.lightType === "spot";
8242
+ }
8243
+ function IsHemisphericLight(entity) {
8244
+ return IsLight(entity) && entity.lightType === "hemispheric";
8245
+ }
8246
+ function GetSceneNodes(scene) {
8247
+ const nodes = new Set();
8248
+ const addNode = (node) => {
8249
+ if (nodes.has(node)) {
8250
+ return;
8251
+ }
8252
+ nodes.add(node);
8253
+ for (const child of node.children) {
8254
+ addNode(child);
8255
+ }
8256
+ };
8257
+ for (const mesh of scene.meshes) {
8258
+ let root = mesh;
8259
+ while (IsSceneNode(root.parent)) {
8260
+ root = root.parent;
8261
+ }
8262
+ addNode(root);
8263
+ }
8264
+ return [...nodes];
8265
+ }
8266
+ function GetSceneNodeRoots(scene) {
8267
+ const nodes = GetSceneNodes(scene);
8268
+ const nodeSet = new Set(nodes);
8269
+ return nodes.filter((node) => !IsSceneNode(node.parent) || !nodeSet.has(node.parent));
8270
+ }
8271
+ function IsSceneNodeDescendantOf(node, ancestor) {
8272
+ let parent = node.parent;
8273
+ while (typeof parent === "object" && parent !== null) {
8274
+ if (parent === ancestor) {
8275
+ return true;
8276
+ }
8277
+ if (!IsSceneNode(parent)) {
8278
+ return false;
8279
+ }
8280
+ parent = parent.parent;
8281
+ }
8282
+ return false;
8283
+ }
8284
+ function SetVector3Value(target, value) {
8285
+ const x = "x" in value ? value.x : value[0];
8286
+ const y = "x" in value ? value.y : value[1];
8287
+ const z = "x" in value ? value.z : value[2];
8288
+ const observableTarget = target;
8289
+ if (typeof observableTarget.set === "function") {
8290
+ observableTarget.set(x, y, z);
8291
+ }
8292
+ else {
8293
+ target.x = x;
8294
+ target.y = y;
8295
+ target.z = z;
8296
+ }
8297
+ }
8298
+ function SetQuaternionValue(target, value) {
8299
+ const x = "x" in value ? value.x : value[0];
8300
+ const y = "x" in value ? value.y : value[1];
8301
+ const z = "x" in value ? value.z : value[2];
8302
+ const w = "x" in value ? value.w : value[3];
8303
+ const observableTarget = target;
8304
+ if (typeof observableTarget.set === "function") {
8305
+ observableTarget.set(x, y, z, w);
8306
+ }
8307
+ else {
8308
+ target.x = x;
8309
+ target.y = y;
8310
+ target.z = z;
8311
+ target.w = w;
8312
+ }
8313
+ }
8314
+ function GetLightDisplayName(light, index) {
8315
+ const typeName = `${light.lightType.charAt(0).toUpperCase()}${light.lightType.slice(1)}`;
8316
+ return `${typeName} Light ${index + 1}`;
8317
+ }
8318
+ function GetShadowGeneratorDisplayName(scene, shadowGenerator) {
8319
+ const lightIndex = scene.lights.findIndex((light) => light.shadowGenerator === shadowGenerator);
8320
+ return lightIndex === -1 ? `Shadow Generator ${scene.shadowGenerators.indexOf(shadowGenerator) + 1}` : `${GetLightDisplayName(scene.lights[lightIndex], lightIndex)} Shadows`;
8321
+ }
8322
+
8323
+ /**
8324
+ * Runtime-neutral controlled editor for two-, three-, and four-component numeric values.
8325
+ * @param props The value, change callback, component adapter, and property-line presentation.
8326
+ * @returns The tensor property line.
8327
+ */
8328
+ const ControlledTensorPropertyLine = (props) => {
8329
+ const { adapter, min, max, unit, step, precision, valueConverter } = props;
8330
+ const converted = (value) => (valueConverter ? valueConverter.from(value) : value);
8331
+ const formatted = (value) => converted(value).toFixed(precision ?? (step !== undefined ? Math.max(0, CalculatePrecision(step)) : 2));
8332
+ const [value, setValue] = useState(props.value);
8333
+ useEffect(() => {
8334
+ setValue(props.value);
8335
+ }, [props.value, props.expandedContent]);
8336
+ const onComponentChange = (componentValue, component) => {
8337
+ const storedValue = valueConverter ? valueConverter.to(componentValue) : componentValue;
8338
+ const newValue = adapter.withComponent(value, component, storedValue);
8339
+ setValue(newValue);
8340
+ props.onChange(newValue);
8341
+ };
8342
+ const summary = adapter.components.map((component) => formatted(adapter.getComponent(props.value, component))).join(", ");
8343
+ return (jsx(PropertyLine, { ...props, expandedContent: jsxs(Fragment, { children: [props.expandedContent, jsx(TensorSliders, { value: value, adapter: adapter, min: min, max: max, unit: unit, step: step, precision: precision, converted: converted, onChange: onComponentChange })] }), children: jsx(Body1, { children: `[${summary}]` }) }));
8167
8344
  };
8168
- const MaterialPropertiesServiceDefinition = {
8169
- friendlyName: "Babylon Lite Material Properties",
8170
- consumes: [PropertiesServiceIdentity],
8171
- factory: (propertiesService) => propertiesService.addSectionContent({
8172
- key: "Babylon Lite Material Properties",
8173
- predicate: IsMaterial,
8174
- content: [
8175
- {
8176
- section: "General",
8177
- component: ({ context }) => jsx(MaterialProperties, { material: context }),
8178
- },
8179
- ],
8180
- }),
8345
+ const TensorSliders = (props) => {
8346
+ const { value, adapter, min, max, unit, step, precision, converted, onChange } = props;
8347
+ return (jsx(Fragment, { children: adapter.components.map((component) => (jsx(NumberInputPropertyLine, { label: component.toUpperCase(), value: converted(adapter.getComponent(value, component)), min: min, max: max, onChange: (componentValue) => onChange(componentValue, component), unit: unit, step: step, precision: precision }, component))) }));
8348
+ };
8349
+ const RadiansToDegreesConverter = {
8350
+ from: (value) => (value * 180) / Math.PI,
8351
+ to: (value) => (value * Math.PI) / 180,
8352
+ };
8353
+ /**
8354
+ * Runtime-neutral controlled quaternion editor with optional Euler presentation.
8355
+ * @param props The quaternion value, runtime conversion adapter, and display options.
8356
+ * @returns The quaternion or Euler property line.
8357
+ */
8358
+ const ControlledQuaternionPropertyLine = (props) => {
8359
+ const { adapter, useEuler, useDegrees, ...rest } = props;
8360
+ const [quaternion, setQuaternion] = useState(props.value);
8361
+ useEffect(() => {
8362
+ setQuaternion(props.value);
8363
+ }, [props.value]);
8364
+ const onQuaternionChange = (value) => {
8365
+ setQuaternion(value);
8366
+ props.onChange(value);
8367
+ };
8368
+ if (useEuler) {
8369
+ const quaternionSummary = adapter.quaternion.components.map((component) => adapter.quaternion.getComponent(quaternion, component).toFixed(4)).join(", ");
8370
+ return (jsx(ControlledTensorPropertyLine, { ...rest, nullable: false, ignoreNullable: false, value: adapter.toEuler(quaternion), adapter: adapter.euler, valueConverter: useDegrees ? RadiansToDegreesConverter : undefined, onChange: (value) => onQuaternionChange(adapter.fromEuler(value, quaternion)), unit: useDegrees ? "°" : "rad", step: useDegrees ? 1 : 0.01, precision: useDegrees ? 1 : 2, expandedContent: jsx(TextPropertyLine, { label: "Quaternion", value: `[${quaternionSummary}]` }) }));
8371
+ }
8372
+ return (jsx(ControlledTensorPropertyLine, { ...rest, nullable: false, value: quaternion, adapter: adapter.quaternion, onChange: onQuaternionChange, unit: useDegrees ? "°" : "rad", step: useDegrees ? 1 : 0.01, precision: useDegrees ? 1 : 2 }));
8181
8373
  };
8182
8374
 
8183
- function IsMesh(entity) {
8184
- return typeof entity === "object" && entity !== null && "_gpu" in entity && "material" in entity;
8185
- }
8186
- const MeshGeneralProperties = (props) => {
8187
- const { mesh } = props;
8188
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: mesh, propertyKey: "name" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Receive Shadows", target: mesh, propertyKey: "receiveShadows" })] }));
8375
+ const ComponentIndices = {
8376
+ x: 0,
8377
+ y: 1,
8378
+ z: 2,
8379
+ w: 3,
8189
8380
  };
8190
- const MeshPropertiesServiceDefinition = {
8191
- friendlyName: "Babylon Lite Mesh Properties",
8192
- consumes: [PropertiesServiceIdentity],
8193
- factory: (propertiesService) => propertiesService.addSectionContent({
8194
- key: "Babylon Lite Mesh Properties",
8195
- predicate: IsMesh,
8196
- content: [
8197
- {
8198
- section: "General",
8199
- component: ({ context }) => jsx(MeshGeneralProperties, { mesh: context }),
8200
- },
8201
- ],
8202
- }),
8381
+ function IsNumberTuple(value) {
8382
+ return Array.isArray(value);
8383
+ }
8384
+ function GetComponent(value, component) {
8385
+ if (IsNumberTuple(value)) {
8386
+ return value[ComponentIndices[component]];
8387
+ }
8388
+ switch (component) {
8389
+ case "x":
8390
+ return value.x;
8391
+ case "y":
8392
+ return value.y;
8393
+ case "z":
8394
+ return "z" in value ? value.z : 0;
8395
+ case "w":
8396
+ return "w" in value ? value.w : 0;
8397
+ }
8398
+ }
8399
+ const Vector3Adapter = {
8400
+ components: ["x", "y", "z"],
8401
+ getComponent: GetComponent,
8402
+ withComponent: (value, component, componentValue) => IsNumberTuple(value)
8403
+ ? [component === "x" ? componentValue : value[0], component === "y" ? componentValue : value[1], component === "z" ? componentValue : value[2]]
8404
+ : { ...value, [component]: componentValue },
8405
+ };
8406
+ const QuaternionTensorAdapter = {
8407
+ components: ["x", "y", "z", "w"],
8408
+ getComponent: GetComponent,
8409
+ withComponent: (value, component, componentValue) => IsNumberTuple(value)
8410
+ ? [
8411
+ component === "x" ? componentValue : value[0],
8412
+ component === "y" ? componentValue : value[1],
8413
+ component === "z" ? componentValue : value[2],
8414
+ component === "w" ? componentValue : value[3],
8415
+ ]
8416
+ : { ...value, [component]: componentValue },
8203
8417
  };
8418
+ /**
8419
+ * Converts a Lite Euler value to a quaternion while preserving tuple versus structural representation.
8420
+ * @param value The Euler value in radians.
8421
+ * @param source The source quaternion whose representation should be preserved.
8422
+ * @returns The converted quaternion.
8423
+ */
8424
+ function CreateQuaternionFromEuler(value, source) {
8425
+ const [x, y, z] = "x" in value ? [value.x, value.y, value.z] : value;
8426
+ const quaternion = eulerToQuat(x, y, z);
8427
+ return "x" in source ? { x: quaternion[0], y: quaternion[1], z: quaternion[2], w: quaternion[3] } : quaternion;
8428
+ }
8429
+ const QuaternionAdapter = {
8430
+ quaternion: QuaternionTensorAdapter,
8431
+ euler: Vector3Adapter,
8432
+ fromEuler: CreateQuaternionFromEuler,
8433
+ toEuler: (value) => {
8434
+ const [x, y, z, w] = "x" in value ? [value.x, value.y, value.z, value.w] : value;
8435
+ return quatToEulerXYZ(x, y, z, w);
8436
+ },
8437
+ };
8438
+ const Vector3PropertyLine = (props) => jsx(ControlledTensorPropertyLine, { ...props, adapter: Vector3Adapter });
8439
+ const QuaternionPropertyLine = (props) => jsx(ControlledQuaternionPropertyLine, { ...props, adapter: QuaternionAdapter });
8204
8440
 
8205
- const GetMeshCount = (scene) => scene.meshes.length;
8206
- const GetLightCount = (scene) => scene.lights.length;
8207
- const GetAnimationGroupCount = (scene) => scene.animationGroups.length;
8208
- const GetShadowGeneratorCount = (scene) => scene.shadowGenerators.length;
8209
- const GetTextLayerCount = (renderer) => renderer.layers.length;
8210
- const GetSpriteLayerCount = (renderer) => renderer.layers.length;
8211
- function IsRegisteredRenderingContext(engine, entity) {
8212
- return typeof entity === "object" && entity !== null && engine.surfaces.some((surface) => getRenderingContexts(surface).includes(entity));
8441
+ function GetPosition$1(camera) {
8442
+ return [camera.position.x, camera.position.y, camera.position.z];
8213
8443
  }
8214
- const SceneProperties = (props) => {
8215
- const { scene } = props;
8216
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: scene, propertyKey: "name", ignoreNullable: true, defaultValue: "" }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Mesh Count", target: scene, getValue: GetMeshCount }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Light Count", target: scene, getValue: GetLightCount }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Animation Group Count", target: scene, getValue: GetAnimationGroupCount }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Shadow Generator Count", target: scene, getValue: GetShadowGeneratorCount }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Fixed Delta (ms)", target: scene, propertyKey: "fixedDeltaMs", min: 0, step: 1 })] }));
8444
+ function GetTarget(camera) {
8445
+ return [camera.target.x, camera.target.y, camera.target.z];
8446
+ }
8447
+ function SetFreeCameraPosition(camera, value) {
8448
+ SetVector3Value(camera.position, value);
8449
+ }
8450
+ function SetCameraTarget(camera, value) {
8451
+ SetVector3Value(camera.target, value);
8452
+ }
8453
+ const CameraGeneralProperties = (props) => {
8454
+ const { camera } = props;
8455
+ const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
8456
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: camera, propertyKey: "name", ignoreNullable: true, defaultValue: "" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Near Plane", target: camera, propertyKey: "nearPlane" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Far Plane", target: camera, propertyKey: "farPlane" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "FOV", target: camera, propertyKey: "fov", min: toDisplayAngle(0.1), max: toDisplayAngle(Math.PI), step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle })] }));
8217
8457
  };
8218
- const TextRendererProperties = (props) => {
8219
- const { renderer } = props;
8220
- return jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Layer Count", target: renderer, getValue: GetTextLayerCount });
8458
+ const FreeCameraTransformProperties = (props) => {
8459
+ const { camera } = props;
8460
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Position", target: camera, getValue: GetPosition$1, setValue: SetFreeCameraPosition }), jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Target", target: camera, getValue: GetTarget, setValue: SetCameraTarget }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Speed", target: camera, propertyKey: "speed" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Inertia", target: camera, propertyKey: "inertia", min: 0, max: 1 })] }));
8221
8461
  };
8222
- const SpriteRendererProperties = (props) => {
8223
- const { renderer } = props;
8224
- return jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Layer Count", target: renderer, getValue: GetSpriteLayerCount });
8462
+ const ArcRotateCameraTransformProperties = (props) => {
8463
+ const { camera } = props;
8464
+ const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
8465
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Target", target: camera, getValue: GetTarget, setValue: SetCameraTarget }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Alpha", target: camera, propertyKey: "alpha", unit: useDegrees ? "°" : "rad", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Beta", target: camera, propertyKey: "beta", unit: useDegrees ? "°" : "rad", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: camera, propertyKey: "radius", min: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Inertia", target: camera, propertyKey: "inertia", min: 0, max: 1 })] }));
8225
8466
  };
8226
- const RenderingContextPropertiesServiceDefinition = {
8227
- friendlyName: "Babylon Lite Rendering Context Properties",
8228
- consumes: [PropertiesServiceIdentity, EngineContextIdentity],
8229
- factory: (propertiesService, engineContext) => {
8230
- const engine = engineContext.engine;
8231
- const sceneRegistration = propertiesService.addSectionContent({
8232
- key: "Babylon Lite Scene Properties",
8233
- predicate: (entity) => {
8234
- if (!IsRegisteredRenderingContext(engine, entity)) {
8235
- return false;
8236
- }
8237
- return getRenderingContextKind(entity) === "scene";
8238
- },
8467
+
8468
+ const CameraPropertiesServiceDefinition = {
8469
+ friendlyName: "Babylon Lite Camera Properties",
8470
+ consumes: [PropertiesServiceIdentity],
8471
+ factory: (propertiesService) => {
8472
+ const cameraRegistration = propertiesService.addSectionContent({
8473
+ key: "Babylon Lite Camera Properties",
8474
+ predicate: IsCamera,
8239
8475
  content: [
8240
8476
  {
8241
8477
  section: "General",
8242
- component: ({ context }) => jsx(SceneProperties, { scene: context }),
8478
+ component: ({ context }) => jsx(CameraGeneralProperties, { camera: context }),
8243
8479
  },
8244
8480
  ],
8245
8481
  });
8246
- const textRendererRegistration = propertiesService.addSectionContent({
8247
- key: "Babylon Lite Text Renderer Properties",
8248
- predicate: (entity) => IsRegisteredRenderingContext(engine, entity) && getRenderingContextKind(entity) === "text-renderer",
8482
+ const freeCameraRegistration = propertiesService.addSectionContent({
8483
+ key: "Babylon Lite Free Camera Properties",
8484
+ predicate: IsFreeCamera,
8249
8485
  content: [
8250
8486
  {
8251
- section: "General",
8252
- component: ({ context }) => jsx(TextRendererProperties, { renderer: context }),
8487
+ section: "Transform",
8488
+ component: ({ context }) => jsx(FreeCameraTransformProperties, { camera: context }),
8253
8489
  },
8254
8490
  ],
8255
8491
  });
8256
- const spriteRendererRegistration = propertiesService.addSectionContent({
8257
- key: "Babylon Lite Sprite Renderer Properties",
8492
+ const arcRotateCameraRegistration = propertiesService.addSectionContent({
8493
+ key: "Babylon Lite Arc Rotate Camera Properties",
8494
+ predicate: IsArcRotateCamera,
8495
+ content: [
8496
+ {
8497
+ section: "Transform",
8498
+ component: ({ context }) => jsx(ArcRotateCameraTransformProperties, { camera: context }),
8499
+ },
8500
+ ],
8501
+ });
8502
+ return {
8503
+ dispose: () => {
8504
+ arcRotateCameraRegistration.dispose();
8505
+ freeCameraRegistration.dispose();
8506
+ cameraRegistration.dispose();
8507
+ },
8508
+ };
8509
+ },
8510
+ };
8511
+
8512
+ /**
8513
+ * Runtime-neutral controlled color editor with RGB or RGBA sliders and an injected picker.
8514
+ * @param props The controlled color, runtime adapter, and property-line presentation.
8515
+ * @returns The color property line.
8516
+ */
8517
+ const ControlledColorPropertyLine = (props) => {
8518
+ const { adapter, value, onChange, propertyLineRef } = props;
8519
+ const [currentValue, setCurrentValue] = useState(value);
8520
+ useEffect(() => {
8521
+ setCurrentValue(value);
8522
+ }, [value]);
8523
+ const handleChange = (newValue) => {
8524
+ setCurrentValue(newValue);
8525
+ onChange(newValue);
8526
+ };
8527
+ const onSliderChange = (componentValue, component) => {
8528
+ const color = adapter.getColor(currentValue);
8529
+ handleChange(adapter.createColor({
8530
+ ...color,
8531
+ [component]: component === "a" ? componentValue : componentValue / 255,
8532
+ }, currentValue));
8533
+ };
8534
+ const color = adapter.getColor(currentValue);
8535
+ return (jsx(PropertyLine, { ref: propertyLineRef, ...props, expandedContent: jsx(ColorSliders, { color: color, onSliderChange: onSliderChange }), children: createElement(adapter.picker, {
8536
+ value: currentValue,
8537
+ onChange: handleChange,
8538
+ isLinearMode: props.isLinearMode,
8539
+ disabled: props.disabled,
8540
+ className: props.className,
8541
+ style: props.style,
8542
+ title: props.title,
8543
+ }) }));
8544
+ };
8545
+ const ColorSliders = (props) => {
8546
+ const { color, onSliderChange } = props;
8547
+ return (jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "R", value: color.r * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "r") }), jsx(SyncedSliderPropertyLine, { label: "G", value: color.g * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "g") }), jsx(SyncedSliderPropertyLine, { label: "B", value: color.b * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "b") }), color.a === undefined ? undefined : (jsx(SyncedSliderPropertyLine, { label: "A", value: color.a, min: 0, max: 1, step: 0.01, onChange: (value) => onSliderChange(value, "a") }))] }));
8548
+ };
8549
+
8550
+ const useStyles$1 = makeStyles({
8551
+ picker: {
8552
+ width: "350px",
8553
+ height: "350px",
8554
+ },
8555
+ trigger: {
8556
+ display: "flex",
8557
+ alignItems: "center",
8558
+ },
8559
+ });
8560
+ function LinearChannelToGamma(value) {
8561
+ return value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055;
8562
+ }
8563
+ function GammaChannelToLinear(value) {
8564
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
8565
+ }
8566
+ function ConvertColorSpace(color, convertChannel) {
8567
+ return {
8568
+ r: convertChannel(color.r),
8569
+ g: convertChannel(color.g),
8570
+ b: convertChannel(color.b),
8571
+ ...("a" in color ? { a: color.a } : {}),
8572
+ };
8573
+ }
8574
+ function RgbToHsv(color) {
8575
+ const max = Math.max(color.r, color.g, color.b);
8576
+ const min = Math.min(color.r, color.g, color.b);
8577
+ const delta = max - min;
8578
+ let hue = 0;
8579
+ if (delta !== 0) {
8580
+ if (max === color.r) {
8581
+ hue = 60 * (((color.g - color.b) / delta) % 6);
8582
+ }
8583
+ else if (max === color.g) {
8584
+ hue = 60 * ((color.b - color.r) / delta + 2);
8585
+ }
8586
+ else {
8587
+ hue = 60 * ((color.r - color.g) / delta + 4);
8588
+ }
8589
+ }
8590
+ return {
8591
+ h: hue < 0 ? hue + 360 : hue,
8592
+ s: max === 0 ? 0 : delta / max,
8593
+ v: max,
8594
+ ...("a" in color ? { a: color.a } : {}),
8595
+ };
8596
+ }
8597
+ function HsvToRgb(hue, saturation, value, alpha) {
8598
+ const chroma = value * saturation;
8599
+ const hueSector = hue / 60;
8600
+ const intermediate = chroma * (1 - Math.abs((hueSector % 2) - 1));
8601
+ const offset = value - chroma;
8602
+ let red = 0;
8603
+ let green = 0;
8604
+ let blue = 0;
8605
+ if (hueSector < 1) {
8606
+ red = chroma;
8607
+ green = intermediate;
8608
+ }
8609
+ else if (hueSector < 2) {
8610
+ red = intermediate;
8611
+ green = chroma;
8612
+ }
8613
+ else if (hueSector < 3) {
8614
+ green = chroma;
8615
+ blue = intermediate;
8616
+ }
8617
+ else if (hueSector < 4) {
8618
+ green = intermediate;
8619
+ blue = chroma;
8620
+ }
8621
+ else if (hueSector < 5) {
8622
+ red = intermediate;
8623
+ blue = chroma;
8624
+ }
8625
+ else {
8626
+ red = chroma;
8627
+ blue = intermediate;
8628
+ }
8629
+ return {
8630
+ r: red + offset,
8631
+ g: green + offset,
8632
+ b: blue + offset,
8633
+ ...(alpha !== undefined ? { a: alpha } : {}),
8634
+ };
8635
+ }
8636
+ function ColorToHex(color) {
8637
+ const channelToHex = (value) => Math.round(Math.min(1, Math.max(0, value)) * 255)
8638
+ .toString(16)
8639
+ .padStart(2, "0");
8640
+ return `#${channelToHex(color.r)}${channelToHex(color.g)}${channelToHex(color.b)}${color.a === undefined ? "" : channelToHex(color.a)}`;
8641
+ }
8642
+ /**
8643
+ * Runtime-neutral color picker for structural RGB and RGBA values.
8644
+ * @param props The controlled value, adapter, color-space mode, and change callback.
8645
+ * @returns A color swatch that opens the Fluent color picker.
8646
+ */
8647
+ const StructuralColorPickerPopup = (props) => {
8648
+ const { value, onChange, adapter, isLinearMode, ...rest } = props;
8649
+ const classes = useStyles$1();
8650
+ const { size } = useContext(ToolContext);
8651
+ const [currentValue, setCurrentValue] = useState(value);
8652
+ useEffect(() => {
8653
+ setCurrentValue(value);
8654
+ }, [value]);
8655
+ const storedColor = adapter.getColor(currentValue);
8656
+ const gammaColor = useMemo(() => (isLinearMode ? ConvertColorSpace(storedColor, LinearChannelToGamma) : storedColor), [storedColor, isLinearMode]);
8657
+ const hexColor = ColorToHex(gammaColor);
8658
+ const handleColorPickerChange = (_event, data) => {
8659
+ const changedGammaColor = HsvToRgb(data.color.h, data.color.s, data.color.v, storedColor.a === undefined ? undefined : (data.color.a ?? storedColor.a));
8660
+ const changedStoredColor = isLinearMode ? ConvertColorSpace(changedGammaColor, GammaChannelToLinear) : changedGammaColor;
8661
+ const changedValue = adapter.createColor(changedStoredColor, currentValue);
8662
+ setCurrentValue(changedValue);
8663
+ onChange(changedValue);
8664
+ };
8665
+ return (jsx(Popover, { trigger: jsx(ColorSwatch, { ...rest, className: classes.trigger, borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: hexColor, value: hexColor.slice(1) }), children: jsxs(ColorPicker, { className: classes.picker, color: RgbToHsv(gammaColor), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), storedColor.a !== undefined ? jsx(AlphaSlider, { "aria-label": "Alpha" }) : undefined] }) }));
8666
+ };
8667
+
8668
+ function IsColor4Value(value) {
8669
+ return "r" in value ? "a" in value : value.length === 4;
8670
+ }
8671
+ function GetStructuralColor(value) {
8672
+ if (!("r" in value)) {
8673
+ return {
8674
+ r: value[0],
8675
+ g: value[1],
8676
+ b: value[2],
8677
+ ...(value.length === 4 ? { a: value[3] } : {}),
8678
+ };
8679
+ }
8680
+ return value;
8681
+ }
8682
+ function CreateColor3Value(color, source) {
8683
+ if (!("r" in source)) {
8684
+ return [color.r, color.g, color.b];
8685
+ }
8686
+ return {
8687
+ r: color.r,
8688
+ g: color.g,
8689
+ b: color.b,
8690
+ };
8691
+ }
8692
+ function CreateColor4Value(color, source) {
8693
+ if (!("r" in source)) {
8694
+ return [color.r, color.g, color.b, color.a ?? source[3]];
8695
+ }
8696
+ return {
8697
+ r: color.r,
8698
+ g: color.g,
8699
+ b: color.b,
8700
+ a: color.a ?? source.a,
8701
+ };
8702
+ }
8703
+ const Picker = (props) => (jsx(StructuralColorPickerPopup, { ...props, adapter: {
8704
+ getColor: GetStructuralColor,
8705
+ createColor: (color, source) => (IsColor4Value(source) ? CreateColor4Value(color, source) : CreateColor3Value(color, source)),
8706
+ } }));
8707
+ const Adapter = {
8708
+ picker: Picker,
8709
+ getColor: GetStructuralColor,
8710
+ createColor: (color, source) => (IsColor4Value(source) ? CreateColor4Value(color, source) : CreateColor3Value(color, source)),
8711
+ };
8712
+ const Color3PropertyLine = (props) => (jsx(ControlledColorPropertyLine, { ...props, adapter: Adapter }));
8713
+
8714
+ function GetVector3(value) {
8715
+ return [value.x, value.y, value.z];
8716
+ }
8717
+ /**
8718
+ * Updates a plain Babylon Lite light property and marks its GPU light data dirty through a public observable transform.
8719
+ * @param light The light to update.
8720
+ * @param propertyKey The property to update.
8721
+ * @param value The new property value.
8722
+ */
8723
+ function SetLightProperty(light, propertyKey, value) {
8724
+ light[propertyKey] = value;
8725
+ const dirtyVector = "direction" in light ? light.direction : light.position;
8726
+ dirtyVector.set(dirtyVector.x, dirtyVector.y, dirtyVector.z);
8727
+ }
8728
+ function GetDiffuse(light) {
8729
+ return light.diffuse;
8730
+ }
8731
+ function ToColorTuple(value) {
8732
+ return "r" in value ? [value.r, value.g, value.b] : [value[0], value[1], value[2]];
8733
+ }
8734
+ function SetDiffuse(light, value) {
8735
+ SetLightProperty(light, "diffuse", ToColorTuple(value));
8736
+ }
8737
+ function GetSpecular(light) {
8738
+ return light.specular;
8739
+ }
8740
+ function SetSpecular(light, value) {
8741
+ SetLightProperty(light, "specular", ToColorTuple(value));
8742
+ }
8743
+ function GetIntensity(light) {
8744
+ return light.intensity;
8745
+ }
8746
+ function SetIntensity(light, value) {
8747
+ SetLightProperty(light, "intensity", value);
8748
+ }
8749
+ function GetRange(light) {
8750
+ return light.range;
8751
+ }
8752
+ function SetRange(light, value) {
8753
+ SetLightProperty(light, "range", value);
8754
+ }
8755
+ function GetExponent(light) {
8756
+ return light.exponent;
8757
+ }
8758
+ function SetExponent(light, value) {
8759
+ SetLightProperty(light, "exponent", value);
8760
+ }
8761
+ function GetDiffuseColor(light) {
8762
+ return light.diffuseColor;
8763
+ }
8764
+ function SetDiffuseColor(light, value) {
8765
+ SetLightProperty(light, "diffuseColor", ToColorTuple(value));
8766
+ }
8767
+ function GetSpecularColor(light) {
8768
+ return light.specularColor;
8769
+ }
8770
+ function SetSpecularColor(light, value) {
8771
+ SetLightProperty(light, "specularColor", ToColorTuple(value));
8772
+ }
8773
+ function GetGroundColor(light) {
8774
+ return light.groundColor;
8775
+ }
8776
+ function SetGroundColor(light, value) {
8777
+ SetLightProperty(light, "groundColor", ToColorTuple(value));
8778
+ }
8779
+ const LightGeneralProperties = (props) => {
8780
+ const { light } = props;
8781
+ return jsx(TextPropertyLine, { label: "Type", value: light.lightType });
8782
+ };
8783
+ const DirectionalLightSetupProperties = (props) => {
8784
+ const { light } = props;
8785
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Position", target: light.position, getValue: GetVector3, setValue: SetVector3Value }), jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Direction", target: light.direction, getValue: GetVector3, setValue: SetVector3Value }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Diffuse", target: light, getValue: GetDiffuse, setValue: SetDiffuse, isLinearMode: true }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Specular", target: light, getValue: GetSpecular, setValue: SetSpecular, isLinearMode: true }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Intensity", target: light, getValue: GetIntensity, setValue: SetIntensity })] }));
8786
+ };
8787
+ const PointLightSetupProperties = (props) => {
8788
+ const { light } = props;
8789
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Position", target: light.position, getValue: GetVector3, setValue: SetVector3Value }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Diffuse", target: light, getValue: GetDiffuse, setValue: SetDiffuse, isLinearMode: true }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Specular", target: light, getValue: GetSpecular, setValue: SetSpecular, isLinearMode: true }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Intensity", target: light, getValue: GetIntensity, setValue: SetIntensity }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Range", target: light, getValue: GetRange, setValue: SetRange })] }));
8790
+ };
8791
+ const SpotLightSetupProperties = (props) => {
8792
+ const { light } = props;
8793
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Position", target: light.position, getValue: GetVector3, setValue: SetVector3Value }), jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Direction", target: light.direction, getValue: GetVector3, setValue: SetVector3Value }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Diffuse", target: light, getValue: GetDiffuse, setValue: SetDiffuse, isLinearMode: true }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Specular", target: light, getValue: GetSpecular, setValue: SetSpecular, isLinearMode: true }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Intensity", target: light, getValue: GetIntensity, setValue: SetIntensity }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Range", target: light, getValue: GetRange, setValue: SetRange }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Angle", target: light, propertyKey: "angle", unit: "rad", min: 0, max: Math.PI }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Exponent", target: light, getValue: GetExponent, setValue: SetExponent, min: 0 })] }));
8794
+ };
8795
+ const HemisphericLightSetupProperties = (props) => {
8796
+ const { light } = props;
8797
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Direction", target: light.direction, getValue: GetVector3, setValue: SetVector3Value }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Diffuse", target: light, getValue: GetDiffuseColor, setValue: SetDiffuseColor, isLinearMode: true }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Specular", target: light, getValue: GetSpecularColor, setValue: SetSpecularColor, isLinearMode: true }), jsx(DerivedProperty, { component: Color3PropertyLine, label: "Ground", target: light, getValue: GetGroundColor, setValue: SetGroundColor, isLinearMode: true }), jsx(DerivedProperty, { component: NumberInputPropertyLine, label: "Intensity", target: light, getValue: GetIntensity, setValue: SetIntensity })] }));
8798
+ };
8799
+
8800
+ const LightPropertiesServiceDefinition = {
8801
+ friendlyName: "Babylon Lite Light Properties",
8802
+ consumes: [PropertiesServiceIdentity],
8803
+ factory: (propertiesService) => {
8804
+ const registrations = [
8805
+ propertiesService.addSectionContent({
8806
+ key: "Babylon Lite Light Properties",
8807
+ predicate: IsLight,
8808
+ content: [{ section: "General", component: ({ context }) => jsx(LightGeneralProperties, { light: context }) }],
8809
+ }),
8810
+ propertiesService.addSectionContent({
8811
+ key: "Babylon Lite Directional Light Properties",
8812
+ predicate: IsDirectionalLight,
8813
+ content: [{ section: "Setup", component: ({ context }) => jsx(DirectionalLightSetupProperties, { light: context }) }],
8814
+ }),
8815
+ propertiesService.addSectionContent({
8816
+ key: "Babylon Lite Point Light Properties",
8817
+ predicate: IsPointLight,
8818
+ content: [{ section: "Setup", component: ({ context }) => jsx(PointLightSetupProperties, { light: context }) }],
8819
+ }),
8820
+ propertiesService.addSectionContent({
8821
+ key: "Babylon Lite Spot Light Properties",
8822
+ predicate: IsSpotLight,
8823
+ content: [{ section: "Setup", component: ({ context }) => jsx(SpotLightSetupProperties, { light: context }) }],
8824
+ }),
8825
+ propertiesService.addSectionContent({
8826
+ key: "Babylon Lite Hemispheric Light Properties",
8827
+ predicate: IsHemisphericLight,
8828
+ content: [{ section: "Setup", component: ({ context }) => jsx(HemisphericLightSetupProperties, { light: context }) }],
8829
+ }),
8830
+ ];
8831
+ return {
8832
+ dispose: () => registrations.forEach((registration) => registration.dispose()),
8833
+ };
8834
+ },
8835
+ };
8836
+
8837
+ function IsMaterial(entity) {
8838
+ return typeof entity === "object" && entity !== null && getMaterialFamily(entity) !== undefined;
8839
+ }
8840
+ const MaterialProperties = (props) => {
8841
+ const { material } = props;
8842
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: material, propertyKey: "name", ignoreNullable: true, defaultValue: "" }), jsx(TextPropertyLine, { label: "Family", value: getMaterialFamily(material) ?? "Unknown" })] }));
8843
+ };
8844
+ const MaterialPropertiesServiceDefinition = {
8845
+ friendlyName: "Babylon Lite Material Properties",
8846
+ consumes: [PropertiesServiceIdentity],
8847
+ factory: (propertiesService) => propertiesService.addSectionContent({
8848
+ key: "Babylon Lite Material Properties",
8849
+ predicate: IsMaterial,
8850
+ content: [
8851
+ {
8852
+ section: "General",
8853
+ component: ({ context }) => jsx(MaterialProperties, { material: context }),
8854
+ },
8855
+ ],
8856
+ }),
8857
+ };
8858
+
8859
+ const MeshGeneralProperties = (props) => {
8860
+ const { mesh } = props;
8861
+ return jsx(BoundProperty, { component: SwitchPropertyLine, label: "Receive Shadows", target: mesh, propertyKey: "receiveShadows" });
8862
+ };
8863
+ const MeshPropertiesServiceDefinition = {
8864
+ friendlyName: "Babylon Lite Mesh Properties",
8865
+ consumes: [PropertiesServiceIdentity],
8866
+ factory: (propertiesService) => propertiesService.addSectionContent({
8867
+ key: "Babylon Lite Mesh Properties",
8868
+ predicate: IsMesh,
8869
+ content: [
8870
+ {
8871
+ section: "Rendering",
8872
+ component: ({ context }) => jsx(MeshGeneralProperties, { mesh: context }),
8873
+ },
8874
+ ],
8875
+ }),
8876
+ };
8877
+
8878
+ const GetMeshCount = (scene) => scene.meshes.length;
8879
+ const GetLightCount = (scene) => scene.lights.length;
8880
+ const GetAnimationGroupCount = (scene) => scene.animationGroups.length;
8881
+ const GetShadowGeneratorCount = (scene) => scene.shadowGenerators.length;
8882
+ const GetTextLayerCount = (renderer) => renderer.layers.length;
8883
+ const GetSpriteLayerCount = (renderer) => renderer.layers.length;
8884
+ function IsRegisteredRenderingContext(engine, entity) {
8885
+ return typeof entity === "object" && entity !== null && engine.surfaces.some((surface) => getRenderingContexts(surface).includes(entity));
8886
+ }
8887
+ const SceneProperties = (props) => {
8888
+ const { scene } = props;
8889
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: scene, propertyKey: "name", ignoreNullable: true, defaultValue: "" }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Mesh Count", target: scene, getValue: GetMeshCount }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Light Count", target: scene, getValue: GetLightCount }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Animation Group Count", target: scene, getValue: GetAnimationGroupCount }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Shadow Generator Count", target: scene, getValue: GetShadowGeneratorCount }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Fixed Delta (ms)", target: scene, propertyKey: "fixedDeltaMs", min: 0, step: 1 })] }));
8890
+ };
8891
+ const TextRendererProperties = (props) => {
8892
+ const { renderer } = props;
8893
+ return jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Layer Count", target: renderer, getValue: GetTextLayerCount });
8894
+ };
8895
+ const SpriteRendererProperties = (props) => {
8896
+ const { renderer } = props;
8897
+ return jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Layer Count", target: renderer, getValue: GetSpriteLayerCount });
8898
+ };
8899
+ const RenderingContextPropertiesServiceDefinition = {
8900
+ friendlyName: "Babylon Lite Rendering Context Properties",
8901
+ consumes: [PropertiesServiceIdentity, EngineContextIdentity],
8902
+ factory: (propertiesService, engineContext) => {
8903
+ const engine = engineContext.engine;
8904
+ const sceneRegistration = propertiesService.addSectionContent({
8905
+ key: "Babylon Lite Scene Properties",
8906
+ predicate: (entity) => {
8907
+ if (!IsRegisteredRenderingContext(engine, entity)) {
8908
+ return false;
8909
+ }
8910
+ return getRenderingContextKind(entity) === "scene";
8911
+ },
8912
+ content: [
8913
+ {
8914
+ section: "General",
8915
+ component: ({ context }) => jsx(SceneProperties, { scene: context }),
8916
+ },
8917
+ ],
8918
+ });
8919
+ const textRendererRegistration = propertiesService.addSectionContent({
8920
+ key: "Babylon Lite Text Renderer Properties",
8921
+ predicate: (entity) => IsRegisteredRenderingContext(engine, entity) && getRenderingContextKind(entity) === "text-renderer",
8922
+ content: [
8923
+ {
8924
+ section: "General",
8925
+ component: ({ context }) => jsx(TextRendererProperties, { renderer: context }),
8926
+ },
8927
+ ],
8928
+ });
8929
+ const spriteRendererRegistration = propertiesService.addSectionContent({
8930
+ key: "Babylon Lite Sprite Renderer Properties",
8258
8931
  predicate: (entity) => IsRegisteredRenderingContext(engine, entity) && getRenderingContextKind(entity) === "sprite-renderer",
8259
8932
  content: [
8260
8933
  {
@@ -8273,6 +8946,272 @@ const RenderingContextPropertiesServiceDefinition = {
8273
8946
  },
8274
8947
  };
8275
8948
 
8949
+ const PrettyJSONIndent = 2;
8950
+ function IsParsable(metadata) {
8951
+ try {
8952
+ const parsed = JSON.parse(metadata);
8953
+ return !!parsed && !IsString(parsed);
8954
+ }
8955
+ catch {
8956
+ return false;
8957
+ }
8958
+ }
8959
+ /**
8960
+ * Checks if the input is a string.
8961
+ * @param input - any input to check
8962
+ * @returns boolean - true if the input is a string, false otherwise
8963
+ */
8964
+ function IsString(input) {
8965
+ return typeof input === "string" || input instanceof String;
8966
+ }
8967
+ /**
8968
+ * Checks recursively for functions on an object and returns `false` if any are found.
8969
+ * @param o any object, string or number
8970
+ * @returns boolean
8971
+ */
8972
+ function ObjectCanSafelyStringify(o) {
8973
+ if (typeof o === "function") {
8974
+ return false;
8975
+ }
8976
+ if (o === null || o === true || o === false || typeof o === "number" || IsString(o)) {
8977
+ return true;
8978
+ }
8979
+ if (typeof o === "object") {
8980
+ if (Object.values(o).length === 0) {
8981
+ return true;
8982
+ }
8983
+ return Object.values(o).every((value) => ObjectCanSafelyStringify(value));
8984
+ }
8985
+ if (Array.isArray(o)) {
8986
+ return o.every((value) => ObjectCanSafelyStringify(value));
8987
+ }
8988
+ return false;
8989
+ }
8990
+ function GetMetadataEntityType(metadata) {
8991
+ if (metadata == null) {
8992
+ return "null";
8993
+ }
8994
+ else if (IsString(metadata)) {
8995
+ return "string";
8996
+ }
8997
+ else if (!ObjectCanSafelyStringify(metadata)) {
8998
+ return "object";
8999
+ }
9000
+ else {
9001
+ return "JSON";
9002
+ }
9003
+ }
9004
+ function HasGltfExtras(metadata) {
9005
+ return IsParsable(metadata) && !!JSON.parse(metadata).gltf;
9006
+ }
9007
+ function StringifyMetadata(metadata, format) {
9008
+ if (IsString(metadata)) {
9009
+ return metadata;
9010
+ }
9011
+ if (metadata) {
9012
+ // Try JSON.stringify even for objects with functions — it safely omits
9013
+ // function-valued and undefined properties. Only fall back to String()
9014
+ // for cases that actually throw (e.g. circular references).
9015
+ try {
9016
+ return JSON.stringify(metadata, undefined, format ? PrettyJSONIndent : undefined);
9017
+ }
9018
+ catch {
9019
+ return String(metadata);
9020
+ }
9021
+ }
9022
+ return null;
9023
+ }
9024
+ function Restringify(value, format) {
9025
+ return IsParsable(value) ? JSON.stringify(JSON.parse(value), undefined, format ? PrettyJSONIndent : undefined) : value;
9026
+ }
9027
+ function PopulateGLTFExtras(metadata) {
9028
+ if (!metadata) {
9029
+ metadata = "{}";
9030
+ }
9031
+ if (!IsParsable(metadata)) {
9032
+ return metadata;
9033
+ }
9034
+ try {
9035
+ const parsedJson = JSON.parse(metadata);
9036
+ if (parsedJson) {
9037
+ if (Reflect.has(parsedJson, "gltf")) {
9038
+ if (!Reflect.has(parsedJson.gltf, "extras")) {
9039
+ parsedJson.gltf.extras = {};
9040
+ }
9041
+ }
9042
+ else {
9043
+ parsedJson.gltf = { extras: {} };
9044
+ }
9045
+ }
9046
+ return JSON.stringify(parsedJson);
9047
+ }
9048
+ catch {
9049
+ return metadata;
9050
+ }
9051
+ }
9052
+ function SaveMetadata(entity, metadata) {
9053
+ if (IsParsable(metadata)) {
9054
+ entity.metadata = JSON.parse(metadata);
9055
+ }
9056
+ else {
9057
+ entity.metadata = metadata;
9058
+ }
9059
+ }
9060
+ const useStyles = makeStyles({
9061
+ mainDiv: {
9062
+ display: "flex",
9063
+ flexDirection: "column",
9064
+ gap: tokens.spacingVerticalXS,
9065
+ },
9066
+ buttonDiv: {
9067
+ display: "grid",
9068
+ gridAutoFlow: "column",
9069
+ gridTemplateColumns: "1fr auto auto auto",
9070
+ gap: tokens.spacingHorizontalXS,
9071
+ },
9072
+ });
9073
+ /**
9074
+ * Component to display metadata properties of an entity.
9075
+ * @param props - The properties for the component.
9076
+ * @returns A React component that displays metadata properties.
9077
+ */
9078
+ const MetadataProperties = (props) => {
9079
+ const { entity } = props;
9080
+ const classes = useStyles();
9081
+ const { size } = useContext(ToolContext);
9082
+ const metadata = useProperty(entity, "metadata");
9083
+ const stringifiedMetadata = useMemo(() => StringifyMetadata(metadata, false) ?? "", [metadata]);
9084
+ const metadataType = useMemo(() => GetMetadataEntityType(metadata), [metadata]);
9085
+ const canPreventObjectCorruption = metadataType === "object";
9086
+ const [preventObjectCorruption, setPreventObjectCorruption] = useState(false);
9087
+ const isReadonly = canPreventObjectCorruption && preventObjectCorruption;
9088
+ const [editedMetadata, setEditedMetadata] = useState(stringifiedMetadata);
9089
+ useEffect(() => {
9090
+ setEditedMetadata(stringifiedMetadata);
9091
+ }, [stringifiedMetadata]);
9092
+ const isEditedMetadataJSON = useMemo(() => IsParsable(editedMetadata), [editedMetadata]);
9093
+ const unformattedEditedMetadata = useMemo(() => Restringify(editedMetadata, false), [editedMetadata]);
9094
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Property Type", value: metadataType }), jsx(Collapse, { visible: canPreventObjectCorruption, children: jsx(SwitchPropertyLine, { label: "Prevent Object Corruption", value: isReadonly, onChange: setPreventObjectCorruption }) }), jsx(LineContainer, { uniqueId: "MetadataTextarea", children: jsx(Textarea, { disabled: isReadonly, value: editedMetadata, onChange: setEditedMetadata }) }), jsx(ButtonLine, { label: "Populate glTF extras", disabled: !!editedMetadata && (!IsParsable(editedMetadata) || HasGltfExtras(editedMetadata)), onClick: () => {
9095
+ const isFormatted = Restringify(editedMetadata, true) === editedMetadata;
9096
+ let withGLTFExtras = PopulateGLTFExtras(editedMetadata);
9097
+ if (isFormatted) {
9098
+ withGLTFExtras = Restringify(withGLTFExtras, true);
9099
+ }
9100
+ setEditedMetadata(withGLTFExtras);
9101
+ } }), jsx(LineContainer, { uniqueId: "MetadataButtonDiv", children: jsxs("div", { className: classes.buttonDiv, children: [jsx(Button$1, { size: size, icon: jsx(SaveRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => SaveMetadata(entity, editedMetadata), children: jsx(Body1, { children: "Save" }) }), jsx(Tooltip$1, { content: "Undo Changes", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(ArrowUndoRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => setEditedMetadata(stringifiedMetadata) }) }), jsx(Tooltip$1, { content: "Format (Pretty Print)", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(BracesRegular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, true)) }) }), jsx(Tooltip$1, { content: "Clear Formatting (Undo Pretty Print)", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(BracesDismiss16Regular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, false)) }) })] }) })] }));
9102
+ };
9103
+
9104
+ function GetPosition(node) {
9105
+ return [node.position.x, node.position.y, node.position.z];
9106
+ }
9107
+ function GetRotation(node) {
9108
+ return [node.rotationQuaternion.x, node.rotationQuaternion.y, node.rotationQuaternion.z, node.rotationQuaternion.w];
9109
+ }
9110
+ function GetScaling(node) {
9111
+ return [node.scaling.x, node.scaling.y, node.scaling.z];
9112
+ }
9113
+ function GetVisibility(node) {
9114
+ return node.visible !== false;
9115
+ }
9116
+ function SetVisibility(node, visible) {
9117
+ setSubtreeVisible(node, visible);
9118
+ }
9119
+ function SetPosition(node, value) {
9120
+ SetVector3Value(node.position, value);
9121
+ }
9122
+ function SetRotation(node, value) {
9123
+ SetQuaternionValue(node.rotationQuaternion, value);
9124
+ }
9125
+ function SetScaling(node, value) {
9126
+ SetVector3Value(node.scaling, value);
9127
+ }
9128
+ const SceneNodeGeneralProperties = (props) => {
9129
+ const { node } = props;
9130
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: node, propertyKey: "name" }), jsx(DerivedProperty, { component: SwitchPropertyLine, label: "Visible", target: node, getValue: GetVisibility, setValue: SetVisibility })] }));
9131
+ };
9132
+ const SceneNodeTransformProperties = (props) => {
9133
+ const { node } = props;
9134
+ const [useDegrees] = useSetting(UseDegreesSettingDescriptor);
9135
+ const [useEuler] = useSetting(UseEulerSettingDescriptor);
9136
+ return (jsxs(Fragment, { children: [jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Position", target: node, getValue: GetPosition, setValue: SetPosition }), jsx(DerivedProperty, { component: QuaternionPropertyLine, label: "Rotation", target: node, getValue: GetRotation, setValue: SetRotation, useDegrees: useDegrees, useEuler: useEuler }), jsx(DerivedProperty, { component: Vector3PropertyLine, label: "Scaling", target: node, getValue: GetScaling, setValue: SetScaling, step: 0.1 })] }));
9137
+ };
9138
+
9139
+ const SceneNodePropertiesServiceDefinition = {
9140
+ friendlyName: "Babylon Lite Scene Node Properties",
9141
+ consumes: [PropertiesServiceIdentity],
9142
+ factory: (propertiesService) => propertiesService.addSectionContent({
9143
+ key: "Babylon Lite Scene Node Properties",
9144
+ predicate: IsSceneNode,
9145
+ content: [
9146
+ {
9147
+ section: "General",
9148
+ component: ({ context }) => jsx(SceneNodeGeneralProperties, { node: context }),
9149
+ },
9150
+ {
9151
+ section: "Transform",
9152
+ component: ({ context }) => jsx(SceneNodeTransformProperties, { node: context }),
9153
+ },
9154
+ {
9155
+ section: "Metadata",
9156
+ component: ({ context }) => jsx(MetadataProperties, { entity: context }),
9157
+ },
9158
+ ],
9159
+ }),
9160
+ };
9161
+
9162
+ /**
9163
+ * Returns whether a rendering context is a scene context.
9164
+ * @param context The rendering context to test.
9165
+ * @returns Whether the context is a scene context.
9166
+ */
9167
+ function IsSceneContext(context) {
9168
+ return getRenderingContextKind(context) === "scene";
9169
+ }
9170
+ /**
9171
+ * Creates an Explorer section containing the supplied scene entities.
9172
+ * @param id The stable section identifier.
9173
+ * @param displayName The section's display name.
9174
+ * @param entities The entities displayed by the section.
9175
+ * @param getEntityDisplayInfo Gets the display information for an entity.
9176
+ * @param entityIcon The optional icon component for the entities.
9177
+ * @returns The Explorer node description for the section.
9178
+ */
9179
+ function CreateSceneExplorerSectionNode(id, displayName, entities, getEntityDisplayInfo, entityIcon) {
9180
+ return {
9181
+ id,
9182
+ kind: "group",
9183
+ getDisplayInfo: () => ({ name: displayName }),
9184
+ getChildren: () => entities.map((entity, index) => ({
9185
+ id: GetEntityId(entity).toString(),
9186
+ kind: "item",
9187
+ entity,
9188
+ icon: entityIcon,
9189
+ getDisplayInfo: () => getEntityDisplayInfo(entity, index),
9190
+ })),
9191
+ };
9192
+ }
9193
+
9194
+ function GetScenes(engineContext) {
9195
+ return engineContext.engine.surfaces.flatMap((surface) => getRenderingContexts(surface).filter(IsSceneContext));
9196
+ }
9197
+ const ShadowGeneratorPropertiesServiceDefinition = {
9198
+ friendlyName: "Babylon Lite Shadow Generator Properties",
9199
+ consumes: [PropertiesServiceIdentity, EngineContextIdentity],
9200
+ factory: (propertiesService, engineContext) => propertiesService.addSectionContent({
9201
+ key: "Babylon Lite Shadow Generator Properties",
9202
+ predicate: (entity) => typeof entity === "object" && entity !== null && GetScenes(engineContext).some((scene) => scene.shadowGenerators.includes(entity)),
9203
+ content: [
9204
+ {
9205
+ section: "General",
9206
+ component: ({ context }) => {
9207
+ const scene = GetScenes(engineContext).find((candidate) => candidate.shadowGenerators.includes(context));
9208
+ return jsx(TextPropertyLine, { label: "Identity", value: scene ? GetShadowGeneratorDisplayName(scene, context) : "Shadow Generator" });
9209
+ },
9210
+ },
9211
+ ],
9212
+ }),
9213
+ };
9214
+
8276
9215
  function GetRunCount(layer) {
8277
9216
  return layer.data.runs.length;
8278
9217
  }
@@ -8330,6 +9269,100 @@ const TexturePropertiesServiceDefinition = {
8330
9269
  }),
8331
9270
  };
8332
9271
 
9272
+ function ClearRemovedSelection(selectionService, engineContext, selectionWasRemoved) {
9273
+ const selectedEntity = selectionService.selectedEntity;
9274
+ if (selectionWasRemoved &&
9275
+ !GetSceneContexts(engineContext.engine).some((scene) => GetSceneNodeRoots(scene).some((root) => root === selectedEntity || (IsSceneNode(selectedEntity) && IsSceneNodeDescendantOf(selectedEntity, root))))) {
9276
+ selectionService.selectedEntity = null;
9277
+ }
9278
+ }
9279
+
9280
+ const CameraIcon = () => jsx(CameraRegular, { color: tokens.colorPaletteGreenForeground2 });
9281
+ const CameraExplorerServiceDefinition = {
9282
+ friendlyName: "Babylon Lite Camera Explorer",
9283
+ consumes: [EngineExplorerServiceIdentity, ExplorerServiceIdentity, WatcherServiceIdentity, SelectionServiceIdentity, EngineContextIdentity],
9284
+ factory: (engineExplorerService, explorerService, watcherService, selectionService, engineContext) => {
9285
+ const providerRegistration = engineExplorerService.addRenderingContextNodeProvider({
9286
+ order: 10,
9287
+ predicate: IsSceneContext,
9288
+ getNodes: (scene) => {
9289
+ return [
9290
+ CreateSceneExplorerSectionNode("cameras", "Cameras", scene.camera ? [scene.camera] : [], (camera) => CreateWatchedNameDisplayInfo(watcherService, camera, () => camera.name || "Camera"), CameraIcon),
9291
+ ];
9292
+ },
9293
+ getSnapshot: (scene) => (scene.camera ? [scene.camera] : []),
9294
+ });
9295
+ const removeRegistration = explorerService.addItemCommand({
9296
+ predicate: (entity) => IsCamera(entity) && GetSceneContexts(engineContext.engine).filter((scene) => scene.camera === entity).length === 1,
9297
+ order: 10000,
9298
+ getCommand: (camera) => ({
9299
+ type: "action",
9300
+ mode: "contextMenu",
9301
+ displayName: "Remove from Scene",
9302
+ icon: DeleteRegular,
9303
+ hotKey: { keyCode: "Delete" },
9304
+ execute: () => {
9305
+ const owningScenes = GetSceneContexts(engineContext.engine).filter((candidate) => candidate.camera === camera);
9306
+ if (owningScenes.length === 1) {
9307
+ const selectedEntity = selectionService.selectedEntity;
9308
+ const selectionWasRemoved = selectedEntity === camera || (IsSceneNode(selectedEntity) && IsSceneNodeDescendantOf(selectedEntity, camera));
9309
+ removeFromScene(owningScenes[0], camera);
9310
+ ClearRemovedSelection(selectionService, engineContext, selectionWasRemoved);
9311
+ }
9312
+ },
9313
+ }),
9314
+ });
9315
+ return {
9316
+ dispose: () => {
9317
+ removeRegistration.dispose();
9318
+ providerRegistration.dispose();
9319
+ },
9320
+ };
9321
+ },
9322
+ };
9323
+
9324
+ const LightIcon = () => jsx(LightbulbRegular, { color: tokens.colorPaletteYellowForeground2 });
9325
+ const LightExplorerServiceDefinition = {
9326
+ friendlyName: "Babylon Lite Light Explorer",
9327
+ consumes: [EngineExplorerServiceIdentity, ExplorerServiceIdentity, SelectionServiceIdentity, EngineContextIdentity],
9328
+ factory: (engineExplorerService, explorerService, selectionService, engineContext) => {
9329
+ const providerRegistration = engineExplorerService.addRenderingContextNodeProvider({
9330
+ order: 20,
9331
+ predicate: IsSceneContext,
9332
+ getNodes: (scene) => {
9333
+ return [CreateSceneExplorerSectionNode("lights", "Lights", scene.lights, (light, index) => ({ name: GetLightDisplayName(light, index) }), LightIcon)];
9334
+ },
9335
+ getSnapshot: (scene) => scene.lights,
9336
+ });
9337
+ const removeRegistration = explorerService.addItemCommand({
9338
+ predicate: (entity) => IsLight(entity) && GetSceneContexts(engineContext.engine).filter((scene) => scene.lights.includes(entity)).length === 1,
9339
+ order: 10000,
9340
+ getCommand: (light) => ({
9341
+ type: "action",
9342
+ mode: "contextMenu",
9343
+ displayName: "Remove from Scene",
9344
+ icon: DeleteRegular,
9345
+ hotKey: { keyCode: "Delete" },
9346
+ execute: () => {
9347
+ const owningScenes = GetSceneContexts(engineContext.engine).filter((candidate) => candidate.lights.includes(light));
9348
+ if (owningScenes.length === 1) {
9349
+ const selectedEntity = selectionService.selectedEntity;
9350
+ const selectionWasRemoved = selectedEntity === light || selectedEntity === light.shadowGenerator || (IsSceneNode(selectedEntity) && IsSceneNodeDescendantOf(selectedEntity, light));
9351
+ removeFromScene(owningScenes[0], light);
9352
+ ClearRemovedSelection(selectionService, engineContext, selectionWasRemoved);
9353
+ }
9354
+ },
9355
+ }),
9356
+ });
9357
+ return {
9358
+ dispose: () => {
9359
+ removeRegistration.dispose();
9360
+ providerRegistration.dispose();
9361
+ },
9362
+ };
9363
+ },
9364
+ };
9365
+
8333
9366
  const MeshIcon = createFluentIcon("Mesh", "1em", '<g transform="scale(1.25)"><path d="M14.03,3.54l-5.11-2.07c-.61-.25-1.27-.25-1.88,0L1.93,3.54c-.57.23-.94.78-.94,1.39v6.15c0,.61.37,1.16.94,1.39l5.11,2.07c.3.12.62.18.94.18s.64-.06.94-.18l5.12-2.07c.57-.23.94-.78.94-1.39v-6.15c0-.61-.37-1.16-.94-1.39ZM13.97,7.71l-2.11.86v-2.71l2.11-.86v2.71ZM1.99,5l2.11.86v2.71l-2.11-.86v-2.71ZM11.35,4.98l-2.04-.83,1.78-.72,2.04.83-1.78.72ZM10.02,5.52l-2.04.83-2.04-.83,2.04-.83,2.04.83ZM4.6,4.98l-1.78-.72,2.04-.83,1.78.72-2.04.83ZM5.1,6.26l2.38.96v2.71l-2.38-.96v-2.71ZM8.48,7.22l2.38-.96v2.71l-2.38.96v-2.71ZM7.41,2.39c.18-.07.37-.11.56-.11s.38.04.56.11l1.22.49-1.78.72-1.79-.72,1.22-.49ZM1.99,11.07v-2.29l2.11.86v2.62l-1.8-.73c-.19-.08-.31-.26-.31-.46ZM5.1,12.67v-2.62l2.38.96v2.61s-.04,0-.06-.01l-2.31-.94ZM8.54,13.61s-.04,0-.06.01v-2.61l2.38-.96v2.62l-2.31.94ZM13.66,11.54l-1.8.73v-2.62l2.11-.86v2.29c0,.2-.12.39-.31.46Z"/></g>');
8334
9367
  createFluentIcon("Translate", "1em", '<g transform="scale(0.833)"><path d="M20.16,12.98l-2.75-2.75c-.29-.29-.77-.29-1.06,0-.29.29-.29.77,0,1.06l1.47,1.47h-6.69v-6.69l1.47,1.47c.29.29.77.29,1.06,0,.29-.29.29-.77,0-1.06l-2.75-2.75c-.14-.14-.33-.22-.53-.22s-.39.08-.53.22l-2.75,2.75c-.29.29-.29.77,0,1.06.29.29.77.29,1.06,0l1.47-1.47v7.13l-3.52,3.52v-2.08c0-.41-.34-.75-.75-.75s-.75.34-.75.75v3.89c0,.2.08.39.22.53.14.14.33.22.53.22h3.89c.41,0,.75-.34.75-.75s-.34-.75-.75-.75h-2.08s3.52-3.52,3.52-3.52h7.13l-1.47,1.47c-.29.29-.29.77,0,1.06s.77.29,1.06,0l2.75-2.75c.14-.14.22-.33.22-.53s-.08-.39-.22-.53Z" /></g>');
8335
9368
  const MaterialIcon = createFluentIcon("Material", "1em", '<g transform="scale(1.25)"><path d="M14.74,6.3c-.09-.36-.38-.64-.75-.72-.04-.09-.08-.18-.12-.27.1-.15.16-.32.16-.51,0-.18-.05-.34-.13-.48-1.23-1.97-3.41-3.28-5.9-3.28C4.16,1.04,1.04,4.16,1.04,7.99c0,.39.23.72.57.88.02.12.03.25.06.37-.18.18-.3.42-.3.7,0,.11.02.21.06.31.94,2.74,3.53,4.71,6.58,4.71,3.84,0,6.96-3.12,6.96-6.96,0-.59-.08-1.16-.22-1.7ZM2.07,8.58c-.02-.19-.03-.39-.03-.58,0-3.29,2.67-5.96,5.96-5.96,2.23,0,4.17,1.23,5.2,3.05.05.18-.07.45-.3.75-.57-.73-1.45-1.21-2.45-1.21-1.72,0-3.12,1.4-3.12,3.11,0,.33.07.65.16.95-3.05.82-5.17.52-5.42-.11ZM12.56,7.75c0,1.17-.95,2.11-2.11,2.11s-2.12-.95-2.12-2.11.95-2.11,2.12-2.11,2.11.95,2.11,2.11ZM8,13.96c-2.6,0-4.81-1.68-5.62-4.01.5.16,1.11.24,1.79.24,1.15,0,2.49-.22,3.79-.59.57.76,1.47,1.26,2.49,1.26,1.72,0,3.11-1.4,3.11-3.11,0-.34-.07-.65-.17-.96.13-.13.24-.26.34-.39.14.51.22,1.04.22,1.6,0,3.29-2.67,5.96-5.96,5.96Z"/></g>');
@@ -8339,38 +9372,6 @@ createFluentIcon("BreakTangent", "20", '<g transform="scale(0.5)"><path d="M27.1
8339
9372
  createFluentIcon("UnifyTangent", "20", '<g transform="scale(0.5)"><path d="M27.94,18.28a1.49,1.49,0,0,0-1.41,1h-5l-1.62-1.63-1.62,1.63h-5a1.5,1.5,0,1,0,0,1h5l1.62,1.62,1.62-1.62h5a1.5,1.5,0,1,0,1.41-2Z"/></g>');
8340
9373
  createFluentIcon("StepTangent", "20", '<g transform="scale(0.5)"><path d="M29,16.71a1.5,1.5,0,1,0-2,1.41v5.67H11v1H28V18.12A1.51,1.51,0,0,0,29,16.71Z"/></g>');
8341
9374
 
8342
- /**
8343
- * Returns whether a rendering context is a scene context.
8344
- * @param context The rendering context to test.
8345
- * @returns Whether the context is a scene context.
8346
- */
8347
- function IsSceneContext(context) {
8348
- return getRenderingContextKind(context) === "scene";
8349
- }
8350
- /**
8351
- * Creates an Explorer section containing the supplied scene entities.
8352
- * @param id The stable section identifier.
8353
- * @param displayName The section's display name.
8354
- * @param entities The entities displayed by the section.
8355
- * @param getEntityDisplayInfo Gets the display information for an entity.
8356
- * @param entityIcon The optional icon component for the entities.
8357
- * @returns The Explorer node description for the section.
8358
- */
8359
- function CreateSceneExplorerSectionNode(id, displayName, entities, getEntityDisplayInfo, entityIcon) {
8360
- return {
8361
- id,
8362
- kind: "group",
8363
- getDisplayInfo: () => ({ name: displayName }),
8364
- getChildren: () => entities.map((entity, index) => ({
8365
- id: GetEntityId(entity).toString(),
8366
- kind: "item",
8367
- entity,
8368
- icon: entityIcon,
8369
- getDisplayInfo: () => getEntityDisplayInfo(entity, index),
8370
- })),
8371
- };
8372
- }
8373
-
8374
9375
  /**
8375
9376
  * Gets the unique materials referenced by a scene's meshes.
8376
9377
  * @param scene The scene to inspect.
@@ -8405,17 +9406,178 @@ const MaterialExplorerServiceDefinition = {
8405
9406
  }),
8406
9407
  };
8407
9408
 
9409
+ const NodeTopologyMarkers = new WeakMap();
9410
+ function GetNodeTopologyMarker(node) {
9411
+ const parent = IsSceneNode(node.parent) ? node.parent : null;
9412
+ const existing = NodeTopologyMarkers.get(node);
9413
+ if (existing?.parent === parent) {
9414
+ return existing.marker;
9415
+ }
9416
+ const marker = {};
9417
+ NodeTopologyMarkers.set(node, { parent, marker });
9418
+ return marker;
9419
+ }
9420
+ function CreateNodeDescription(node, watcherService) {
9421
+ return {
9422
+ id: GetEntityId(node).toString(),
9423
+ kind: "item",
9424
+ entity: node,
9425
+ icon: () => (IsMesh(node) ? jsx(MeshIcon, { color: tokens.colorPaletteBlueForeground2 }) : jsx(MyLocationRegular, { color: tokens.colorPaletteBlueForeground2 })),
9426
+ getDisplayInfo: () => CreateWatchedNameDisplayInfo(watcherService, node, () => node.name || (IsMesh(node) ? "Unnamed Mesh" : "Unnamed Transform Node")),
9427
+ getChildren: () => node.children.filter(IsSceneNode).map((child) => CreateNodeDescription(child, watcherService)),
9428
+ };
9429
+ }
9430
+ function IsNodeInScene(scene, node) {
9431
+ return GetSceneNodeRoots(scene).some((root) => root === node || IsSceneNodeDescendantOf(node, root));
9432
+ }
8408
9433
  const MeshExplorerServiceDefinition = {
8409
- friendlyName: "Babylon Lite Mesh Explorer",
8410
- consumes: [EngineExplorerServiceIdentity, WatcherServiceIdentity],
8411
- factory: (engineExplorerService, watcherService) => engineExplorerService.addRenderingContextNodeProvider({
8412
- order: 0,
8413
- predicate: IsSceneContext,
8414
- getNodes: (scene) => [
8415
- CreateSceneExplorerSectionNode("meshes", "Meshes", scene.meshes, (mesh) => CreateWatchedNameDisplayInfo(watcherService, mesh, () => mesh.name), () => jsx(MeshIcon, { color: tokens.colorPaletteBlueForeground2 })),
8416
- ],
8417
- getSnapshot: (scene) => scene.meshes,
8418
- }),
9434
+ friendlyName: "Babylon Lite Scene Node Explorer",
9435
+ consumes: [EngineExplorerServiceIdentity, ExplorerServiceIdentity, WatcherServiceIdentity, SelectionServiceIdentity, EngineContextIdentity],
9436
+ factory: (engineExplorerService, explorerService, watcherService, selectionService, engineContext) => {
9437
+ const providerRegistration = engineExplorerService.addRenderingContextNodeProvider({
9438
+ order: 0,
9439
+ predicate: IsSceneContext,
9440
+ getNodes: (scene) => {
9441
+ return [
9442
+ {
9443
+ id: "nodes",
9444
+ kind: "group",
9445
+ getDisplayInfo: () => ({ name: "Nodes" }),
9446
+ getChildren: () => GetSceneNodeRoots(scene).map((node) => CreateNodeDescription(node, watcherService)),
9447
+ dragDropConfig: {
9448
+ canDrag: (entity) => IsSceneNode(entity) && IsNodeInScene(scene, entity),
9449
+ canDrop: (draggedEntity, targetEntity) => IsSceneNode(draggedEntity) &&
9450
+ IsNodeInScene(scene, draggedEntity) &&
9451
+ (targetEntity === null ||
9452
+ (IsSceneNode(targetEntity) &&
9453
+ IsNodeInScene(scene, targetEntity) &&
9454
+ draggedEntity !== targetEntity &&
9455
+ !IsSceneNodeDescendantOf(targetEntity, draggedEntity))) &&
9456
+ draggedEntity.parent !== targetEntity,
9457
+ onDrop: (draggedEntity, targetEntity) => {
9458
+ if (IsSceneNode(draggedEntity) && (targetEntity === null || IsSceneNode(targetEntity))) {
9459
+ setParent(draggedEntity, targetEntity);
9460
+ }
9461
+ },
9462
+ },
9463
+ },
9464
+ ];
9465
+ },
9466
+ getSnapshot: (scene) => GetSceneNodeRoots(scene)
9467
+ .flatMap((root) => [root, ...GetNodeDescendants(root)])
9468
+ .flatMap((node) => [node, GetNodeTopologyMarker(node)]),
9469
+ });
9470
+ const visibilityRegistration = explorerService.addItemCommand({
9471
+ predicate: (entity) => IsSceneNode(entity) && GetOwningScenes(engineContext, entity).length > 0,
9472
+ order: 1100,
9473
+ getCommand: (node) => {
9474
+ const onChange = new Observable();
9475
+ const visibilityWatcher = watcherService.watchValue(() => node.visible !== false, () => onChange.notifyObservers());
9476
+ return {
9477
+ type: "toggle",
9478
+ mode: "contextMenu",
9479
+ get displayName() {
9480
+ return node.visible === false ? "Show Node" : "Hide Node";
9481
+ },
9482
+ icon: () => (node.visible === false ? jsx(EyeOffRegular, {}) : jsx(EyeRegular, {})),
9483
+ get isEnabled() {
9484
+ return node.visible !== false;
9485
+ },
9486
+ set isEnabled(visible) {
9487
+ setSubtreeVisible(node, visible);
9488
+ },
9489
+ onChange,
9490
+ dispose: () => {
9491
+ visibilityWatcher.dispose();
9492
+ onChange.clear();
9493
+ },
9494
+ };
9495
+ },
9496
+ });
9497
+ const removeRegistration = explorerService.addItemCommand({
9498
+ predicate: (entity) => IsSceneNode(entity) && GetOwningScenes(engineContext, entity).length === 1,
9499
+ order: 10000,
9500
+ getCommand: (node) => ({
9501
+ type: "action",
9502
+ mode: "contextMenu",
9503
+ displayName: "Remove from Scene",
9504
+ icon: DeleteRegular,
9505
+ hotKey: { keyCode: "Delete" },
9506
+ execute: () => {
9507
+ const owningScenes = GetOwningScenes(engineContext, node);
9508
+ const [scene] = owningScenes;
9509
+ if (scene && owningScenes.length === 1) {
9510
+ const selectedEntity = selectionService.selectedEntity;
9511
+ const selectionWasRemoved = selectedEntity === node || (IsSceneNode(selectedEntity) && IsSceneNodeDescendantOf(selectedEntity, node));
9512
+ if (node.parent) {
9513
+ setParent(node, null);
9514
+ }
9515
+ removeFromScene(scene, node);
9516
+ ClearRemovedSelection(selectionService, engineContext, selectionWasRemoved);
9517
+ }
9518
+ },
9519
+ }),
9520
+ });
9521
+ return {
9522
+ dispose: () => {
9523
+ removeRegistration.dispose();
9524
+ visibilityRegistration.dispose();
9525
+ providerRegistration.dispose();
9526
+ },
9527
+ };
9528
+ },
9529
+ };
9530
+ function GetNodeDescendants(node) {
9531
+ return node.children.filter(IsSceneNode).flatMap((child) => [child, ...GetNodeDescendants(child)]);
9532
+ }
9533
+ function GetOwningScenes(engineContext, node) {
9534
+ return GetSceneContexts(engineContext.engine).filter((scene) => GetSceneNodeRoots(scene).some((root) => root === node || IsSceneNodeDescendantOf(node, root)));
9535
+ }
9536
+
9537
+ const ShadowGeneratorIcon = () => jsx(WeatherMoonRegular, { color: tokens.colorPaletteYellowForeground2 });
9538
+ const ShadowGeneratorExplorerServiceDefinition = {
9539
+ friendlyName: "Babylon Lite Shadow Generator Explorer",
9540
+ consumes: [EngineExplorerServiceIdentity, ExplorerServiceIdentity, SelectionServiceIdentity, EngineContextIdentity],
9541
+ factory: (engineExplorerService, explorerService, selectionService, engineContext) => {
9542
+ const providerRegistration = engineExplorerService.addRenderingContextNodeProvider({
9543
+ order: 30,
9544
+ predicate: IsSceneContext,
9545
+ getNodes: (scene) => {
9546
+ return [
9547
+ CreateSceneExplorerSectionNode("shadow-generators", "Shadow Generators", scene.shadowGenerators, (shadowGenerator) => ({ name: GetShadowGeneratorDisplayName(scene, shadowGenerator) }), ShadowGeneratorIcon),
9548
+ ];
9549
+ },
9550
+ getSnapshot: (scene) => scene.shadowGenerators,
9551
+ });
9552
+ const removeRegistration = explorerService.addItemCommand({
9553
+ predicate: (entity) => typeof entity === "object" &&
9554
+ entity !== null &&
9555
+ GetSceneContexts(engineContext.engine).filter((scene) => scene.shadowGenerators.some((shadowGenerator) => shadowGenerator === entity)).length === 1,
9556
+ order: 10000,
9557
+ getCommand: (shadowGenerator) => ({
9558
+ type: "action",
9559
+ mode: "contextMenu",
9560
+ displayName: "Remove from Scene",
9561
+ icon: DeleteRegular,
9562
+ hotKey: { keyCode: "Delete" },
9563
+ execute: () => {
9564
+ const owningScenes = GetSceneContexts(engineContext.engine).filter((candidate) => candidate.shadowGenerators.includes(shadowGenerator));
9565
+ if (owningScenes.length === 1) {
9566
+ if (selectionService.selectedEntity === shadowGenerator) {
9567
+ selectionService.selectedEntity = null;
9568
+ }
9569
+ removeFromScene(owningScenes[0], shadowGenerator);
9570
+ }
9571
+ },
9572
+ }),
9573
+ });
9574
+ return {
9575
+ dispose: () => {
9576
+ removeRegistration.dispose();
9577
+ providerRegistration.dispose();
9578
+ },
9579
+ };
9580
+ },
8419
9581
  };
8420
9582
 
8421
9583
  const TextLayerIcon = () => jsx(TextTRegular, {});
@@ -8489,13 +9651,20 @@ function ShowInspector(engine, options = {}) {
8489
9651
  watcherServiceDefinition,
8490
9652
  EngineExplorerServiceDefinition,
8491
9653
  MeshExplorerServiceDefinition,
9654
+ CameraExplorerServiceDefinition,
9655
+ LightExplorerServiceDefinition,
9656
+ ShadowGeneratorExplorerServiceDefinition,
8492
9657
  MaterialExplorerServiceDefinition,
8493
9658
  TextureExplorerServiceDefinition,
8494
9659
  TextLayerExplorerServiceDefinition,
8495
9660
  PropertiesServiceDefinition,
8496
9661
  EnginePropertiesServiceDefinition,
8497
9662
  RenderingContextPropertiesServiceDefinition,
9663
+ SceneNodePropertiesServiceDefinition,
8498
9664
  MeshPropertiesServiceDefinition,
9665
+ CameraPropertiesServiceDefinition,
9666
+ LightPropertiesServiceDefinition,
9667
+ ShadowGeneratorPropertiesServiceDefinition,
8499
9668
  MaterialPropertiesServiceDefinition,
8500
9669
  TexturePropertiesServiceDefinition,
8501
9670
  TextLayerPropertiesServiceDefinition,
@@ -8515,4 +9684,4 @@ function ShowInspector(engine, options = {}) {
8515
9684
  }
8516
9685
 
8517
9686
  export { PropertyLine as $, Accordion as A, BooleanBadgePropertyLine as B, Checkbox as C, DraggableLine as D, EngineContextIdentity as E, FileUploadLine as F, GetPropertyDescriptor as G, HexPropertyLine as H, InfoLabel as I, MakeModularBridge as J, MakeModularTool as K, Link as L, MakeDialogTeachingMoment as M, MakePopoverTeachingMoment as N, MakeTeachingMoment as O, MessageBar as P, NumberDropdown as Q, NumberDropdownPropertyLine as R, ShellServiceIdentity as S, NumberInputPropertyLine as T, ObservableCollection as U, Pane as V, PlaceholderPropertyLine as W, Popover as X, PositionedPopover as Y, PropertiesServiceIdentity as Z, PropertyContext as _, AccordionSection as a, SearchBar as a0, SearchBox as a1, SelectionServiceDefinition as a2, SelectionServiceIdentity as a3, SettingsServiceIdentity as a4, SettingsStore as a5, SettingsStoreIdentity as a6, ShowInspector as a7, SidePaneContainer as a8, Slider as a9, useInterceptObservable as aA, useKeyListener as aB, useKeyState as aC, useObservableCollection as aD, useObservableState as aE, useOrderedObservableCollection as aF, usePollingObservable as aG, usePropertyChangedNotifier as aH, useResource as aI, useTheme as aJ, useThemeMode as aK, useToast as aL, SpinButton as aa, SpinButtonPropertyLine as ab, StringDropdown as ac, StringDropdownPropertyLine as ad, StringifiedPropertyLine as ae, Switch as af, SwitchPropertyLine as ag, SyncedSliderInput as ah, SyncedSliderPropertyLine as ai, TeachingMoment as aj, TextAreaPropertyLine as ak, TextInput as al, TextInputPropertyLine as am, TextPropertyLine as an, Textarea as ao, Theme as ap, ThemeServiceIdentity as aq, ToastProvider as ar, ToggleButton as as, Tooltip as at, UploadButton as au, WatcherServiceIdentity as av, useAngleConverters as aw, useAsyncResource as ax, useEventListener as ay, useEventfulState as az, AccordionSectionItem as b, BridgeCommandRegistryIdentity as c, BuiltInsExtensionFeed as d, Button as e, ButtonLine as f, CheckboxPropertyLine as g, ChildWindow as h, Collapse as i, ComboBox as j, ComboBoxPropertyLine as k, ConstructorFactory as l, Dropdown as m, EngineExplorerServiceIdentity as n, EntitySelector as o, ErrorBoundary as p, ExplorerServiceIdentity as q, ExtensibleAccordion as r, InterceptFunction as s, InterceptProperty as t, useExtensionManager as u, IsPropertyReadonly as v, LineContainer as w, LinkPropertyLine as x, List as y, MakeLazyComponent as z };
8518
- //# sourceMappingURL=index-DmQN3QdU.js.map
9687
+ //# sourceMappingURL=index-6eEEtyAL.js.map