@vizij/render 0.0.7 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,19 +1,10 @@
1
1
  // src/vizij.tsx
2
- import {
3
- Suspense,
4
- memo as memo6,
5
- useContext as useContext3,
6
- useEffect as useEffect6
7
- } from "react";
2
+ import { Suspense, memo as memo6, useContext as useContext3, useEffect as useEffect7 } from "react";
8
3
  import { ErrorBoundary } from "react-error-boundary";
9
- import {
10
- Object3D as Object3D4,
11
- SRGBColorSpace,
12
- NoToneMapping
13
- } from "three";
4
+ import { Object3D as Object3D4, SRGBColorSpace, NoToneMapping } from "three";
14
5
  import { Canvas, useThree } from "@react-three/fiber";
15
6
  import { Line as Line3, OrthographicCamera, Text } from "@react-three/drei";
16
- import { useShallow as useShallow6 } from "zustand/react/shallow";
7
+ import { useShallow as useShallow7 } from "zustand/react/shallow";
17
8
 
18
9
  // src/renderables/renderable.tsx
19
10
  import { memo as memo5, useMemo as useMemo5 } from "react";
@@ -35,13 +26,7 @@ function useVizijStore(selector) {
35
26
  }
36
27
 
37
28
  // src/renderables/group.tsx
38
- import {
39
- memo,
40
- useCallback,
41
- useEffect as useEffect2,
42
- useRef,
43
- useMemo
44
- } from "react";
29
+ import { memo, useCallback, useEffect as useEffect2, useRef, useMemo } from "react";
45
30
  import * as THREE from "three";
46
31
  import { useShallow } from "zustand/react/shallow";
