@vizij/render 0.1.2 → 1.0.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,13 +1,13 @@
1
1
  // src/vizij.tsx
2
2
  import { Suspense, memo as memo6, useContext as useContext3, useEffect as useEffect7 } from "react";
3
3
  import { ErrorBoundary } from "react-error-boundary";
4
- import { Object3D as Object3D4, SRGBColorSpace, NoToneMapping } from "three";
4
+ import { Object3D as Object3D4, SRGBColorSpace } from "three";
5
5
  import { Canvas, useThree } from "@react-three/fiber";
6
6
  import { Line as Line3, OrthographicCamera, Text } from "@react-three/drei";
7
7
  import { useShallow as useShallow7 } from "zustand/react/shallow";
8
8
 
9
9
  // src/renderables/renderable.tsx
10
- import { memo as memo5, useMemo as useMemo5 } from "react";
10
+ import { memo as memo5 } from "react";
11
11
  import { useShallow as useShallow5 } from "zustand/react/shallow";
12
12
 
13
13
  // src/hooks/use-vizij-store.ts
@@ -84,35 +84,33 @@ function useFeatures(namespace, features, callbacks, debugInfo) {
84
84
  }, [features, callbacks, store, debugInfo, namespace]);
85
85
  }
86
86
 
87
- // src/functions/create-stored-data.ts
88
- import { omit } from "lodash";
89
-
90
87
  // src/functions/create-stored-features.ts