47
32
  import {
@@ -138,7 +123,7 @@ function InnerRenderedGroup({
138
123
  namespace,
139
124
  chain
140
125
  }) {
141
- const ref = useRef();
126
+ const ref = useRef(null);
142
127
  const group = useVizijStore(useShallow((state) => state.world[id]));
143
128
  const refIsNull = !group.refs[namespace]?.current;
144
129
  const animatables = useVizijStore(useShallow((state) => state.animatables));
@@ -249,13 +234,7 @@ function InnerRenderedGroup({
249
234
  var RenderedGroup = memo(InnerRenderedGroup);
250
235
 
251
236
  // src/renderables/ellipse.tsx
252
- import {
253
- memo as memo2,
254
- useCallback as useCallback2,
255
- useEffect as useEffect3,
256
- useRef as useRef2,
257
- useMemo as useMemo2
258
- } from "react";
237
+ import { memo as memo2, useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useMemo as useMemo2 } from "react";
259
238
  import { useShallow as useShallow2 } from "zustand/react/shallow";
260
239
  import {
261
240
  instanceOfRawNumber as instanceOfRawNumber2,
@@ -272,9 +251,11 @@ function InnerRenderedEllipse({
272
251
  namespace,
273
252
  chain
274
253
  }) {
275
- const ellipseRef = useRef2();
276
- const materialRef = useRef2();
277
- const lineRef = useRef2();
254
+ const ellipseRef = useRef2(null);
255
+ const materialRef = useRef2(
256
+ null
257
+ );
258
+ const lineRef = useRef2(null);
278
259
  const strokeOffsetRef = useRef2(0);
279
260
  const strokeWidthRef = useRef2(0);
280
261
  const onElementClick = useVizijStore(
@@ -547,13 +528,7 @@ var showLine = (ellipse) => {
547
528
  };
548
529
 
549
530
  // src/renderables/rectangle.tsx
550
- import {
551
- memo as memo3,
552
- useCallback as useCallback3,
553
- useEffect as useEffect4,
554
- useRef as useRef3,
555
- useMemo as useMemo3
556
- } from "react";
531
+ import { memo as memo3, useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useMemo as useMemo3 } from "react";
557
532
  import { useShallow as useShallow3 } from "zustand/react/shallow";
558
533
  import {
559
534
  instanceOfRawNumber as instanceOfRawNumber3,
@@ -570,9 +545,11 @@ function InnerRenderedRectangle({
570
545
  namespace,
571
546
  chain
572
547
  }) {
573
- const rectangleRef = useRef3();
574
- const materialRef = useRef3();
575
- const lineRef = useRef3();
548
+ const rectangleRef = useRef3(null);
549
+ const materialRef = useRef3(
550
+ null
551
+ );
552
+ const lineRef = useRef3(null);
576
553
  const strokeOffsetRef = useRef3(0);
577
554
  const strokeWidthRef = useRef3(0);
578
555
  const onElementClick = useVizijStore(
@@ -846,13 +823,7 @@ var showLine2 = (rectangle) => {
846
823
  };
847
824
 
848
825
  // src/renderables/shape.tsx
849
- import {
850
- memo as memo4,
851
- useCallback as useCallback4,
852
- useRef as useRef4,
853
- useMemo as useMemo4,
854
- useEffect as useEffect5
855
- } from "react";
826
+ import { memo as memo4, useCallback as useCallback4, useRef as useRef4, useMemo as useMemo4, useEffect as useEffect5 } from "react";
856
827
  import * as THREE2 from "three";
857
828
  import { useShallow as useShallow4 } from "zustand/react/shallow";
858
829
  import {
@@ -869,8 +840,8 @@ function InnerRenderedShape({
869
840
  namespace,
870
841
  chain
871
842
  }) {
872
- const refGroup = useRef4();
873
- const ref = useRef4();
843
+ const refGroup = useRef4(null);
844
+ const ref = useRef4(null);
874
845
  const shape = useVizijStore(useShallow4((state) => state.world[id]));
875
846
  const refs = useVizijStore(
876
847
  useShallow4((state) => state.world[id].refs)
@@ -911,7 +882,7 @@ function InnerRenderedShape({
911
882
  }),
912
883
  [shape, animatableValues, selectionData]
913
884
  );
914
- const material = useRef4();
885
+ const material = useRef4(null);
915
886
  const morphTargetSettings = useMemo4(() => {
916
887
  if (shape.morphTargets) {
917
888
  const dictionary = shape.morphTargets.reduce(
@@ -1432,6 +1403,35 @@ function createAnimatable(value) {
1432
1403
  }
1433
1404
  createAnimatable({ type: "euler", name: "Rotation" });
1434
1405
 
1406
+ // src/functions/exportable-bodies.ts
1407
+ function asGroupEntries(world) {
1408
+ return Object.values(world).filter((entry) => entry.type === "group");
1409
+ }
1410
+ function selectExportableGroupEntries(world, filterIds) {
1411
+ const groupEntries = asGroupEntries(world);
1412
+ const filterSet = Array.isArray(filterIds) && filterIds.length > 0 ? new Set(filterIds) : null;
1413
+ if (filterSet) {
1414
+ return groupEntries.filter((entry) => filterSet.has(entry.id));
1415
+ }
1416
+ const rootBoundsGroups = groupEntries.filter(
1417
+ (entry) => Boolean(entry.rootBounds)
1418
+ );
1419
+ if (rootBoundsGroups.length > 0) {
1420
+ return rootBoundsGroups;
1421
+ }
1422
+ const explicitRootGroups = groupEntries.filter(
1423
+ (entry) => entry.root === true
1424
+ );
1425
+ if (explicitRootGroups.length > 0) {
1426
+ return explicitRootGroups;
1427
+ }
1428
+ const topLevelGroups = groupEntries.filter((entry) => !entry.parent);
1429
+ if (topLevelGroups.length > 0) {
1430
+ return topLevelGroups;
1431
+ }
1432
+ return groupEntries;
1433
+ }
1434
+
1435
1435
  // src/store.ts
1436
1436
  THREE3.Object3D.DEFAULT_UP.set(0, 0, 1);
1437
1437
  enableMapSet();
@@ -1507,23 +1507,17 @@ var VizijSlice = (set, get) => ({
1507
1507
  },
1508
1508
  getExportableBodies: (filterIds) => {
1509
1509
  const worldData = get().world;
1510
- if (!filterIds) {
1511
- const bodies = Object.values(worldData).filter((entry) => entry.type === "group" && entry.rootBounds).map((entry) => {
1512
- const firstNs = Object.keys(entry.refs)[0];
1513
- const refGroup = entry.refs[firstNs].current;
1514
- return refGroup;
1515
- });
1516
- return bodies;
1517
- } else {
1518
- const bodies = Object.values(worldData).filter(
1519
- (entry) => entry.type === "group" && entry.rootBounds && filterIds.includes(entry.id)
1520
- ).map((entry) => {
1521
- const firstNs = Object.keys(entry.refs)[0];
1522
- const refGroup = entry.refs[firstNs].current;
1523
- return refGroup;
1524
- });
1525
- return bodies;
1526
- }
1510
+ const candidateGroups = selectExportableGroupEntries(
1511
+ worldData,
1512
+ filterIds
1513
+ );
1514
+ return candidateGroups.flatMap((entry) => {
1515
+ const refs = Object.values(
1516
+ entry.refs ?? {}
1517
+ );
1518
+ const resolved = refs.find((ref) => ref?.current)?.current ?? null;
1519
+ return resolved ? [resolved] : [];
1520
+ });
1527
1521
  },
1528
1522
  setGeometry: (id, geometry) => {
1529
1523
  set(
@@ -1557,6 +1551,19 @@ var VizijSlice = (set, get) => ({
1557
1551
  })
1558
1552
  );
1559
1553
  },
1554
+ setValues: (writes = []) => {
1555
+ if (writes.length === 0) {
1556
+ return;
1557
+ }
1558
+ set(
1559
+ produce((state) => {
1560
+ writes.forEach(({ id, namespace, value }) => {
1561
+ const lookupId = getLookup2(namespace, id);
1562
+ state.values.set(lookupId, value);
1563
+ });
1564
+ })
1565
+ );
1566
+ },
1560
1567
  setWorldElementName: (id, value) => {
1561
1568
  set(
1562
1569
  produce((state) => {
@@ -1751,8 +1758,117 @@ var createVizijStore = (initial) => create()(
1751
1758
  }))
1752
1759
  );
1753
1760
 
1761
+ // src/effects/selection-glow-effect.tsx
1762
+ import { Fragment as Fragment4, useEffect as useEffect6, useMemo as useMemo6, useRef as useRef5 } from "react";
1763
+ import { useFrame } from "@react-three/fiber";
1764
+ import { useShallow as useShallow6 } from "zustand/react/shallow";
1765
+ import {
1766
+ AdditiveBlending,
1767
+ Color,
1768
+ EdgesGeometry,
1769
+ LineBasicMaterial,
1770
+ Matrix4,
1771
+ Quaternion,
1772
+ Vector3
1773
+ } from "three";
1774
+ import { jsx as jsx6 } from "react/jsx-runtime";
1775
+ function SelectionGlowEffect({
1776
+ enabled = false,
1777
+ color = "#ff1010ff",
1778
+ opacity = 0.9,
1779
+ thresholdAngle = 2
1780
+ }) {
1781
+ const selections = useVizijStore(
1782
+ useShallow6(
1783
+ (state) => enabled ? state.elementSelection ?? [] : []
1784
+ )
1785
+ );
1786
+ if (!enabled || selections.length === 0) {
1787
+ return null;
1788
+ }
1789
+ return /* @__PURE__ */ jsx6(Fragment4, { children: selections.map((selection) => /* @__PURE__ */ jsx6(
1790
+ SelectionOutline,
1791
+ {
1792
+ selection,
1793
+ color: selection.color ?? color,
1794
+ opacity,
1795
+ thresholdAngle
1796
+ },
1797
+ `${selection.namespace}:${selection.id}`
1798
+ )) });
1799
+ }
1800
+ function SelectionOutline({
1801
+ selection,
1802
+ color,
1803
+ opacity,
1804
+ thresholdAngle
1805
+ }) {
1806
+ const target = useVizijStore(
1807
+ useShallow6((state) => {
1808
+ const entry = state.world[selection.id];
1809
+ const ref = entry?.refs?.[selection.namespace];
1810
+ const geometry = entry?.geometry ?? null;
1811
+ return { ref, geometry };
1812
+ })
1813
+ );
1814
+ const sourceRef = target.ref;
1815
+ const edgesGeometry = useMemo6(() => {
1816
+ if (!target.geometry) return null;
1817
+ const edges = new EdgesGeometry(target.geometry, thresholdAngle);
1818
+ return edges;
1819
+ }, [target.geometry, thresholdAngle]);
1820
+ useEffect6(() => () => edgesGeometry?.dispose(), [edgesGeometry]);
1821
+ const material = useMemo6(() => {
1822
+ const mat = new LineBasicMaterial({
1823
+ color: new Color(color),
1824
+ transparent: true,
1825
+ opacity,
1826
+ blending: AdditiveBlending,
1827
+ depthTest: false,
1828
+ depthWrite: false,
1829
+ toneMapped: false
1830
+ });
1831
+ return mat;
1832
+ }, [color, opacity]);
1833
+ useEffect6(() => () => material.dispose(), [material]);
1834
+ const lineRef = useRef5(null);
1835
+ useFrame(() => {
1836
+ const source = sourceRef?.current;
1837
+ const line = lineRef.current;
1838
+ if (!source || !line) return;
1839
+ copyWorldTransform(source, line);
1840
+ line.visible = source.visible;
1841
+ });
1842
+ if (!sourceRef || !edgesGeometry) {
1843
+ return null;
1844
+ }
1845
+ return /* @__PURE__ */ jsx6(
1846
+ "lineSegments",
1847
+ {
1848
+ ref: lineRef,
1849
+ geometry: edgesGeometry,
1850
+ material,
1851
+ frustumCulled: false,
1852
+ renderOrder: 1e3
1853
+ }
1854
+ );
1855
+ }
1856
+ var tempMatrix = new Matrix4();
1857
+ var tempPosition = new Vector3();
1858
+ var tempQuaternion = new Quaternion();
1859
+ var tempScale = new Vector3();
1860
+ function copyWorldTransform(source, target) {
1861
+ source.updateWorldMatrix(true, false);
1862
+ tempMatrix.copy(source.matrixWorld);
1863
+ tempMatrix.decompose(tempPosition, tempQuaternion, tempScale);
1864
+ target.position.copy(tempPosition);
1865
+ target.quaternion.copy(tempQuaternion);
1866
+ target.scale.copy(tempScale);
1867
+ target.updateMatrix();
1868
+ }
1869
+
1754
1870
  // src/vizij.tsx
1755
- import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1871
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1756
1872
  Object3D4.DEFAULT_UP.set(0, 0, 1);
1757
1873
  function Vizij({
1758
1874
  style,
@@ -1760,11 +1876,12 @@ function Vizij({
1760
1876
  rootId,
1761
1877
  namespace = "default",
1762
1878
  showSafeArea = false,
1879
+ showSelectionGlow = false,
1763
1880
  onPointerMissed
1764
1881
  }) {
1765
1882
  const ctx = useContext3(VizijContext);
1766
1883
  if (ctx) {
1767
- return /* @__PURE__ */ jsx6(
1884
+ return /* @__PURE__ */ jsx7(
1768
1885
  Canvas,
1769
1886
  {
1770
1887
  shadows: false,
@@ -1776,18 +1893,19 @@ function Vizij({
1776
1893
  toneMapping: NoToneMapping,
1777
1894
  antialias: true
1778
1895
  },
1779
- children: /* @__PURE__ */ jsx6(
1896
+ children: /* @__PURE__ */ jsx7(
1780
1897
  MemoizedInnerVizij,
1781
1898
  {
1782
1899
  rootId,
1783
1900
  namespace,
1784
- showSafeArea
1901
+ showSafeArea,
1902
+ showSelectionGlow
1785
1903
  }
1786
1904
  )
1787
1905
  }
1788
1906
  );
1789
1907
  } else {
1790
- return /* @__PURE__ */ jsx6(VizijContext.Provider, { value: useDefaultVizijStore, children: /* @__PURE__ */ jsx6(
1908
+ return /* @__PURE__ */ jsx7(VizijContext.Provider, { value: useDefaultVizijStore, children: /* @__PURE__ */ jsx7(
1791
1909
  Canvas,
1792
1910
  {
1793
1911
  style,
@@ -1798,12 +1916,13 @@ function Vizij({
1798
1916
  toneMapping: NoToneMapping,
1799
1917
  antialias: true
1800
1918
  },
1801
- children: /* @__PURE__ */ jsx6(
1919
+ children: /* @__PURE__ */ jsx7(
1802
1920
  MemoizedInnerVizij,
1803
1921
  {
1804
1922
  rootId,
1805
1923
  namespace,
1806
- showSafeArea
1924
+ showSafeArea,
1925
+ showSelectionGlow
1807
1926
  }
1808
1927
  )
1809
1928
  }
@@ -1814,15 +1933,16 @@ function InnerVizij({
1814
1933
  rootId,
1815
1934
  namespace = "default",
1816
1935
  container,
1817
- showSafeArea
1936
+ showSafeArea,
1937
+ showSelectionGlow
1818
1938
  }) {
1819
1939
  const sceneParentSizing = container ? {
1820
1940
  width: container.width * container.resolution,
1821
1941
  height: container.height * container.resolution
1822
1942
  } : void 0;
1823
- return /* @__PURE__ */ jsxs4(Fragment4, { children: [
1824
- /* @__PURE__ */ jsx6("ambientLight", { intensity: Math.PI / 2 }),
1825
- /* @__PURE__ */ jsx6(
1943
+ return /* @__PURE__ */ jsxs4(Fragment5, { children: [
1944
+ /* @__PURE__ */ jsx7("ambientLight", { intensity: Math.PI / 2 }),
1945
+ /* @__PURE__ */ jsx7(
1826
1946
  OrthographicCamera,
1827
1947
  {
1828
1948
  makeDefault: true,
@@ -1831,7 +1951,7 @@ function InnerVizij({
1831
1951
  far: 101
1832
1952
  }
1833
1953
  ),
1834
- /* @__PURE__ */ jsx6(Suspense, { fallback: null, children: /* @__PURE__ */ jsx6(
1954
+ /* @__PURE__ */ jsx7(Suspense, { fallback: null, children: /* @__PURE__ */ jsx7(
1835
1955
  World,
1836
1956
  {
1837
1957
  rootId,
@@ -1839,7 +1959,8 @@ function InnerVizij({
1839
1959
  parentSizing: sceneParentSizing
1840
1960
  }
1841
1961
  ) }),
1842
- showSafeArea && /* @__PURE__ */ jsx6(SafeAreaRenderer, { rootId })
1962
+ showSelectionGlow && /* @__PURE__ */ jsx7(SelectionGlowEffect, { enabled: true }),
1963
+ showSafeArea && /* @__PURE__ */ jsx7(SafeAreaRenderer, { rootId })
1843
1964
  ] });
1844
1965
  }
1845
1966
  var MemoizedInnerVizij = memo6(InnerVizij);
@@ -1849,7 +1970,7 @@ function InnerWorld({
1849
1970
  parentSizing
1850
1971
  }) {
1851
1972
  const [present, rootBounds] = useVizijStore(
1852
- useShallow6((state) => {
1973
+ useShallow7((state) => {
1853
1974
  const group = state.world[rootId];
1854
1975
  const bounds = group?.rootBounds ?? defaultRootBounds;
1855
1976
  return [group !== void 0, bounds];
@@ -1859,7 +1980,7 @@ function InnerWorld({
1859
1980
  camera: state.camera,
1860
1981
  size: state.size
1861
1982
  }));
1862
- useEffect6(() => {
1983
+ useEffect7(() => {
1863
1984
  const width = rootBounds.size.x;
1864
1985
  const height = rootBounds.size.y;
1865
1986
  if (camera && parentSizing === void 0 && camera.isOrthographicCamera) {
@@ -1888,8 +2009,8 @@ function InnerWorld({
1888
2009
  }
1889
2010
  }, [rootBounds, camera, parentSizing, size]);
1890
2011
  return /* @__PURE__ */ jsxs4(ErrorBoundary, { fallback: null, children: [
1891
- present && /* @__PURE__ */ jsx6(Renderable, { id: rootId, namespace, chain: [] }),
1892
- !present && /* @__PURE__ */ jsx6(
2012
+ present && /* @__PURE__ */ jsx7(Renderable, { id: rootId, namespace, chain: [] }),
2013
+ !present && /* @__PURE__ */ jsx7(
1893
2014
  Text,
1894
2015
  {
1895
2016
  position: [0, 0, 0],
@@ -1912,7 +2033,7 @@ function SafeAreaRenderer({ rootId }) {
1912
2033
  const right = rootBounds.center.x + rootBounds.size.x / 2;
1913
2034
  const top = rootBounds.center.y + rootBounds.size.y / 2;
1914
2035
  const bottom = rootBounds.center.y - rootBounds.size.y / 2;
1915
- return /* @__PURE__ */ jsx6(
2036
+ return /* @__PURE__ */ jsx7(
1916
2037
  Line3,
1917
2038
  {
1918
2039
  points: [
@@ -1943,11 +2064,11 @@ var ShapeMaterial = /* @__PURE__ */ ((ShapeMaterial2) => {
1943
2064
  })(ShapeMaterial || {});
1944
2065
 
1945
2066
  // src/hooks/use-vizij-store-subscription.ts
1946
- import { useContext as useContext4, useEffect as useEffect7 } from "react";
2067
+ import { useContext as useContext4, useEffect as useEffect8 } from "react";
1947
2068
  function useVizijStoreSubscription(selector, listener) {
1948
2069
  const store = useContext4(VizijContext);
1949
2070
  if (!store) throw new Error("Missing VizijProvider in the tree");
1950
- useEffect7(() => {
2071
+ useEffect8(() => {
1951
2072
  const initialValue = selector(store.getState());
1952
2073
  listener(initialValue);
1953
2074
  return store.subscribe(selector, listener);
@@ -2013,9 +2134,7 @@ function namespaceArrayToRefs(namespaces) {
2013
2134
  }
2014
2135
 
2015
2136
  // src/functions/gltf-loading/import-mesh.ts
2016
- import {
2017
- Object3D as Object3D5
2018
- } from "three";
2137
+ import { Object3D as Object3D5 } from "three";
2019
2138
 
2020
2139
  // src/functions/gltf-loading/import-geometry.ts
2021
2140
  function sanitizeMorphKey(name, fallbackIndex, used) {
@@ -2055,7 +2174,7 @@ function importGeometry(geometry, mesh) {
2055
2174
  type: "number",
2056
2175
  default: mesh.morphTargetInfluences?.[index] ?? 0,
2057
2176
  constraints: {
2058
- min: 0,
2177
+ min: -1,
2059
2178
  max: 1
2060
2179
  },
2061
2180
  pub: {
@@ -2185,6 +2304,15 @@ function importMesh(mesh, namespaces, colorLookup) {
2185
2304
  world = { ...world, ...newWorldItems };
2186
2305
  animatables = { ...animatables, ...newAnimatables };
2187
2306
  children.push(childId);
2307
+ } else if (shouldImportAsGroupChild(child)) {
2308
+ const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
2309
+ ...colorLookup,
2310
+ ...newColorLookup
2311
+ });
2312
+ newColorLookup = { ...newColorLookup, ...newMeshColors };
2313
+ world = { ...world, ...newWorldItems };
2314
+ animatables = { ...animatables, ...newAnimatables };
2315
+ children.push(childId);
2188
2316
  }
2189
2317
  });
2190
2318
  const newShape = {
@@ -2228,6 +2356,24 @@ function getShapeMaterial(mesh, useEmissive) {
2228
2356
  return "standard" /* Standard */;
2229
2357
  }
2230
2358
  }
2359
+ function shouldImportAsGroupChild(child) {
2360
+ if (!child.isObject3D) {
2361
+ return false;
2362
+ }
2363
+ if (child.isMesh) {
2364
+ return false;
2365
+ }
2366
+ if (child.isCamera) {
2367
+ return false;
2368
+ }
2369
+ if (child.isLight) {
2370
+ return false;
2371
+ }
2372
+ if (child.isBone) {
2373
+ return false;
2374
+ }
2375
+ return true;
2376
+ }
2231
2377
 
2232
2378
  // src/functions/gltf-loading/import-group.ts
2233
2379
  Object3D6.DEFAULT_UP.set(0, 0, 1);
@@ -2287,7 +2433,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2287
2433
  world = { ...world, ...newWorldItems };
2288
2434
  animatables = { ...animatables, ...newAnimatables };
2289
2435
  children.push(childId);
2290
- } else if (child.isGroup || child.isObject3D && child.children.length !== 0) {
2436
+ } else if (shouldImportAsGroupChild2(child)) {
2291
2437
  const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
2292
2438
  ...colorLookup,
2293
2439
  ...newColorLookup
@@ -2316,6 +2462,24 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2316
2462
  world = { ...world, [newGroup.id]: newGroup };
2317
2463
  return [world, animatables, newGroup.id, newColorLookup];
2318
2464
  }
2465
+ function shouldImportAsGroupChild2(child) {
2466
+ if (!child.isObject3D) {
2467
+ return false;
2468
+ }
2469
+ if (child.isMesh) {
2470
+ return false;
2471
+ }
2472
+ if (child.isCamera) {
2473
+ return false;
2474
+ }
2475
+ if (child.isLight) {
2476
+ return false;
2477
+ }
2478
+ if (child.isBone) {
2479
+ return false;
2480
+ }
2481
+ return true;
2482
+ }
2319
2483
 
2320
2484
  // src/functions/gltf-loading/import-scene.ts
2321
2485
  Object3D7.DEFAULT_UP.set(0, 0, 1);
@@ -2589,9 +2753,10 @@ function deriveRootBounds(group) {
2589
2753
  }
2590
2754
 
2591
2755
  // src/functions/vizij-bundle.ts
2756
+ import { cloneDeepSafe } from "@vizij/utils";
2592
2757
  var BUNDLE_KEYS = ["VIZIJ_bundle"];
2593
2758
  function cloneBundle(value) {
2594
- return JSON.parse(JSON.stringify(value));
2759
+ return cloneDeepSafe(value);
2595
2760
  }
2596
2761
  function readExtensionValue(extensionContainer) {
2597
2762
  for (const key of BUNDLE_KEYS) {
@@ -2625,6 +2790,13 @@ function searchParserJsonForBundle(parserJson) {
2625
2790
  if (!parserJson || typeof parserJson !== "object") {
2626
2791
  return null;
2627
2792
  }
2793
+ const rootExtensions = parserJson && typeof parserJson === "object" ? parserJson.extensions : null;
2794
+ if (rootExtensions && typeof rootExtensions === "object") {
2795
+ const match = readExtensionValue(rootExtensions);
2796
+ if (match) {
2797
+ return cloneBundle(match.value);
2798
+ }
2799
+ }
2628
2800
  const nodes = Array.isArray(parserJson.nodes) ? parserJson.nodes : [];
2629
2801
  for (const node of nodes) {
2630
2802
  const extensions = node && typeof node === "object" ? node.extensions : null;
@@ -2689,6 +2861,7 @@ function applyVizijBundle(object, bundle) {
2689
2861
  }
2690
2862
 
2691
2863
  // src/functions/gltf-loading/extract-animations.ts
2864
+ import { cloneDeepSafe as cloneDeepSafe2 } from "@vizij/utils";
2692
2865
  var CHANNEL_PATH_TO_TRACK_PROPERTY = {
2693
2866
  translation: "position",
2694
2867
  rotation: "quaternion",
@@ -2704,7 +2877,7 @@ function clonePlainObject(value) {
2704
2877
  if (!value) {
2705
2878
  return void 0;
2706
2879
  }
2707
- return JSON.parse(JSON.stringify(value));
2880
+ return cloneDeepSafe2(value);
2708
2881
  }
2709
2882
  function inferValueSize(valueType) {
2710
2883
  switch (valueType) {
@@ -3059,23 +3232,94 @@ function extractVizijAnimations(parserJson, clips) {
3059
3232
 
3060
3233
  // src/functions/load-gltf.ts
3061
3234
  THREE5.Object3D.DEFAULT_UP.set(0, 0, 1);
3235
+ var GLB_MAGIC = 1179937895;
3236
+ var GLB_VERSION = 2;
3237
+ var GLB_HEADER_BYTES = 12;
3238
+ var GLB_CHUNK_HEADER_BYTES = 8;
3239
+ var GLB_JSON_CHUNK_TYPE = 1313821514;
3062
3240
  var EmptyModelError = class extends Error {
3063
3241
  constructor(message) {
3064
3242
  super(message);
3065
3243
  this.name = "EmptyModelError";
3066
3244
  }
3067
3245
  };
3246
+ function parseGlbJsonChunk(buffer) {
3247
+ if (buffer.byteLength < GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES) {
3248
+ return void 0;
3249
+ }
3250
+ const view = new DataView(buffer);
3251
+ const magic = view.getUint32(0, true);
3252
+ const version = view.getUint32(4, true);
3253
+ if (magic !== GLB_MAGIC || version !== GLB_VERSION) {
3254
+ return void 0;
3255
+ }
3256
+ const chunkLength = view.getUint32(GLB_HEADER_BYTES, true);
3257
+ const chunkType = view.getUint32(GLB_HEADER_BYTES + 4, true);
3258
+ if (chunkType !== GLB_JSON_CHUNK_TYPE) {
3259
+ return void 0;
3260
+ }
3261
+ const chunkStart = GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES;
3262
+ const chunkEnd = chunkStart + chunkLength;
3263
+ if (chunkEnd > buffer.byteLength) {
3264
+ return void 0;
3265
+ }
3266
+ try {
3267
+ const chunkBytes = new Uint8Array(buffer, chunkStart, chunkLength);
3268
+ const jsonText = new TextDecoder().decode(chunkBytes);
3269
+ return JSON.parse(jsonText);
3270
+ } catch {
3271
+ return void 0;
3272
+ }
3273
+ }
3274
+ async function resolveParserJson(parserJson, fallback) {
3275
+ if (parserJson && typeof parserJson === "object") {
3276
+ return parserJson;
3277
+ }
3278
+ if (fallback.arrayBuffer) {
3279
+ const fromArrayBuffer = parseGlbJsonChunk(fallback.arrayBuffer);
3280
+ if (fromArrayBuffer && typeof fromArrayBuffer === "object") {
3281
+ return fromArrayBuffer;
3282
+ }
3283
+ }
3284
+ if (fallback.blob) {
3285
+ try {
3286
+ const blobBuffer = typeof fallback.blob.arrayBuffer === "function" ? await fallback.blob.arrayBuffer() : await new Response(fallback.blob).arrayBuffer();
3287
+ const fromBlob = parseGlbJsonChunk(blobBuffer);
3288
+ if (fromBlob && typeof fromBlob === "object") {
3289
+ return fromBlob;
3290
+ }
3291
+ } catch {
3292
+ }
3293
+ }
3294
+ if (fallback.url && typeof fetch === "function") {
3295
+ try {
3296
+ const response = await fetch(fallback.url);
3297
+ if (response.ok) {
3298
+ const binary = await response.arrayBuffer();
3299
+ const fromUrl = parseGlbJsonChunk(binary);
3300
+ if (fromUrl && typeof fromUrl === "object") {
3301
+ return fromUrl;
3302
+ }
3303
+ }
3304
+ } catch {
3305
+ }
3306
+ }
3307
+ return parserJson;
3308
+ }
3068
3309
  async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
3069
3310
  const modelLoader = new GLTFLoader();
3070
3311
  modelLoader.setDRACOLoader(new DRACOLoader());
3071
3312
  const modelData = await modelLoader.loadAsync(url);
3313
+ const parserJson = await resolveParserJson(modelData?.parser?.json, {
3314
+ url
3315
+ });
3072
3316
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3073
3317
  const asset = parseScene(
3074
3318
  modelData.scene,
3075
3319
  actualizedNamespaces,
3076
3320
  aggressiveImport,
3077
3321
  rootBounds,
3078
- modelData?.parser?.json,
3322
+ parserJson,
3079
3323
  modelData.animations
3080
3324
  );
3081
3325
  return [asset.world, asset.animatables, asset.animations];
@@ -3089,7 +3333,8 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
3089
3333
  objectUrl,
3090
3334
  actualizedNamespaces,
3091
3335
  aggressiveImport,
3092
- rootBounds
3336
+ rootBounds,
3337
+ { blob }
3093
3338
  );
3094
3339
  return [asset.world, asset.animatables, asset.animations];
3095
3340
  } finally {
@@ -3103,14 +3348,18 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
3103
3348
  loader.parse(
3104
3349
  arrayBuffer,
3105
3350
  "",
3106
- (gltf) => {
3351
+ async (gltf) => {
3107
3352
  try {
3353
+ const parserJson = await resolveParserJson(
3354
+ gltf?.parser?.json,
3355
+ { arrayBuffer }
3356
+ );
3108
3357
  const asset = parseScene(
3109
3358
  gltf.scene,
3110
3359
  actualizedNamespaces,
3111
3360
  aggressiveImport,
3112
3361
  rootBounds,
3113
- gltf?.parser?.json,
3362
+ parserJson,
3114
3363
  gltf.animations
3115
3364
  );
3116
3365
  resolve([asset.world, asset.animatables, asset.animations]);
@@ -3137,19 +3386,23 @@ function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson,
3137
3386
  );
3138
3387
  const bundle = extractVizijBundle(scene, parserJson);
3139
3388
  const animations = extractVizijAnimations(parserJson, clips);
3140
- return { world, animatables, bundle, animations };
3389
+ return { world, animatables, bundle, animations, scene };
3141
3390
  }
3142
- async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds) {
3391
+ async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
3143
3392
  const modelLoader = new GLTFLoader();
3144
3393
  modelLoader.setDRACOLoader(new DRACOLoader());
3145
3394
  const modelData = await modelLoader.loadAsync(url);
3395
+ const parserJson = await resolveParserJson(modelData?.parser?.json, {
3396
+ url,
3397
+ ...parserJsonFallback
3398
+ });
3146
3399
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3147
3400
  return parseScene(
3148
3401
  modelData.scene,
3149
3402
  actualizedNamespaces,
3150
3403
  aggressiveImport,
3151
3404
  rootBounds,
3152
- modelData?.parser?.json,
3405
+ parserJson,
3153
3406
  modelData.animations
3154
3407
  );
3155
3408
  }
@@ -3162,7 +3415,8 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
3162
3415
  objectUrl,
3163
3416
  actualizedNamespaces,
3164
3417
  aggressiveImport,
3165
- rootBounds
3418
+ rootBounds,
3419
+ { blob }
3166
3420
  );
3167
3421
  } finally {
3168
3422
  URL.revokeObjectURL(objectUrl);
@@ -3175,14 +3429,18 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
3175
3429
  loader.parse(
3176
3430
  arrayBuffer,
3177
3431
  "",
3178
- (gltf) => {
3432
+ async (gltf) => {
3179
3433
  try {
3434
+ const parserJson = await resolveParserJson(
3435
+ gltf?.parser?.json,
3436
+ { arrayBuffer }
3437
+ );
3180
3438
  const asset = parseScene(
3181
3439
  gltf.scene,
3182
3440
  actualizedNamespaces,
3183
3441
  aggressiveImport,
3184
3442
  rootBounds,
3185
- gltf?.parser?.json,
3443
+ parserJson,
3186
3444
  gltf.animations
3187
3445
  );
3188
3446
  resolve(asset);
@@ -3234,6 +3492,160 @@ var loadGltfFromBlob = (blob, namespaces) => {
3234
3492
  import { GLTFExporter } from "three-stdlib";
3235
3493
  import * as THREE6 from "three";
3236
3494
  THREE6.Object3D.DEFAULT_UP.set(0, 0, 1);
3495
+ function normalizeExportError(error) {
3496
+ if (error instanceof Error) {
3497
+ return error;
3498
+ }
3499
+ if (typeof error === "string") {
3500
+ return new Error(error);
3501
+ }
3502
+ return new Error("Failed to export scene.");
3503
+ }
3504
+ function triggerBlobDownload(blob, filename) {
3505
+ const url = URL.createObjectURL(blob);
3506
+ const link = document.createElement("a");
3507
+ link.href = url;
3508
+ link.download = filename;
3509
+ link.style.display = "none";
3510
+ document.body.appendChild(link);
3511
+ link.click();
3512
+ document.body.removeChild(link);
3513
+ setTimeout(() => URL.revokeObjectURL(url), 0);
3514
+ }
3515
+ var GLB_MAGIC2 = 1179937895;
3516
+ var GLB_VERSION2 = 2;
3517
+ var GLB_JSON_CHUNK_TYPE2 = 1313821514;
3518
+ var GLB_HEADER_BYTES2 = 12;
3519
+ var GLB_CHUNK_HEADER_BYTES2 = 8;
3520
+ var GLB_JSON_PADDING_BYTE = 32;
3521
+ function isNearlyEqual(a, b, epsilon = 1e-6) {
3522
+ return Math.abs(a - b) <= epsilon;
3523
+ }
3524
+ function isIdentityTransformNode(node) {
3525
+ const translation = node.translation;
3526
+ if (Array.isArray(translation) && (translation.length !== 3 || !isNearlyEqual(Number(translation[0]), 0) || !isNearlyEqual(Number(translation[1]), 0) || !isNearlyEqual(Number(translation[2]), 0))) {
3527
+ return false;
3528
+ }
3529
+ const rotation = node.rotation;
3530
+ if (Array.isArray(rotation) && (rotation.length !== 4 || !isNearlyEqual(Number(rotation[0]), 0) || !isNearlyEqual(Number(rotation[1]), 0) || !isNearlyEqual(Number(rotation[2]), 0) || !isNearlyEqual(Number(rotation[3]), 1))) {
3531
+ return false;
3532
+ }
3533
+ const scale = node.scale;
3534
+ if (Array.isArray(scale) && (scale.length !== 3 || !isNearlyEqual(Number(scale[0]), 1) || !isNearlyEqual(Number(scale[1]), 1) || !isNearlyEqual(Number(scale[2]), 1))) {
3535
+ return false;
3536
+ }
3537
+ return true;
3538
+ }
3539
+ function isPassThroughWrapperNode(node) {
3540
+ if (!node || typeof node !== "object") {
3541
+ return false;
3542
+ }
3543
+ if (!Array.isArray(node.children) || node.children.length === 0) {
3544
+ return false;
3545
+ }
3546
+ const hasOnlyNumericChildren = node.children.every(
3547
+ (index) => Number.isInteger(index)
3548
+ );
3549
+ if (!hasOnlyNumericChildren) {
3550
+ return false;
3551
+ }
3552
+ if (node.mesh !== void 0 || node.camera !== void 0 || node.skin !== void 0) {
3553
+ return false;
3554
+ }
3555
+ if (node.extensions && typeof node.extensions === "object" && Object.keys(node.extensions).length > 0) {
3556
+ return false;
3557
+ }
3558
+ return isIdentityTransformNode(node);
3559
+ }
3560
+ function normalizeExportedSceneJson(json, fallbackSceneName) {
3561
+ if (!json || typeof json !== "object") {
3562
+ return false;
3563
+ }
3564
+ if (!Array.isArray(json.scenes) || !Array.isArray(json.nodes)) {
3565
+ return false;
3566
+ }
3567
+ const sceneIndexRaw = json.scene;
3568
+ const sceneIndex = typeof sceneIndexRaw === "number" && Number.isInteger(sceneIndexRaw) ? sceneIndexRaw : 0;
3569
+ const sceneDef = json.scenes[sceneIndex];
3570
+ if (!sceneDef || typeof sceneDef !== "object") {
3571
+ return false;
3572
+ }
3573
+ let changed = false;
3574
+ let wrapperNodeName;
3575
+ if (Array.isArray(sceneDef.nodes) && sceneDef.nodes.length === 1) {
3576
+ const wrapperIndex = sceneDef.nodes[0];
3577
+ if (Number.isInteger(wrapperIndex)) {
3578
+ const wrapperNode = json.nodes[wrapperIndex];
3579
+ if (isPassThroughWrapperNode(wrapperNode)) {
3580
+ sceneDef.nodes = [...wrapperNode.children];
3581
+ wrapperNodeName = typeof wrapperNode.name === "string" ? wrapperNode.name : void 0;
3582
+ changed = true;
3583
+ }
3584
+ }
3585
+ }
3586
+ const currentSceneName = typeof sceneDef.name === "string" ? sceneDef.name.trim() : "";
3587
+ if (currentSceneName === "AuxScene") {
3588
+ const nextSceneName = (wrapperNodeName?.trim() || fallbackSceneName?.trim() || "Scene").trim();
3589
+ if (nextSceneName.length > 0 && nextSceneName !== currentSceneName) {
3590
+ sceneDef.name = nextSceneName;
3591
+ changed = true;
3592
+ }
3593
+ }
3594
+ return changed;
3595
+ }
3596
+ function sanitizeExportedGlb(buffer, fallbackSceneName) {
3597
+ if (buffer.byteLength < GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2) {
3598
+ return buffer;
3599
+ }
3600
+ const originalBytes = new Uint8Array(buffer);
3601
+ const view = new DataView(buffer);
3602
+ const magic = view.getUint32(0, true);
3603
+ const version = view.getUint32(4, true);
3604
+ if (magic !== GLB_MAGIC2 || version !== GLB_VERSION2) {
3605
+ return buffer;
3606
+ }
3607
+ const jsonChunkLength = view.getUint32(GLB_HEADER_BYTES2, true);
3608
+ const jsonChunkType = view.getUint32(GLB_HEADER_BYTES2 + 4, true);
3609
+ if (jsonChunkType !== GLB_JSON_CHUNK_TYPE2) {
3610
+ return buffer;
3611
+ }
3612
+ const jsonChunkStart = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2;
3613
+ const jsonChunkEnd = jsonChunkStart + jsonChunkLength;
3614
+ if (jsonChunkEnd > originalBytes.length) {
3615
+ return buffer;
3616
+ }
3617
+ let jsonPayload;
3618
+ try {
3619
+ const jsonText = new TextDecoder().decode(
3620
+ originalBytes.slice(jsonChunkStart, jsonChunkEnd)
3621
+ );
3622
+ jsonPayload = JSON.parse(jsonText);
3623
+ } catch {
3624
+ return buffer;
3625
+ }
3626
+ const changed = normalizeExportedSceneJson(jsonPayload, fallbackSceneName);
3627
+ if (!changed) {
3628
+ return buffer;
3629
+ }
3630
+ const encodedJson = new TextEncoder().encode(JSON.stringify(jsonPayload));
3631
+ const paddedJsonLength = encodedJson.length + 3 & ~3;
3632
+ const paddedJson = new Uint8Array(paddedJsonLength);
3633
+ paddedJson.fill(GLB_JSON_PADDING_BYTE);
3634
+ paddedJson.set(encodedJson);
3635
+ const remainingChunks = originalBytes.slice(jsonChunkEnd);
3636
+ const totalLength = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2 + paddedJsonLength + remainingChunks.length;
3637
+ const sanitized = new ArrayBuffer(totalLength);
3638
+ const sanitizedBytes = new Uint8Array(sanitized);
3639
+ const sanitizedView = new DataView(sanitized);
3640
+ sanitizedView.setUint32(0, GLB_MAGIC2, true);
3641
+ sanitizedView.setUint32(4, GLB_VERSION2, true);
3642
+ sanitizedView.setUint32(8, totalLength, true);
3643
+ sanitizedView.setUint32(GLB_HEADER_BYTES2, paddedJsonLength, true);
3644
+ sanitizedView.setUint32(GLB_HEADER_BYTES2 + 4, GLB_JSON_CHUNK_TYPE2, true);
3645
+ sanitizedBytes.set(paddedJson, jsonChunkStart);
3646
+ sanitizedBytes.set(remainingChunks, jsonChunkStart + paddedJsonLength);
3647
+ return sanitized;
3648
+ }
3237
3649
  function exportScene(data, fileNameOrOptions = "scene.glb") {
3238
3650
  const options = typeof fileNameOrOptions === "string" ? { fileName: fileNameOrOptions } : fileNameOrOptions ?? {};
3239
3651
  const fileName = options.fileName ?? "scene.glb";
@@ -3248,7 +3660,15 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
3248
3660
  }
3249
3661
  }
3250
3662
  }));
3251
- const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(data, options.bundle) : () => {
3663
+ const sourceRoot = data;
3664
+ const exportRoot = sourceRoot instanceof THREE6.Scene ? sourceRoot : sourceRoot.clone(true);
3665
+ const exportTarget = exportRoot instanceof THREE6.Scene ? exportRoot : (() => {
3666
+ const scene = new THREE6.Scene();
3667
+ scene.name = data.name?.trim() || "Scene";
3668
+ scene.add(exportRoot);
3669
+ return scene;
3670
+ })();
3671
+ const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(exportRoot, options.bundle) : () => {
3252
3672
  };
3253
3673
  const binary = options.binary ?? true;
3254
3674
  const exporterOptions = {
@@ -3262,33 +3682,40 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
3262
3682
  }
3263
3683
  try {
3264
3684
  exporter.parse(
3265
- data,
3685
+ exportTarget,
3266
3686
  (gltf) => {
3267
3687
  detachBundle();
3268
3688
  if (!(gltf instanceof ArrayBuffer)) {
3269
- throw new Error("Failed to export scene!");
3689
+ const error = new Error("Failed to export scene.");
3690
+ options.onError?.(error);
3691
+ return;
3270
3692
  }
3271
- const link = document.createElement("a");
3272
- link.href = URL.createObjectURL(
3273
- new Blob([gltf], {
3274
- type: "application/octet-stream"
3275
- })
3693
+ const sanitizedGltf = sanitizeExportedGlb(
3694
+ gltf,
3695
+ data.name?.trim() || void 0
3276
3696
  );
3277
3697
  const trimmed = fileName.trim();
3278
3698
  const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
3279
3699
  const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
3280
- link.download = downloadName;
3281
- link.click();
3282
- URL.revokeObjectURL(link.href);
3700
+ triggerBlobDownload(
3701
+ new Blob([sanitizedGltf], {
3702
+ type: "application/octet-stream"
3703
+ }),
3704
+ downloadName
3705
+ );
3706
+ options.onComplete?.();
3283
3707
  },
3284
- () => {
3708
+ (error) => {
3285
3709
  detachBundle();
3710
+ options.onError?.(normalizeExportError(error));
3286
3711
  },
3287
3712
  exporterOptions
3288
3713
  );
3289
3714
  } catch (error) {
3290
3715
  detachBundle();
3291
- throw error;
3716
+ const normalizedError = normalizeExportError(error);
3717
+ options.onError?.(normalizedError);
3718
+ throw normalizedError;
3292
3719
  }
3293
3720
  }
3294
3721
  export {
@@ -3307,6 +3734,7 @@ export {
3307
3734
  loadGLTFFromBlobWithBundle,
3308
3735
  loadGLTFWithBundle,
3309
3736
  loadGltfFromBlob,
3737
+ parseGlbJsonChunk,
3310
3738
  useDefaultVizijStore,
3311
3739
  useFeatures,
3312
3740
  useVizijStore,