91
- import { mapValues } from "lodash";
92
88
  function createStoredFeatures(objectFeatures, animatableValues) {
93
- return mapValues(objectFeatures, (feat) => {
94
- if (feat.animated) {
95
- const storedFeat = {
96
- animated: true,
97
- value: animatableValues[feat.value],
98
- ...feat.label ? { label: feat.label } : {}
99
- };
100
- return storedFeat;
101
- } else {
89
+ const stored = Object.entries(objectFeatures).map(
90
+ ([key, feat]) => {
91
+ if (feat.animated) {
92
+ const storedFeat = {
93
+ animated: true,
94
+ value: animatableValues[feat.value],
95
+ ...feat.label ? { label: feat.label } : {}
96
+ };
97
+ return [key, storedFeat];
98
+ }
102
99
  const staticFeature = feat;
103
- return staticFeature.label ? { ...staticFeature } : staticFeature;
100
+ return [key, staticFeature.label ? { ...staticFeature } : staticFeature];
104
101
  }
105
- });
102
+ );
103
+ return Object.fromEntries(stored);
106
104
  }
107
105
 
108
106
  // src/functions/create-stored-data.ts
109
107
  function createStoredRenderable(data, animatableValues) {
110
- const d = omit(data, "refs", "geometry");
108
+ const { geometry: _geometry, ...stored } = data;
111
109
  const storedFeatures = createStoredFeatures(
112
110
  data.features,
113
111
  animatableValues
114
112
  );
115
- return { ...d, features: storedFeatures };
113
+ return { ...stored, features: storedFeatures };
116
114
  }
117
115
 
118
116
  // src/renderables/group.tsx
@@ -125,7 +123,9 @@ function InnerRenderedGroup({
125
123
  }) {
126
124
  const ref = useRef(null);
127
125
  const group = useVizijStore(useShallow((state) => state.world[id]));
128
- const refIsNull = !group.refs[namespace]?.current;
126
+ const refIsNull = useVizijStore(
127
+ (state) => !state.refs[id]?.[namespace]?.current
128
+ );
129
129
  const animatables = useVizijStore(useShallow((state) => state.animatables));
130
130
  const setHoveredElement = useVizijStore(
131
131
  useShallow((state) => state.setHoveredElement)
@@ -267,7 +267,9 @@ function InnerRenderedEllipse({
267
267
  const ellipse = useVizijStore(
268
268
  useShallow2((state) => state.world[id])
269
269
  );
270
- const refIsNull = !ellipse.refs[namespace]?.current;
270
+ const refIsNull = useVizijStore(
271
+ (state) => !state.refs[id]?.[namespace]?.current
272
+ );
271
273
  const animatables = useVizijStore(useShallow2((state) => state.animatables));
272
274
  const animatableValues = useMemo2(() => {
273
275
  const av = {};
@@ -561,7 +563,9 @@ function InnerRenderedRectangle({
561
563
  const rectangle = useVizijStore(
562
564
  useShallow3((state) => state.world[id])
563
565
  );
564
- const refIsNull = !rectangle.refs[namespace]?.current;
566
+ const refIsNull = useVizijStore(
567
+ (state) => !state.refs[id]?.[namespace]?.current
568
+ );
565
569
  const animatables = useVizijStore(useShallow3((state) => state.animatables));
566
570
  const animatableValues = useMemo3(() => {
567
571
  const av = {};
@@ -833,6 +837,61 @@ import {
833
837
  instanceOfRawRGB as instanceOfRawRGB3,
834
838
  instanceOfRawVector3 as instanceOfRawVector34
835
839
  } from "@vizij/utils";
840
+
841
+ // src/functions/material-name.ts
842
+ var MATERIAL_FEATURE_KEYS = [
843
+ "color",
844
+ "opacity",
845
+ "roughness",
846
+ "metalness",
847
+ "shininess",
848
+ "emissive",
849
+ "emissiveIntensity",
850
+ "specular"
851
+ ];
852
+ var MATERIAL_NAME_SUFFIXES = [
853
+ " color",
854
+ " colours",
855
+ " colour",
856
+ " opacity",
857
+ " roughness",
858
+ " metalness",
859
+ " shininess",
860
+ " emissive intensity",
861
+ " emissive",
862
+ " specular"
863
+ ];
864
+ function deriveMaterialName(shape, values) {
865
+ for (const key of MATERIAL_FEATURE_KEYS) {
866
+ const feature = shape.features[key];
867
+ if (feature && feature.animated) {
868
+ const animatable = values[feature.value];
869
+ const candidate = extractMaterialName(animatable?.name);
870
+ if (candidate) {
871
+ return candidate;
872
+ }
873
+ }
874
+ }
875
+ return void 0;
876
+ }
877
+ function extractMaterialName(name) {
878
+ if (!name) {
879
+ return void 0;
880
+ }
881
+ const trimmed = name.trim();
882
+ if (!trimmed) {
883
+ return void 0;
884
+ }
885
+ const lowered = trimmed.toLowerCase();
886
+ for (const suffix of MATERIAL_NAME_SUFFIXES) {
887
+ if (lowered.endsWith(suffix)) {
888
+ return trimmed.slice(0, trimmed.length - suffix.length).trim();
889
+ }
890
+ }
891
+ return trimmed;
892
+ }
893
+
894
+ // src/renderables/shape.tsx
836
895
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
837
896
  THREE2.Object3D.DEFAULT_UP.set(0, 0, 1);
838
897
  function InnerRenderedShape({
@@ -843,10 +902,9 @@ function InnerRenderedShape({
843
902
  const refGroup = useRef4(null);
844
903
  const ref = useRef4(null);
845
904
  const shape = useVizijStore(useShallow4((state) => state.world[id]));
846
- const refs = useVizijStore(
847
- useShallow4((state) => state.world[id].refs)
905
+ const refIsNull = useVizijStore(
906
+ (state) => !state.refs[id]?.[namespace]?.current
848
907
  );
849
- const refIsNull = !refs[namespace]?.current;
850
908
  const animatables = useVizijStore(useShallow4((state) => state.animatables));
851
909
  const animatableValues = useMemo4(() => {
852
910
  const av = {};
@@ -1131,125 +1189,44 @@ function InnerRenderedShape({
1131
1189
  );
1132
1190
  }
1133
1191
  var RenderedShape = memo4(InnerRenderedShape);
1134
- var MATERIAL_FEATURE_KEYS = [
1135
- "color",
1136
- "opacity",
1137
- "roughness",
1138
- "metalness",
1139
- "shininess",
1140
- "emissive",
1141
- "emissiveIntensity",
1142
- "specular"
1143
- ];
1144
- var MATERIAL_NAME_SUFFIXES = [
1145
- " color",
1146
- " colours",
1147
- " colour",
1148
- " opacity",
1149
- " roughness",
1150
- " metalness",
1151
- " shininess",
1152
- " emissive intensity",
1153
- " emissive",
1154
- " specular"
1155
- ];
1156
- function deriveMaterialName(shape, values) {
1157
- for (const key of MATERIAL_FEATURE_KEYS) {
1158
- const feature = shape.features[key];
1159
- if (feature && feature.animated) {
1160
- const animatable = values[feature.value];
1161
- const candidate = extractMaterialName(animatable?.name);
1162
- if (candidate) {
1163
- return candidate;
1164
- }
1165
- }
1166
- }
1167
- return void 0;
1168
- }
1169
- function extractMaterialName(name) {
1170
- if (!name) {
1171
- return void 0;
1172
- }
1173
- const trimmed = name.trim();
1174
- if (!trimmed) {
1175
- return void 0;
1176
- }
1177
- const lowered = trimmed.toLowerCase();
1178
- for (const suffix of MATERIAL_NAME_SUFFIXES) {
1179
- if (lowered.endsWith(suffix)) {
1180
- return trimmed.slice(0, trimmed.length - suffix.length).trim();
1181
- }
1182
- }
1183
- return trimmed;
1184
- }
1185
1192
 
1186
1193
  // src/renderables/renderable.tsx
1187
- import { Fragment as Fragment3, jsx as jsx5 } from "react/jsx-runtime";
1194
+ import { jsx as jsx5 } from "react/jsx-runtime";
1188
1195
  function InnerRenderable({
1189
1196
  id,
1190
1197
  namespace,
1191
1198
  chain
1192
1199
  }) {
1193
1200
  const type = useVizijStore(useShallow5((state) => state.world[id].type));
1194
- const refs = useVizijStore(useShallow5((state) => state.world[id].refs));
1195
- const resolvedNamespaces = useMemo5(() => {
1196
- let namespaces = [namespace];
1197
- if (namespace in refs) {
1198
- namespaces = [namespace];
1199
- } else {
1200
- namespaces = Object.keys(refs);
1201
- }
1202
- return namespaces;
1203
- }, [namespace, refs]);
1204
- if (resolvedNamespaces.length === 0) {
1205
- return null;
1201
+ const key = `${namespace}.${id}`;
1202
+ switch (type) {
1203
+ case "group":
1204
+ return /* @__PURE__ */ jsx5(RenderedGroup, { id, namespace, chain }, key);
1205
+ case "ellipse":
1206
+ return /* @__PURE__ */ jsx5(
1207
+ RenderedEllipse,
1208
+ {
1209
+ id,
1210
+ namespace,
1211
+ chain
1212
+ },
1213
+ key
1214
+ );
1215
+ case "rectangle":
1216
+ return /* @__PURE__ */ jsx5(
1217
+ RenderedRectangle,
1218
+ {
1219
+ id,
1220
+ namespace,
1221
+ chain
1222
+ },
1223
+ key
1224
+ );
1225
+ case "shape":
1226
+ return /* @__PURE__ */ jsx5(RenderedShape, { id, namespace, chain }, key);
1227
+ default:
1228
+ return null;
1206
1229
  }
1207
- return /* @__PURE__ */ jsx5(Fragment3, { children: resolvedNamespaces.map((ns) => {
1208
- switch (type) {
1209
- case "group":
1210
- return /* @__PURE__ */ jsx5(
1211
- RenderedGroup,
1212
- {
1213
- id,
1214
- namespace: ns,
1215
- chain
1216
- },
1217
- `${ns}.${id}`
1218
- );
1219
- case "ellipse":
1220
- return /* @__PURE__ */ jsx5(
1221
- RenderedEllipse,
1222
- {
1223
- id,
1224
- namespace: ns,
1225
- chain
1226
- },
1227
- `${ns}.${id}`
1228
- );
1229
- case "rectangle":
1230
- return /* @__PURE__ */ jsx5(
1231
- RenderedRectangle,
1232
- {
1233
- id,
1234
- namespace: ns,
1235
- chain
1236
- },
1237
- `${ns}.${id}`
1238
- );
1239
- case "shape":
1240
- return /* @__PURE__ */ jsx5(
1241
- RenderedShape,
1242
- {
1243
- id,
1244
- namespace: ns,
1245
- chain
1246
- },
1247
- `${ns}.${id}`
1248
- );
1249
- default:
1250
- return null;
1251
- }
1252
- }) });
1253
1230
  }
1254
1231
  var Renderable = memo5(InnerRenderable);
1255
1232
 
@@ -1260,10 +1237,6 @@ import { produce, enableMapSet } from "immer";
1260
1237
  import * as THREE3 from "three";
1261
1238
  import { getLookup as getLookup2 } from "@vizij/utils";
1262
1239
 
1263
- // src/actions/create-new-element.ts
1264
- import { createRef } from "react";
1265
- import { mapValues as mapValues2 } from "lodash";
1266
-
1267
1240
  // src/functions/create-world-element.ts
1268
1241
  import { createBrowserSafeId } from "@vizij/utils";
1269
1242
  function createDefaultGroup(partialBody) {
@@ -1275,7 +1248,6 @@ function createDefaultGroup(partialBody) {
1275
1248
  name: partialBody.name || "new-body",
1276
1249
  type: partialBody.type || "group",
1277
1250
  tags: partialBody.tags || [],
1278
- refs: partialBody.refs || {},
1279
1251
  features: { translation, rotation, scale },
1280
1252
  root: partialBody.root ?? false,
1281
1253
  children: partialBody.children || []
@@ -1292,11 +1264,9 @@ function createNewElement(state, type, root = false) {
1292
1264
  });
1293
1265
  if (Object.entries(state.world).length === 0) {
1294
1266
  const name = `New-Root`;
1295
- const refs = { default: createRef() };
1296
1267
  const newElement = createDefaultGroup({
1297
1268
  name,
1298
- root: true,
1299
- refs
1269
+ root: true
1300
1270
  });
1301
1271
  state.world[newElement.id] = newElement;
1302
1272
  state.elementSelection = [buildSelection(newElement.id)];
@@ -1308,11 +1278,7 @@ function createNewElement(state, type, root = false) {
1308
1278
  return;
1309
1279
  }
1310
1280
  const name = `New-Body`;
1311
- const refs = mapValues2(
1312
- worldRootEntry.refs,
1313
- () => createRef()
1314
- );
1315
- const newChild = createDefaultGroup({ name, root, refs });
1281
+ const newChild = createDefaultGroup({ name, root });
1316
1282
  worldRootEntry.children.push(newChild.id);
1317
1283
  state.world[newChild.id] = newChild;
1318
1284
  state.elementSelection = [buildSelection(newChild.id)];
@@ -1512,6 +1478,7 @@ enableMapSet();
1512
1478
  var VizijSlice = (set, get) => ({
1513
1479
  // worldRef: createRef<THREE.Group>(),
1514
1480
  world: {},
1481
+ refs: {},
1515
1482
  animatables: {},
1516
1483
  values: /* @__PURE__ */ new Map(),
1517
1484
  renderHit: false,
@@ -1585,12 +1552,10 @@ var VizijSlice = (set, get) => ({
1585
1552
  worldData,
1586
1553
  filterIds
1587
1554
  );
1555
+ const refs = get().refs;
1588
1556
  return candidateGroups.flatMap((entry) => {
1589
- const refs = Object.values(
1590
- entry.refs ?? {}
1591
- );
1592
- const resolved = refs.find((ref) => ref?.current)?.current ?? null;
1593
- return resolved ? [resolved] : [];
1557
+ const mounted = Object.values(refs[entry.id] ?? {}).map((ref) => ref.current).find((object) => Boolean(object));
1558
+ return mounted ? [mounted] : [];
1594
1559
  });
1595
1560
  },
1596
1561
  setGeometry: (id, geometry) => {
@@ -1812,14 +1777,12 @@ var VizijSlice = (set, get) => ({
1812
1777
  }));
1813
1778
  },
1814
1779
  setReference: (id, namespace, ref) => {
1815
- set(
1816
- produce((state) => {
1817
- state.world[id].refs[namespace].current = ref.current;
1818
- if (ref.current?.children && state.world[id].refs[namespace].current) {
1819
- state.world[id].refs[namespace].current.children = ref.current.children;
1820
- }
1821
- })
1822
- );
1780
+ set((state) => ({
1781
+ refs: {
1782
+ ...state.refs,
1783
+ [id]: { ...state.refs[id], [namespace]: ref }
1784
+ }
1785
+ }));
1823
1786
  }
1824
1787
  });
1825
1788
  var useDefaultVizijStore = create()(
@@ -1834,8 +1797,57 @@ var createVizijStore = (initial) => create()(
1834
1797
  }))
1835
1798
  );
1836
1799
 
1800
+ // src/types/shape.ts
1801
+ var ShapeMaterial = /* @__PURE__ */ ((ShapeMaterial2) => {
1802
+ ShapeMaterial2["Standard"] = "standard";
1803
+ ShapeMaterial2["Phong"] = "phong";
1804
+ ShapeMaterial2["Basic"] = "basic";
1805
+ ShapeMaterial2["Lambert"] = "lambert";
1806
+ ShapeMaterial2["Normal"] = "normal";
1807
+ return ShapeMaterial2;
1808
+ })(ShapeMaterial || {});
1809
+
1810
+ // src/types/tone-mapping.ts
1811
+ import {
1812
+ ACESFilmicToneMapping,
1813
+ AgXToneMapping,
1814
+ NeutralToneMapping,
1815
+ NoToneMapping
1816
+ } from "three";
1817
+ var TONE_MAPPING_MODES = [
1818
+ "none",
1819
+ "agx",
1820
+ "aces",
1821
+ "neutral"
1822
+ ];
1823
+ var TONE_MAPPING_LABELS = {
1824
+ none: "None (web match)",
1825
+ agx: "AgX (Blender)",
1826
+ aces: "ACES Filmic",
1827
+ neutral: "Neutral"
1828
+ };
1829
+ var DEFAULT_TONE_MAPPING_MODE = "none";
1830
+ var TONE_MAPPING_CONSTANTS = {
1831
+ none: NoToneMapping,
1832
+ agx: AgXToneMapping,
1833
+ aces: ACESFilmicToneMapping,
1834
+ neutral: NeutralToneMapping
1835
+ };
1836
+ function isToneMappingMode(value) {
1837
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(TONE_MAPPING_CONSTANTS, value);
1838
+ }
1839
+ function resolveToneMapping(mode) {
1840
+ if (isToneMappingMode(mode)) {
1841
+ return TONE_MAPPING_CONSTANTS[mode];
1842
+ }
1843
+ return TONE_MAPPING_CONSTANTS[DEFAULT_TONE_MAPPING_MODE];
1844
+ }
1845
+ function normalizeToneMappingMode(value) {
1846
+ return isToneMappingMode(value) ? value : DEFAULT_TONE_MAPPING_MODE;
1847
+ }
1848
+
1837
1849
  // src/effects/selection-glow-effect.tsx
1838
- import { Fragment as Fragment4, useEffect as useEffect6, useMemo as useMemo6, useRef as useRef5 } from "react";
1850
+ import { Fragment as Fragment3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef5 } from "react";
1839
1851
  import { useFrame } from "@react-three/fiber";
1840
1852
  import { useShallow as useShallow6 } from "zustand/react/shallow";
1841
1853
  import {
@@ -1862,7 +1874,7 @@ function SelectionGlowEffect({
1862
1874
  if (!enabled || selections.length === 0) {
1863
1875
  return null;
1864
1876
  }
1865
- return /* @__PURE__ */ jsx6(Fragment4, { children: selections.map((selection) => /* @__PURE__ */ jsx6(
1877
+ return /* @__PURE__ */ jsx6(Fragment3, { children: selections.map((selection) => /* @__PURE__ */ jsx6(
1866
1878
  SelectionOutline,
1867
1879
  {
1868
1880
  selection,
@@ -1882,19 +1894,19 @@ function SelectionOutline({
1882
1894
  const target = useVizijStore(
1883
1895
  useShallow6((state) => {
1884
1896
  const entry = state.world[selection.id];
1885
- const ref = entry?.refs?.[selection.namespace];
1897
+ const ref = state.refs[selection.id]?.[selection.namespace];
1886
1898
  const geometry = entry?.geometry ?? null;
1887
1899
  return { ref, geometry };
1888
1900
  })
1889
1901
  );
1890
1902
  const sourceRef = target.ref;
1891
- const edgesGeometry = useMemo6(() => {
1903
+ const edgesGeometry = useMemo5(() => {
1892
1904
  if (!target.geometry) return null;
1893
1905
  const edges = new EdgesGeometry(target.geometry, thresholdAngle);
1894
1906
  return edges;
1895
1907
  }, [target.geometry, thresholdAngle]);
1896
1908
  useEffect6(() => () => edgesGeometry?.dispose(), [edgesGeometry]);
1897
- const material = useMemo6(() => {
1909
+ const material = useMemo5(() => {
1898
1910
  const mat = new LineBasicMaterial({
1899
1911
  color: new Color(color),
1900
1912
  transparent: true,
@@ -1944,7 +1956,7 @@ function copyWorldTransform(source, target) {
1944
1956
  }
1945
1957
 
1946
1958
  // src/vizij.tsx
1947
- import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1959
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1948
1960
  Object3D4.DEFAULT_UP.set(0, 0, 1);
1949
1961
  function Vizij({
1950
1962
  style,
@@ -1953,6 +1965,7 @@ function Vizij({
1953
1965
  namespace = "default",
1954
1966
  showSafeArea = false,
1955
1967
  showSelectionGlow = false,
1968
+ toneMapping = "none",
1956
1969
  onPointerMissed
1957
1970
  }) {
1958
1971
  const ctx = useContext3(VizijContext);
@@ -1973,7 +1986,6 @@ function Vizij({
1973
1986
  onPointerMissed,
1974
1987
  gl: {
1975
1988
  outputColorSpace: SRGBColorSpace,
1976
- toneMapping: NoToneMapping,
1977
1989
  antialias: true
1978
1990
  },
1979
1991
  children: /* @__PURE__ */ jsx7(
@@ -1982,7 +1994,8 @@ function Vizij({
1982
1994
  rootId,
1983
1995
  namespace,
1984
1996
  showSafeArea,
1985
- showSelectionGlow
1997
+ showSelectionGlow,
1998
+ toneMapping
1986
1999
  }
1987
2000
  )
1988
2001
  }
@@ -1996,7 +2009,6 @@ function Vizij({
1996
2009
  onPointerMissed,
1997
2010
  gl: {
1998
2011
  outputColorSpace: SRGBColorSpace,
1999
- toneMapping: NoToneMapping,
2000
2012
  antialias: true
2001
2013
  },
2002
2014
  children: /* @__PURE__ */ jsx7(
@@ -2005,7 +2017,8 @@ function Vizij({
2005
2017
  rootId,
2006
2018
  namespace,
2007
2019
  showSafeArea,
2008
- showSelectionGlow
2020
+ showSelectionGlow,
2021
+ toneMapping
2009
2022
  }
2010
2023
  )
2011
2024
  }
@@ -2017,14 +2030,16 @@ function InnerVizij({
2017
2030
  namespace = "default",
2018
2031
  container,
2019
2032
  showSafeArea,
2020
- showSelectionGlow
2033
+ showSelectionGlow,
2034
+ toneMapping = "none"
2021
2035
  }) {
2022
2036
  const sceneParentSizing = container ? {
2023
2037
  width: container.width * container.resolution,
2024
2038
  height: container.height * container.resolution
2025
2039
  } : void 0;
2026
- return /* @__PURE__ */ jsxs4(Fragment5, { children: [
2040
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
2027
2041
  /* @__PURE__ */ jsx7("ambientLight", { intensity: Math.PI / 2 }),
2042
+ /* @__PURE__ */ jsx7(ToneMappingController, { mode: toneMapping }),
2028
2043
  /* @__PURE__ */ jsx7(
2029
2044
  OrthographicCamera,
2030
2045
  {
@@ -2047,6 +2062,31 @@ function InnerVizij({
2047
2062
  ] });
2048
2063
  }
2049
2064
  var MemoizedInnerVizij = memo6(InnerVizij);
2065
+ function ToneMappingController({ mode }) {
2066
+ const gl = useThree((state) => state.gl);
2067
+ const scene = useThree((state) => state.scene);
2068
+ useEffect7(() => {
2069
+ const resolved = resolveToneMapping(mode);
2070
+ if (gl.toneMapping === resolved) {
2071
+ return;
2072
+ }
2073
+ gl.toneMapping = resolved;
2074
+ scene.traverse((object) => {
2075
+ const material = object.material;
2076
+ if (!material) {
2077
+ return;
2078
+ }
2079
+ if (Array.isArray(material)) {
2080
+ material.forEach((mat) => {
2081
+ mat.needsUpdate = true;
2082
+ });
2083
+ } else {
2084
+ material.needsUpdate = true;
2085
+ }
2086
+ });
2087
+ }, [gl, scene, mode]);
2088
+ return null;
2089
+ }
2050
2090
  function InnerWorld({
2051
2091
  rootId,
2052
2092
  namespace = "default",
@@ -2136,16 +2176,6 @@ var defaultRootBounds = {
2136
2176
  size: { x: 5, y: 4 }
2137
2177
  };
2138
2178
 
2139
- // src/types/shape.ts
2140
- var ShapeMaterial = /* @__PURE__ */ ((ShapeMaterial2) => {
2141
- ShapeMaterial2["Standard"] = "standard";
2142
- ShapeMaterial2["Phong"] = "phong";
2143
- ShapeMaterial2["Basic"] = "basic";
2144
- ShapeMaterial2["Lambert"] = "lambert";
2145
- ShapeMaterial2["Normal"] = "normal";
2146
- return ShapeMaterial2;
2147
- })(ShapeMaterial || {});
2148
-
2149
2179
  // src/hooks/use-vizij-store-subscription.ts
2150
2180
  import { useContext as useContext4, useEffect as useEffect8 } from "react";
2151
2181
  function useVizijStoreSubscription(selector, listener) {
@@ -2179,7 +2209,6 @@ import * as THREE5 from "three";
2179
2209
  import { GLTFLoader, DRACOLoader } from "three-stdlib";
2180
2210
 
2181
2211
  // src/functions/gltf-loading/traverse-three.ts
2182
- import { createRef as createRef3 } from "react";
2183
2212
  import * as THREE4 from "three";
2184
2213
 
2185
2214
  // src/functions/gltf-loading/map-features.ts
@@ -2211,12 +2240,6 @@ import { Object3D as Object3D7 } from "three";
2211
2240
  import { Object3D as Object3D6 } from "three";
2212
2241
  import { createBrowserSafeId as createBrowserSafeId5 } from "@vizij/utils";
2213
2242
 
2214
- // src/functions/util.ts
2215
- import { createRef as createRef2 } from "react";
2216
- function namespaceArrayToRefs(namespaces) {
2217
- return namespaces.reduce((acc, ns) => ({ ...acc, [ns]: createRef2() }), {});
2218
- }
2219
-
2220
2243
  // src/functions/gltf-loading/import-mesh.ts
2221
2244
  import { Object3D as Object3D5 } from "three";
2222
2245
  import { createBrowserSafeId as createBrowserSafeId4 } from "@vizij/utils";
@@ -2225,10 +2248,15 @@ import { createBrowserSafeId as createBrowserSafeId4 } from "@vizij/utils";
2225
2248
  import {
2226
2249
  createBrowserSafeId as createBrowserSafeId3
2227
2250
  } from "@vizij/utils";
2228
- function sanitizeMorphKey(name, fallbackIndex, used) {
2251
+
2252
+ // src/functions/gltf-loading/morph-keys.ts
2253
+ function slugifyMorphName(name, fallbackIndex) {
2229
2254
  const baseName = name && name.trim().length > 0 ? name.trim() : `morph_${fallbackIndex + 1}`;
2230
2255
  const slug = baseName.toLowerCase().replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "");
2231
- const safeBase = slug.length > 0 ? slug : `morph_${fallbackIndex + 1}`;
2256
+ return slug.length > 0 ? slug : `morph_${fallbackIndex + 1}`;
2257
+ }
2258
+ function sanitizeMorphKey(name, fallbackIndex, used) {
2259
+ const safeBase = slugifyMorphName(name, fallbackIndex);
2232
2260
  let candidate = safeBase;
2233
2261
  let counter = 1;
2234
2262
  while (used.has(candidate)) {
@@ -2237,6 +2265,12 @@ function sanitizeMorphKey(name, fallbackIndex, used) {
2237
2265
  used.add(candidate);
2238
2266
  return candidate;
2239
2267
  }
2268
+ function deriveMorphFeatureKeys(names) {
2269
+ const used = /* @__PURE__ */ new Set();
2270
+ return names.map((name, index) => sanitizeMorphKey(name, index, used));
2271
+ }
2272
+
2273
+ // src/functions/gltf-loading/import-geometry.ts
2240
2274
  function importGeometry(geometry, mesh) {
2241
2275
  const features = {};
2242
2276
  const animatableValues = {};
@@ -2279,7 +2313,7 @@ function importGeometry(geometry, mesh) {
2279
2313
 
2280
2314
  // src/functions/gltf-loading/import-mesh.ts
2281
2315
  Object3D5.DEFAULT_UP.set(0, 0, 1);
2282
- function importMesh(mesh, namespaces, colorLookup) {
2316
+ function importMesh(mesh, colorLookup) {
2283
2317
  let world = {};
2284
2318
  let animatables = {};
2285
2319
  let newColorLookup = {};
@@ -2448,7 +2482,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2448
2482
  const children = [];
2449
2483
  mesh.children.forEach((child) => {
2450
2484
  if (child.isMesh) {
2451
- const [newWorldItems, newAnimatables, childId, newMeshColors] = importMesh(child, namespaces, {
2485
+ const [newWorldItems, newAnimatables, childId, newMeshColors] = importMesh(child, {
2452
2486
  ...colorLookup,
2453
2487
  ...newColorLookup
2454
2488
  });
@@ -2457,7 +2491,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2457
2491
  animatables = { ...animatables, ...newAnimatables };
2458
2492
  children.push(childId);
2459
2493
  } else if (shouldImportAsGroupChild(child)) {
2460
- const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
2494
+ const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, {
2461
2495
  ...colorLookup,
2462
2496
  ...newColorLookup
2463
2497
  });
@@ -2494,8 +2528,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2494
2528
  ...materialIds.specular ? { specular: { animated: true, value: materialIds.specular } } : {},
2495
2529
  ...geometryFeatures
2496
2530
  },
2497
- children: children.length > 0 ? children : void 0,
2498
- refs: namespaceArrayToRefs(namespaces)
2531
+ children: children.length > 0 ? children : void 0
2499
2532
  };
2500
2533
  world = { ...world, [newShape.id]: newShape };
2501
2534
  return [world, animatables, newShape.id, newColorLookup];
@@ -2537,7 +2570,7 @@ function shouldImportAsGroupChild(child) {
2537
2570
 
2538
2571
  // src/functions/gltf-loading/import-group.ts
2539
2572
  Object3D6.DEFAULT_UP.set(0, 0, 1);
2540
- function importGroup(group, namespaces, colorLookup, rootBounds) {
2573
+ function importGroup(group, colorLookup, rootBounds) {
2541
2574
  let world = {};
2542
2575
  let animatables = {};
2543
2576
  let newColorLookup = {};
@@ -2585,7 +2618,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2585
2618
  animatables = { ...animatables, [scaleAnimatable.id]: scaleAnimatable };
2586
2619
  group.children.forEach((child) => {
2587
2620
  if (child.isMesh) {
2588
- const [newWorldItems, newAnimatables, childId, newMeshColors] = importMesh(child, namespaces, {
2621
+ const [newWorldItems, newAnimatables, childId, newMeshColors] = importMesh(child, {
2589
2622
  ...colorLookup,
2590
2623
  ...newColorLookup
2591
2624
  });
@@ -2594,7 +2627,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2594
2627
  animatables = { ...animatables, ...newAnimatables };
2595
2628
  children.push(childId);
2596
2629
  } else if (shouldImportAsGroupChild2(child)) {
2597
- const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
2630
+ const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, {
2598
2631
  ...colorLookup,
2599
2632
  ...newColorLookup
2600
2633
  });
@@ -2616,8 +2649,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2616
2649
  },
2617
2650
  root: Boolean(rootBounds),
2618
2651
  rootBounds,
2619
- children,
2620
- refs: namespaceArrayToRefs(namespaces)
2652
+ children
2621
2653
  };
2622
2654
  world = { ...world, [newGroup.id]: newGroup };
2623
2655
  return [world, animatables, newGroup.id, newColorLookup];
@@ -2643,12 +2675,11 @@ function shouldImportAsGroupChild2(child) {
2643
2675
 
2644
2676
  // src/functions/gltf-loading/import-scene.ts
2645
2677
  Object3D7.DEFAULT_UP.set(0, 0, 1);
2646
- function importScene(scene, namespaces, rootBounds) {
2678
+ function importScene(scene, rootBounds) {
2647
2679
  let world = {};
2648
2680
  let animatables = {};
2649
2681
  const [newWorldItems, newAnimatables] = importGroup(
2650
2682
  scene,
2651
- namespaces,
2652
2683
  {},
2653
2684
  rootBounds
2654
2685
  );
@@ -2659,7 +2690,7 @@ function importScene(scene, namespaces, rootBounds) {
2659
2690
 
2660
2691
  // src/functions/gltf-loading/traverse-three.ts
2661
2692
  THREE4.Object3D.DEFAULT_UP.set(0, 0, 1);
2662
- function traverseThree(group, namespaces, aggressiveImport = false, rootBounds) {
2693
+ function traverseThree(group, aggressiveImport = false, rootBounds) {
2663
2694
  const worldData = {};
2664
2695
  const animatableData = {};
2665
2696
  let hasRobotData = false;
@@ -2682,11 +2713,7 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2682
2713
  switch (data.type) {
2683
2714
  case "group":
2684
2715
  loadedData = {
2685
- ...data,
2686
- refs: namespaces.reduce(
2687
- (acc, ns) => ({ ...acc, [ns]: createRef3() }),
2688
- {}
2689
- )
2716
+ ...data
2690
2717
  };
2691
2718
  [mappedFeatures, animatableValues] = mapFeatures(data.features);
2692
2719
  isGroupFeatures(mappedFeatures);
@@ -2697,11 +2724,7 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2697
2724
  case "shape":
2698
2725
  loadedData = {
2699
2726
  ...data,
2700
- geometry: child.geometry,
2701
- refs: namespaces.reduce(
2702
- (acc, ns) => ({ ...acc, [ns]: createRef3() }),
2703
- {}
2704
- )
2727
+ geometry: child.geometry
2705
2728
  };
2706
2729
  [mappedFeatures, animatableValues] = mapFeatures(data.features);
2707
2730
  isShapeFeatures(mappedFeatures);
@@ -2711,11 +2734,7 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2711
2734
  break;
2712
2735
  case "ellipse":
2713
2736
  loadedData = {
2714
- ...data,
2715
- refs: namespaces.reduce(
2716
- (acc, ns) => ({ ...acc, [ns]: createRef3() }),
2717
- {}
2718
- )
2737
+ ...data
2719
2738
  };
2720
2739
  [mappedFeatures, animatableValues] = mapFeatures(data.features);
2721
2740
  isEllipseFeatures(mappedFeatures);
@@ -2725,11 +2744,7 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2725
2744
  break;
2726
2745
  case "rectangle":
2727
2746
  loadedData = {
2728
- ...data,
2729
- refs: namespaces.reduce(
2730
- (acc, ns) => ({ ...acc, [ns]: createRef3() }),
2731
- {}
2732
- )
2747
+ ...data
2733
2748
  };
2734
2749
  [mappedFeatures, animatableValues] = mapFeatures(data.features);
2735
2750
  isRectangleFeatures(mappedFeatures);
@@ -2752,7 +2767,6 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2752
2767
  }
2753
2768
  const [newWorldData, newAnimatableData] = importScene(
2754
2769
  group,
2755
- namespaces,
2756
2770
  derivedRootBounds
2757
2771
  );
2758
2772
  Object.assign(worldData, newWorldData);
@@ -3468,16 +3482,6 @@ async function resolveParserJson(parserJson, fallback) {
3468
3482
  return fromArrayBuffer;
3469
3483
  }
3470
3484
  }
3471
- if (fallback.blob) {
3472
- try {
3473
- const blobBuffer = typeof fallback.blob.arrayBuffer === "function" ? await fallback.blob.arrayBuffer() : await new Response(fallback.blob).arrayBuffer();
3474
- const fromBlob = parseGlbJsonChunk(blobBuffer);
3475
- if (fromBlob && typeof fromBlob === "object") {
3476
- return fromBlob;
3477
- }
3478
- } catch {
3479
- }
3480
- }
3481
3485
  if (fallback.url && typeof fetch === "function") {
3482
3486
  try {
3483
3487
  const response = await fetch(fallback.url);
@@ -3493,73 +3497,21 @@ async function resolveParserJson(parserJson, fallback) {
3493
3497
  }
3494
3498
  return parserJson;
3495
3499
  }
3496
- async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
3497
- const asset = await loadGLTFWithBundleInternal(
3498
- url,
3499
- namespaces,
3500
+ async function loadGLTF(url, aggressiveImport = false, rootBounds) {
3501
+ const asset = await loadGLTFWithBundle(url, aggressiveImport, rootBounds);
3502
+ return [asset.world, asset.animatables, asset.animations];
3503
+ }
3504
+ async function loadGLTFFromBlob(blob, aggressiveImport = false, rootBounds) {
3505
+ const asset = await loadGLTFFromBlobWithBundle(
3506
+ blob,
3500
3507
  aggressiveImport,
3501
- rootBounds,
3502
- "url"
3508
+ rootBounds
3503
3509
  );
3504
3510
  return [asset.world, asset.animatables, asset.animations];
3505
3511
  }
3506
- async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, rootBounds) {
3507
- const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3508
- if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
3509
- const objectUrl = URL.createObjectURL(blob);
3510
- try {
3511
- const asset = await loadGLTFWithBundleInternal(
3512
- objectUrl,
3513
- actualizedNamespaces,
3514
- aggressiveImport,
3515
- rootBounds,
3516
- "blob",
3517
- { blob }
3518
- );
3519
- return [asset.world, asset.animatables, asset.animations];
3520
- } finally {
3521
- URL.revokeObjectURL(objectUrl);
3522
- }
3523
- }
3524
- const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
3525
- return new Promise((resolve, reject) => {
3526
- const loader = createTrackedGLTFLoader();
3527
- loader.parse(
3528
- arrayBuffer,
3529
- "",
3530
- async (gltf) => {
3531
- try {
3532
- const parserJson = await resolveParserJson(
3533
- gltf?.parser?.json,
3534
- { arrayBuffer }
3535
- );
3536
- const asset = parseScene(
3537
- gltf.scene,
3538
- actualizedNamespaces,
3539
- aggressiveImport,
3540
- rootBounds,
3541
- parserJson,
3542
- gltf.animations
3543
- );
3544
- resolve([asset.world, asset.animatables, asset.animations]);
3545
- } catch (error) {
3546
- if (error instanceof Error) {
3547
- reject(error);
3548
- } else {
3549
- reject(new Error(String(error)));
3550
- }
3551
- }
3552
- },
3553
- (error) => {
3554
- reject(new Error(`Error loading GLTF: ${error.message}`));
3555
- }
3556
- );
3557
- });
3558
- }
3559
- function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson, clips) {
3512
+ function parseScene(scene, aggressiveImport, rootBounds, parserJson, clips) {
3560
3513
  const [world, animatables] = traverseThree(
3561
3514
  scene,
3562
- namespaces,
3563
3515
  aggressiveImport,
3564
3516
  rootBounds
3565
3517
  );
@@ -3574,122 +3526,42 @@ function createTrackedGLTFLoader() {
3574
3526
  modelLoader.setDRACOLoader(new DRACOLoader());
3575
3527
  return modelLoader;
3576
3528
  }
3577
- async function loadGLTFWithBundleInternal(url, namespaces, aggressiveImport = false, rootBounds, sourceKind = "url", parserJsonFallback) {
3578
- if (sourceKind === "url") {
3579
- recordRenderCounter("gltfUrlLoadCount");
3580
- } else {
3581
- recordRenderCounter("gltfBlobLoadCount");
3582
- }
3529
+ async function loadGLTFWithBundle(url, aggressiveImport = false, rootBounds, parserJsonFallback) {
3530
+ recordRenderCounter("gltfUrlLoadCount");
3583
3531
  const modelLoader = createTrackedGLTFLoader();
3584
3532
  const modelData = await modelLoader.loadAsync(url);
3585
3533
  const parserJson = await resolveParserJson(modelData?.parser?.json, {
3586
3534
  url,
3587
3535
  ...parserJsonFallback
3588
3536
  });
3589
- const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3590
3537
  return parseScene(
3591
3538
  modelData.scene,
3592
- actualizedNamespaces,
3593
3539
  aggressiveImport,
3594
3540
  rootBounds,
3595
3541
  parserJson,
3596
3542
  modelData.animations
3597
3543
  );
3598
3544
  }
3599
- async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
3600
- return loadGLTFWithBundleInternal(
3601
- url,
3602
- namespaces,
3545
+ async function loadGLTFFromBlobWithBundle(blob, aggressiveImport = false, rootBounds) {
3546
+ recordRenderCounter("gltfBlobLoadCount");
3547
+ const arrayBuffer = await blob.arrayBuffer();
3548
+ const loader = createTrackedGLTFLoader();
3549
+ const gltf = await new Promise((resolve, reject) => {
3550
+ loader.parse(arrayBuffer, "", resolve, (error) => {
3551
+ reject(new Error(`Error loading GLTF: ${error.message}`));
3552
+ });
3553
+ });
3554
+ const parserJson = await resolveParserJson(gltf?.parser?.json, {
3555
+ arrayBuffer
3556
+ });
3557
+ return parseScene(
3558
+ gltf.scene,
3603
3559
  aggressiveImport,
3604
3560
  rootBounds,
3605
- "url",
3606
- parserJsonFallback
3561
+ parserJson,
3562
+ gltf.animations
3607
3563
  );
3608
3564
  }
3609
- async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = false, rootBounds) {
3610
- const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3611
- if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
3612
- const objectUrl = URL.createObjectURL(blob);
3613
- try {
3614
- return await loadGLTFWithBundleInternal(
3615
- objectUrl,
3616
- actualizedNamespaces,
3617
- aggressiveImport,
3618
- rootBounds,
3619
- "blob",
3620
- { blob }
3621
- );
3622
- } finally {
3623
- URL.revokeObjectURL(objectUrl);
3624
- }
3625
- }
3626
- const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
3627
- return new Promise((resolve, reject) => {
3628
- const loader = createTrackedGLTFLoader();
3629
- loader.parse(
3630
- arrayBuffer,
3631
- "",
3632
- async (gltf) => {
3633
- try {
3634
- const parserJson = await resolveParserJson(
3635
- gltf?.parser?.json,
3636
- { arrayBuffer }
3637
- );
3638
- const asset = parseScene(
3639
- gltf.scene,
3640
- actualizedNamespaces,
3641
- aggressiveImport,
3642
- rootBounds,
3643
- parserJson,
3644
- gltf.animations
3645
- );
3646
- resolve(asset);
3647
- } catch (error) {
3648
- if (error instanceof Error) {
3649
- reject(error);
3650
- } else {
3651
- reject(new Error(String(error)));
3652
- }
3653
- }
3654
- },
3655
- (error) => {
3656
- reject(new Error(`Error loading GLTF: ${error.message}`));
3657
- }
3658
- );
3659
- });
3660
- }
3661
-
3662
- // src/functions/load-gltf-blob.ts
3663
- import { DRACOLoader as DRACOLoader2, GLTFLoader as GLTFLoader2 } from "three-stdlib";
3664
- var loadGltfFromBlob = (blob, namespaces) => {
3665
- recordRenderCounter("gltfLoadCount");
3666
- recordRenderCounter("gltfBlobLoadCount");
3667
- recordRenderCounter("dracoLoaderCreationCount");
3668
- return new Promise((resolve, reject) => {
3669
- const loader = new GLTFLoader2();
3670
- loader.setDRACOLoader(new DRACOLoader2());
3671
- const reader = new FileReader();
3672
- reader.onload = () => {
3673
- const arrayBuffer = reader.result;
3674
- loader.parse(
3675
- arrayBuffer,
3676
- "",
3677
- // Base path for resolving external resources
3678
- (gltf) => {
3679
- const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3680
- resolve(traverseThree(gltf.scene, actualizedNamespaces));
3681
- },
3682
- (error) => {
3683
- reject(new Error(`Error loading GLTF: ${error.message}`));
3684
- }
3685
- );
3686
- };
3687
- reader.onerror = () => {
3688
- reject(new Error("Failed to read Blob as ArrayBuffer."));
3689
- };
3690
- reader.readAsArrayBuffer(blob);
3691
- });
3692
- };
3693
3565
 
3694
3566
  // src/functions/export.ts
3695
3567
  import { GLTFExporter } from "three-stdlib";
@@ -3704,17 +3576,6 @@ function normalizeExportError(error) {
3704
3576
  }
3705
3577
  return new Error("Failed to export scene.");
3706
3578
  }
3707
- function triggerBlobDownload(blob, filename) {
3708
- const url = URL.createObjectURL(blob);
3709
- const link = document.createElement("a");
3710
- link.href = url;
3711
- link.download = filename;
3712
- link.style.display = "none";
3713
- document.body.appendChild(link);
3714
- link.click();
3715
- document.body.removeChild(link);
3716
- setTimeout(() => URL.revokeObjectURL(url), 0);
3717
- }
3718
3579
  var GLB_MAGIC2 = 1179937895;
3719
3580
  var GLB_VERSION2 = 2;
3720
3581
  var GLB_JSON_CHUNK_TYPE2 = 1313821514;
@@ -3796,7 +3657,7 @@ function normalizeExportedSceneJson(json, fallbackSceneName) {
3796
3657
  }
3797
3658
  return changed;
3798
3659
  }
3799
- function sanitizeExportedGlb(buffer, fallbackSceneName) {
3660
+ function patchGlbJson(buffer, mutate) {
3800
3661
  if (buffer.byteLength < GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2) {
3801
3662
  return buffer;
3802
3663
  }
@@ -3826,8 +3687,7 @@ function sanitizeExportedGlb(buffer, fallbackSceneName) {
3826
3687
  } catch {
3827
3688
  return buffer;
3828
3689
  }
3829
- const changed = normalizeExportedSceneJson(jsonPayload, fallbackSceneName);
3830
- if (!changed) {
3690
+ if (!mutate(jsonPayload)) {
3831
3691
  return buffer;
3832
3692
  }
3833
3693
  const encodedJson = new TextEncoder().encode(JSON.stringify(jsonPayload));
@@ -3849,11 +3709,41 @@ function sanitizeExportedGlb(buffer, fallbackSceneName) {
3849
3709
  sanitizedBytes.set(remainingChunks, jsonChunkStart + paddedJsonLength);
3850
3710
  return sanitized;
3851
3711
  }
3852
- function exportScene(data, fileNameOrOptions = "scene.glb") {
3853
- const options = typeof fileNameOrOptions === "string" ? { fileName: fileNameOrOptions } : fileNameOrOptions ?? {};
3854
- const fileName = options.fileName ?? "scene.glb";
3855
- const animationClips = Array.isArray(options.animations) ? options.animations.filter(Boolean) : [];
3856
- const shouldAttachBundle = Boolean(options.bundle);
3712
+ function sanitizeExportedGlb(buffer, fallbackSceneName) {
3713
+ return patchGlbJson(
3714
+ buffer,
3715
+ (json) => normalizeExportedSceneJson(json, fallbackSceneName)
3716
+ );
3717
+ }
3718
+ function patchVizijBundleMetadata(glb, metadata) {
3719
+ return patchGlbJson(glb, (json) => {
3720
+ const carriers = [];
3721
+ const rootExtensions = json.extensions;
3722
+ if (rootExtensions && typeof rootExtensions === "object") {
3723
+ carriers.push(rootExtensions);
3724
+ }
3725
+ const nodes = Array.isArray(json.nodes) ? json.nodes : [];
3726
+ for (const node of nodes) {
3727
+ const extensions = node?.extensions;
3728
+ if (extensions && typeof extensions === "object") {
3729
+ carriers.push(extensions);
3730
+ }
3731
+ }
3732
+ let patched = false;
3733
+ for (const extensions of carriers) {
3734
+ const bundle = extensions.VIZIJ_bundle;
3735
+ if (!bundle || typeof bundle !== "object") {
3736
+ continue;
3737
+ }
3738
+ const record = bundle;
3739
+ const existing = record.metadata && typeof record.metadata === "object" ? record.metadata : {};
3740
+ record.metadata = { ...existing, ...metadata };
3741
+ patched = true;
3742
+ }
3743
+ return patched;
3744
+ });
3745
+ }
3746
+ async function exportScene(root, options = {}) {
3857
3747
  const exporter = new GLTFExporter();
3858
3748
  exporter.register(() => ({
3859
3749
  writeMesh(mesh, meshDef) {
@@ -3863,81 +3753,330 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
3863
3753
  }
3864
3754
  }
3865
3755
  }));
3866
- const sourceRoot = data;
3867
- const exportRoot = sourceRoot instanceof THREE6.Scene ? sourceRoot : sourceRoot.clone(true);
3756
+ const exportRoot = root instanceof THREE6.Scene ? root : root.clone(true);
3868
3757
  const exportTarget = exportRoot instanceof THREE6.Scene ? exportRoot : (() => {
3869
3758
  const scene = new THREE6.Scene();
3870
- scene.name = data.name?.trim() || "Scene";
3759
+ scene.name = root.name?.trim() || "Scene";
3871
3760
  scene.add(exportRoot);
3872
3761
  return scene;
3873
3762
  })();
3874
- const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(exportRoot, options.bundle) : () => {
3763
+ const detachBundle = options.bundle ? applyVizijBundle(exportRoot, options.bundle) : () => {
3875
3764
  };
3876
- const binary = options.binary ?? true;
3877
- const exporterOptions = {
3878
- trs: true,
3879
- onlyVisible: false,
3880
- binary,
3881
- includeCustomExtensions: true
3882
- };
3883
- if (animationClips.length > 0) {
3884
- exporterOptions.animations = animationClips;
3885
- }
3765
+ const animations = (options.animations ?? []).filter(Boolean);
3766
+ let gltf;
3886
3767
  try {
3887
- exporter.parse(
3888
- exportTarget,
3889
- (gltf) => {
3890
- detachBundle();
3891
- if (!(gltf instanceof ArrayBuffer)) {
3892
- const error = new Error("Failed to export scene.");
3893
- options.onError?.(error);
3894
- return;
3895
- }
3896
- const sanitizedGltf = sanitizeExportedGlb(
3897
- gltf,
3898
- data.name?.trim() || void 0
3899
- );
3900
- const trimmed = fileName.trim();
3901
- const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
3902
- const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
3903
- triggerBlobDownload(
3904
- new Blob([sanitizedGltf], {
3905
- type: "application/octet-stream"
3906
- }),
3907
- downloadName
3908
- );
3909
- options.onComplete?.();
3910
- },
3911
- (error) => {
3912
- detachBundle();
3913
- options.onError?.(normalizeExportError(error));
3914
- },
3915
- exporterOptions
3916
- );
3768
+ gltf = await exporter.parseAsync(exportTarget, {
3769
+ trs: true,
3770
+ onlyVisible: false,
3771
+ binary: true,
3772
+ includeCustomExtensions: true,
3773
+ ...animations.length > 0 ? { animations } : {}
3774
+ });
3917
3775
  } catch (error) {
3776
+ throw normalizeExportError(error);
3777
+ } finally {
3918
3778
  detachBundle();
3919
- const normalizedError = normalizeExportError(error);
3920
- options.onError?.(normalizedError);
3921
- throw normalizedError;
3779
+ }
3780
+ if (!(gltf instanceof ArrayBuffer)) {
3781
+ throw new Error("Failed to export scene.");
3782
+ }
3783
+ return sanitizeExportedGlb(gltf, root.name?.trim() || void 0);
3784
+ }
3785
+
3786
+ // src/functions/build-scene-graph.ts
3787
+ import * as THREE7 from "three";
3788
+ import {
3789
+ instanceOfRawEuler as instanceOfRawEuler5,
3790
+ instanceOfRawHSL as instanceOfRawHSL4,
3791
+ instanceOfRawNumber as instanceOfRawNumber5,
3792
+ instanceOfRawRGB as instanceOfRawRGB4,
3793
+ instanceOfRawVector2 as instanceOfRawVector24,
3794
+ instanceOfRawVector3 as instanceOfRawVector35
3795
+ } from "@vizij/utils";
3796
+ THREE7.Object3D.DEFAULT_UP.set(0, 0, 1);
3797
+ function buildSceneGraph(world, animatables, rootId) {
3798
+ const entry = world[rootId];
3799
+ if (!entry) {
3800
+ throw new Error(`The world has no element "${rootId}".`);
3801
+ }
3802
+ const used = {};
3803
+ for (const feature of Object.values(entry.features)) {
3804
+ if (feature.animated) {
3805
+ used[feature.value] = animatableOf(animatables, feature.value);
3806
+ }
3807
+ }
3808
+ const robotData = createStoredRenderable(entry, used);
3809
+ switch (entry.type) {
3810
+ case "group": {
3811
+ const object = new THREE7.Group();
3812
+ object.name = entry.name;
3813
+ object.userData = { gltfExtensions: { RobotData: robotData } };
3814
+ applyFeatures(entry.features, animatables, groupTransform(object));
3815
+ for (const child of entry.children) {
3816
+ object.add(buildSceneGraph(world, animatables, child));
3817
+ }
3818
+ return object;
3819
+ }
3820
+ case "shape": {
3821
+ const geometry = entry.geometry.clone();
3822
+ if (entry.name) {
3823
+ geometry.name = entry.name;
3824
+ }
3825
+ const material = createMaterial(entry.material);
3826
+ material.name = deriveMaterialName(entry, used) ?? entry.name ?? "";
3827
+ const object = new THREE7.Mesh(geometry, material);
3828
+ object.name = entry.name;
3829
+ object.userData = { gltfExtensions: { RobotData: robotData } };
3830
+ applyFeatures(entry.features, animatables, {
3831
+ ...shapeTransform(object),
3832
+ ...materialFactors(material),
3833
+ ...morphWeights(object, entry)
3834
+ });
3835
+ for (const child of entry.children ?? []) {
3836
+ object.add(buildSceneGraph(world, animatables, child));
3837
+ }
3838
+ return object;
3839
+ }
3840
+ default:
3841
+ throw new Error(
3842
+ `Element "${rootId}" is a ${entry.type}, which has no exported form.`
3843
+ );
3844
+ }
3845
+ }
3846
+ function animatableOf(animatables, id) {
3847
+ const animatable = animatables[id];
3848
+ if (!animatable) {
3849
+ throw new Error(`No animatable "${id}".`);
3850
+ }
3851
+ return animatable;
3852
+ }
3853
+ function applyFeatures(features, animatables, handlers) {
3854
+ for (const [key, handler] of Object.entries(handlers)) {
3855
+ const feature = features[key];
3856
+ if (!feature) {
3857
+ continue;
3858
+ }
3859
+ const value = feature.animated ? authoredDefault(animatableOf(animatables, feature.value)) : feature.value;
3860
+ if (value !== void 0) {
3861
+ handler(value);
3862
+ }
3863
+ }
3864
+ }
3865
+ function groupTransform(object) {
3866
+ return {
3867
+ translation: (value) => {
3868
+ if (instanceOfRawVector35(value)) {
3869
+ object.position.set(value.x, value.y, value.z);
3870
+ } else if (instanceOfRawVector24(value)) {
3871
+ object.position.set(value.x, value.y, object.position.z);
3872
+ }
3873
+ },
3874
+ rotation: (value) => {
3875
+ if (instanceOfRawEuler5(value)) {
3876
+ object.rotation.set(value.x, value.y, value.z, "ZYX");
3877
+ } else if (instanceOfRawNumber5(value)) {
3878
+ object.rotation.set(0, 0, 0);
3879
+ object.rotateZ(value);
3880
+ }
3881
+ },
3882
+ scale: (value) => {
3883
+ if (instanceOfRawVector35(value)) {
3884
+ if (value.x === null || value.y === null || value.z === null) {
3885
+ object.scale.set(0.1, 0.1, 0.1);
3886
+ } else {
3887
+ object.scale.set(value.x, value.y, value.z);
3888
+ }
3889
+ } else if (instanceOfRawNumber5(value)) {
3890
+ object.scale.set(value, value, value);
3891
+ } else {
3892
+ object.scale.set(1, 1, 1);
3893
+ }
3894
+ }
3895
+ };
3896
+ }
3897
+ function shapeTransform(object) {
3898
+ return {
3899
+ translation: (value) => {
3900
+ if (instanceOfRawVector35(value)) {
3901
+ object.position.set(value.x, value.y, value.z);
3902
+ }
3903
+ },
3904
+ rotation: (value) => {
3905
+ if (instanceOfRawEuler5(value)) {
3906
+ object.rotation.set(value.x, value.y, value.z, "ZYX");
3907
+ }
3908
+ },
3909
+ scale: (value) => {
3910
+ if (instanceOfRawVector35(value)) {
3911
+ object.scale.set(value.x, value.y, value.z);
3912
+ } else if (instanceOfRawNumber5(value)) {
3913
+ object.scale.set(value, value, value);
3914
+ } else {
3915
+ object.scale.set(1, 1, 1);
3916
+ }
3917
+ }
3918
+ };
3919
+ }
3920
+ function materialFactors(material) {
3921
+ const standard = material;
3922
+ const phong = material;
3923
+ return {
3924
+ opacity: (value) => {
3925
+ if (instanceOfRawNumber5(value)) {
3926
+ material.opacity = value;
3927
+ material.transparent = value < 1;
3928
+ }
3929
+ },
3930
+ color: (value) => {
3931
+ if (!standard.color) {
3932
+ return;
3933
+ }
3934
+ if (instanceOfRawRGB4(value)) {
3935
+ standard.color.setRGB(value.r, value.g, value.b);
3936
+ } else if (instanceOfRawVector35(value)) {
3937
+ standard.color.setRGB(value.x, value.y, value.z);
3938
+ } else if (instanceOfRawHSL4(value)) {
3939
+ standard.color.setHSL(value.h, value.s, value.l);
3940
+ }
3941
+ },
3942
+ roughness: (value) => {
3943
+ if (standard.roughness !== void 0 && instanceOfRawNumber5(value)) {
3944
+ standard.roughness = value;
3945
+ }
3946
+ },
3947
+ metalness: (value) => {
3948
+ if (standard.metalness !== void 0 && instanceOfRawNumber5(value)) {
3949
+ standard.metalness = value;
3950
+ }
3951
+ },
3952
+ shininess: (value) => {
3953
+ if (phong.shininess !== void 0 && instanceOfRawNumber5(value)) {
3954
+ phong.shininess = value;
3955
+ }
3956
+ },
3957
+ emissive: (value) => {
3958
+ if (standard.emissive && instanceOfRawRGB4(value)) {
3959
+ standard.emissive.setRGB(value.r, value.g, value.b);
3960
+ }
3961
+ },
3962
+ emissiveIntensity: (value) => {
3963
+ if (standard.emissiveIntensity !== void 0 && instanceOfRawNumber5(value)) {
3964
+ standard.emissiveIntensity = value;
3965
+ }
3966
+ },
3967
+ specular: (value) => {
3968
+ if (phong.specular && instanceOfRawRGB4(value)) {
3969
+ phong.specular.setRGB(value.r, value.g, value.b);
3970
+ }
3971
+ }
3972
+ };
3973
+ }
3974
+ function morphWeights(object, shape) {
3975
+ const targets = shape.morphTargets ?? [];
3976
+ if (targets.length === 0) {
3977
+ return {};
3978
+ }
3979
+ const influences = targets.map(() => 0);
3980
+ object.morphTargetDictionary = Object.fromEntries(
3981
+ targets.map((target, index) => [target, index])
3982
+ );
3983
+ object.morphTargetInfluences = influences;
3984
+ return Object.fromEntries(
3985
+ targets.map((target, index) => [
3986
+ target,
3987
+ (value) => {
3988
+ if (instanceOfRawNumber5(value)) {
3989
+ influences[index] = value;
3990
+ }
3991
+ }
3992
+ ])
3993
+ );
3994
+ }
3995
+ function createMaterial(kind) {
3996
+ const parameters = { side: THREE7.DoubleSide };
3997
+ switch (kind) {
3998
+ case "basic" /* Basic */:
3999
+ return new THREE7.MeshBasicMaterial(parameters);
4000
+ case "lambert" /* Lambert */:
4001
+ return new THREE7.MeshLambertMaterial(parameters);
4002
+ case "phong" /* Phong */:
4003
+ return new THREE7.MeshPhongMaterial(parameters);
4004
+ case "normal" /* Normal */:
4005
+ return new THREE7.MeshNormalMaterial(parameters);
4006
+ case "standard" /* Standard */:
4007
+ return new THREE7.MeshStandardMaterial(parameters);
4008
+ }
4009
+ }
4010
+ var SCALAR_KEYS = ["f32", "f64", "i64", "i32", "u64", "u32", "float"];
4011
+ function scalarOf(value) {
4012
+ if (typeof value === "number") {
4013
+ return value;
4014
+ }
4015
+ if (value && typeof value === "object") {
4016
+ for (const key of SCALAR_KEYS) {
4017
+ const scalar = value[key];
4018
+ if (typeof scalar === "number") {
4019
+ return scalar;
4020
+ }
4021
+ }
4022
+ }
4023
+ return void 0;
4024
+ }
4025
+ function authoredDefault(animatable) {
4026
+ const value = animatable.default;
4027
+ const scalar = scalarOf(value);
4028
+ if (scalar !== void 0) {
4029
+ return scalar;
4030
+ }
4031
+ const fields = value?.struct?.fields;
4032
+ if (!Array.isArray(fields)) {
4033
+ return value;
4034
+ }
4035
+ const n = fields.map(
4036
+ (field) => scalarOf(field?.value) ?? 0
4037
+ );
4038
+ switch (animatable.type) {
4039
+ case "vector3":
4040
+ case "euler":
4041
+ return { x: n[0], y: n[1], z: n[2] };
4042
+ case "vector2":
4043
+ return { x: n[0], y: n[1] };
4044
+ case "rgb":
4045
+ return { r: n[0], g: n[1], b: n[2] };
4046
+ case "hsl":
4047
+ return { h: n[0], s: n[1], l: n[2] };
4048
+ default:
4049
+ return void 0;
3922
4050
  }
3923
4051
  }
3924
4052
  export {
4053
+ DEFAULT_TONE_MAPPING_MODE,
3925
4054
  EmptyModelError,
3926
4055
  InnerVizij,
3927
4056
  ShapeMaterial,
4057
+ TONE_MAPPING_LABELS,
4058
+ TONE_MAPPING_MODES,
3928
4059
  Vizij,
3929
4060
  VizijContext,
3930
4061
  VizijSlice,
3931
4062
  applyVizijBundle,
4063
+ buildSceneGraph,
3932
4064
  createVizijStore,
4065
+ deriveMorphFeatureKeys,
4066
+ deriveRootBounds,
3933
4067
  exportScene,
3934
4068
  extractVizijBundle,
4069
+ isToneMappingMode,
3935
4070
  loadGLTF,
3936
4071
  loadGLTFFromBlob,
3937
4072
  loadGLTFFromBlobWithBundle,
3938
4073
  loadGLTFWithBundle,
3939
- loadGltfFromBlob,
4074
+ normalizeToneMappingMode,
3940
4075
  parseGlbJsonChunk,
4076
+ patchGlbJson,
4077
+ patchVizijBundleMetadata,
4078
+ resolveToneMapping,
4079
+ sanitizeMorphKey,
3941
4080
  useDefaultVizijStore,
3942
4081
  useFeatures,
3943
4082
  useVizijStore,