@vizij/render 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  loadGLTFFromBlobWithBundle: () => loadGLTFFromBlobWithBundle,
46
46
  loadGLTFWithBundle: () => loadGLTFWithBundle,
47
47
  loadGltfFromBlob: () => loadGltfFromBlob,
48
+ parseGlbJsonChunk: () => parseGlbJsonChunk,
48
49
  useDefaultVizijStore: () => useDefaultVizijStore,
49
50
  useFeatures: () => useFeatures,
50
51
  useVizijStore: () => useVizijStore,
@@ -1005,6 +1006,48 @@ function InnerRenderedShape({
1005
1006
  );
1006
1007
  }
1007
1008
  },
1009
+ roughness: (value) => {
1010
+ const standard = material.current;
1011
+ if (standard?.roughness !== void 0 && (0, import_utils5.instanceOfRawNumber)(value)) {
1012
+ standard.roughness = value;
1013
+ standard.needsUpdate = true;
1014
+ }
1015
+ },
1016
+ metalness: (value) => {
1017
+ const standard = material.current;
1018
+ if (standard?.metalness !== void 0 && (0, import_utils5.instanceOfRawNumber)(value)) {
1019
+ standard.metalness = value;
1020
+ standard.needsUpdate = true;
1021
+ }
1022
+ },
1023
+ shininess: (value) => {
1024
+ const phong = material.current;
1025
+ if (phong?.shininess !== void 0 && (0, import_utils5.instanceOfRawNumber)(value)) {
1026
+ phong.shininess = value;
1027
+ phong.needsUpdate = true;
1028
+ }
1029
+ },
1030
+ emissive: (value) => {
1031
+ const standard = material.current;
1032
+ if (standard?.emissive && (0, import_utils5.instanceOfRawRGB)(value)) {
1033
+ standard.emissive.setRGB(value.r, value.g, value.b);
1034
+ standard.needsUpdate = true;
1035
+ }
1036
+ },
1037
+ emissiveIntensity: (value) => {
1038
+ const standard = material.current;
1039
+ if (standard?.emissiveIntensity !== void 0 && (0, import_utils5.instanceOfRawNumber)(value)) {
1040
+ standard.emissiveIntensity = value;
1041
+ standard.needsUpdate = true;
1042
+ }
1043
+ },
1044
+ specular: (value) => {
1045
+ const phong = material.current;
1046
+ if (phong?.specular && (0, import_utils5.instanceOfRawRGB)(value)) {
1047
+ phong.specular.setRGB(value.r, value.g, value.b);
1048
+ phong.needsUpdate = true;
1049
+ }
1050
+ },
1008
1051
  ...morphTargetSettings[2]
1009
1052
  },
1010
1053
  shape
@@ -1125,7 +1168,10 @@ var MATERIAL_FEATURE_KEYS = [
1125
1168
  "opacity",
1126
1169
  "roughness",
1127
1170
  "metalness",
1128
- "shininess"
1171
+ "shininess",
1172
+ "emissive",
1173
+ "emissiveIntensity",
1174
+ "specular"
1129
1175
  ];
1130
1176
  var MATERIAL_NAME_SUFFIXES = [
1131
1177
  " color",
@@ -1134,7 +1180,10 @@ var MATERIAL_NAME_SUFFIXES = [
1134
1180
  " opacity",
1135
1181
  " roughness",
1136
1182
  " metalness",
1137
- " shininess"
1183
+ " shininess",
1184
+ " emissive intensity",
1185
+ " emissive",
1186
+ " specular"
1138
1187
  ];
1139
1188
  function deriveMaterialName(shape, values) {
1140
1189
  for (const key of MATERIAL_FEATURE_KEYS) {
@@ -1241,19 +1290,20 @@ var import_zustand2 = require("zustand");
1241
1290
  var import_middleware = require("zustand/middleware");
1242
1291
  var import_immer = require("immer");
1243
1292
  var THREE3 = __toESM(require("three"));
1244
- var import_utils6 = require("@vizij/utils");
1293
+ var import_utils8 = require("@vizij/utils");
1245
1294
 
1246
1295
  // src/actions/create-new-element.ts
1247
1296
  var import_react9 = require("react");
1248
1297
  var import_lodash3 = require("lodash");
1249
1298
 
1250
1299
  // src/functions/create-world-element.ts
1300
+ var import_utils6 = require("@vizij/utils");
1251
1301
  function createDefaultGroup(partialBody) {
1252
1302
  const translation = partialBody.features?.translation || { animated: false, value: { x: 0, y: 0 } };
1253
1303
  const rotation = partialBody.features?.rotation || { animated: false, value: { x: 0, y: 0, z: 0 } };
1254
1304
  const scale = partialBody.features?.scale || { animated: false, value: { x: 1, y: 1, z: 1 } };
1255
1305
  return {
1256
- id: partialBody.id || crypto.randomUUID(),
1306
+ id: partialBody.id || (0, import_utils6.createBrowserSafeId)(),
1257
1307
  name: partialBody.name || "new-body",
1258
1308
  type: partialBody.type || "group",
1259
1309
  tags: partialBody.tags || [],
@@ -1314,7 +1364,32 @@ function removeFromTree(state, nodesToRemove) {
1314
1364
  });
1315
1365
  }
1316
1366
 
1367
+ // src/memoryInvestigation.ts
1368
+ function getGlobalMemoryInvestigation() {
1369
+ return globalThis;
1370
+ }
1371
+ function updateRenderState(updater) {
1372
+ const globalObj = getGlobalMemoryInvestigation();
1373
+ if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
1374
+ return;
1375
+ }
1376
+ if (!globalObj.__vizijMemoryDebugState) {
1377
+ globalObj.__vizijMemoryDebugState = {};
1378
+ }
1379
+ if (!globalObj.__vizijMemoryDebugState.render) {
1380
+ globalObj.__vizijMemoryDebugState.render = {};
1381
+ }
1382
+ updater(globalObj.__vizijMemoryDebugState.render);
1383
+ }
1384
+ function recordRenderCounter(key, delta = 1) {
1385
+ updateRenderState((renderState) => {
1386
+ const current = typeof renderState[key] === "number" ? Number(renderState[key]) : 0;
1387
+ renderState[key] = current + delta;
1388
+ });
1389
+ }
1390
+
1317
1391
  // src/functions/create-animatable.ts
1392
+ var import_utils7 = require("@vizij/utils");
1318
1393
  function createAnimatable(value) {
1319
1394
  if (!value.type) {
1320
1395
  return null;
@@ -1324,7 +1399,7 @@ function createAnimatable(value) {
1324
1399
  }
1325
1400
  if (value.type === "euler") {
1326
1401
  const newAnimatable = {
1327
- id: value.id ?? crypto.randomUUID(),
1402
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1328
1403
  name: `${value.name} Rotation`,
1329
1404
  type: value.type,
1330
1405
  default: value.default ?? { x: 0, y: 0, z: 0 },
@@ -1341,7 +1416,7 @@ function createAnimatable(value) {
1341
1416
  return newAnimatable;
1342
1417
  } else if (value.type === "vector3") {
1343
1418
  const newAnimatable = {
1344
- id: value.id ?? crypto.randomUUID(),
1419
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1345
1420
  name: `${value.name} Vector3`,
1346
1421
  type: value.type,
1347
1422
  default: value.default ?? { x: 0, y: 0, z: 0 },
@@ -1358,7 +1433,7 @@ function createAnimatable(value) {
1358
1433
  return newAnimatable;
1359
1434
  } else if (value.type === "string") {
1360
1435
  const newAnimatable = {
1361
- id: value.id ?? crypto.randomUUID(),
1436
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1362
1437
  name: value.name,
1363
1438
  type: value.type,
1364
1439
  default: value.default ?? "Hello World",
@@ -1371,7 +1446,7 @@ function createAnimatable(value) {
1371
1446
  return newAnimatable;
1372
1447
  } else if (value.type === "number") {
1373
1448
  const newAnimatable = {
1374
- id: value.id ?? crypto.randomUUID(),
1449
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1375
1450
  name: value.name,
1376
1451
  type: value.type,
1377
1452
  default: value.default ?? 0,
@@ -1384,7 +1459,7 @@ function createAnimatable(value) {
1384
1459
  return newAnimatable;
1385
1460
  } else if (value.type === "boolean") {
1386
1461
  const newAnimatable = {
1387
- id: value.id ?? crypto.randomUUID(),
1462
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1388
1463
  name: value.name,
1389
1464
  type: value.type,
1390
1465
  default: value.default ?? false,
@@ -1397,7 +1472,7 @@ function createAnimatable(value) {
1397
1472
  return newAnimatable;
1398
1473
  } else if (value.type === "rgb") {
1399
1474
  const newAnimatable = {
1400
- id: value.id ?? crypto.randomUUID(),
1475
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1401
1476
  name: value.name,
1402
1477
  type: value.type,
1403
1478
  default: value.default ?? { r: 0, g: 0, b: 0 },
@@ -1414,7 +1489,7 @@ function createAnimatable(value) {
1414
1489
  return newAnimatable;
1415
1490
  } else if (value.type === "hsl") {
1416
1491
  const newAnimatable = {
1417
- id: value.id ?? crypto.randomUUID(),
1492
+ id: value.id ?? (0, import_utils7.createBrowserSafeId)(),
1418
1493
  name: value.name,
1419
1494
  type: value.type,
1420
1495
  default: value.default ?? { h: 0, s: 0, l: 0 },
@@ -1434,6 +1509,35 @@ function createAnimatable(value) {
1434
1509
  }
1435
1510
  createAnimatable({ type: "euler", name: "Rotation" });
1436
1511
 
1512
+ // src/functions/exportable-bodies.ts
1513
+ function asGroupEntries(world) {
1514
+ return Object.values(world).filter((entry) => entry.type === "group");
1515
+ }
1516
+ function selectExportableGroupEntries(world, filterIds) {
1517
+ const groupEntries = asGroupEntries(world);
1518
+ const filterSet = Array.isArray(filterIds) && filterIds.length > 0 ? new Set(filterIds) : null;
1519
+ if (filterSet) {
1520
+ return groupEntries.filter((entry) => filterSet.has(entry.id));
1521
+ }
1522
+ const rootBoundsGroups = groupEntries.filter(
1523
+ (entry) => Boolean(entry.rootBounds)
1524
+ );
1525
+ if (rootBoundsGroups.length > 0) {
1526
+ return rootBoundsGroups;
1527
+ }
1528
+ const explicitRootGroups = groupEntries.filter(
1529
+ (entry) => entry.root === true
1530
+ );
1531
+ if (explicitRootGroups.length > 0) {
1532
+ return explicitRootGroups;
1533
+ }
1534
+ const topLevelGroups = groupEntries.filter((entry) => !entry.parent);
1535
+ if (topLevelGroups.length > 0) {
1536
+ return topLevelGroups;
1537
+ }
1538
+ return groupEntries;
1539
+ }
1540
+
1437
1541
  // src/store.ts
1438
1542
  THREE3.Object3D.DEFAULT_UP.set(0, 0, 1);
1439
1543
  (0, import_immer.enableMapSet)();
@@ -1509,23 +1613,17 @@ var VizijSlice = (set, get) => ({
1509
1613
  },
1510
1614
  getExportableBodies: (filterIds) => {
1511
1615
  const worldData = get().world;
1512
- if (!filterIds) {
1513
- const bodies = Object.values(worldData).filter((entry) => entry.type === "group" && entry.rootBounds).map((entry) => {
1514
- const firstNs = Object.keys(entry.refs)[0];
1515
- const refGroup = entry.refs[firstNs].current;
1516
- return refGroup;
1517
- });
1518
- return bodies;
1519
- } else {
1520
- const bodies = Object.values(worldData).filter(
1521
- (entry) => entry.type === "group" && entry.rootBounds && filterIds.includes(entry.id)
1522
- ).map((entry) => {
1523
- const firstNs = Object.keys(entry.refs)[0];
1524
- const refGroup = entry.refs[firstNs].current;
1525
- return refGroup;
1526
- });
1527
- return bodies;
1528
- }
1616
+ const candidateGroups = selectExportableGroupEntries(
1617
+ worldData,
1618
+ filterIds
1619
+ );
1620
+ return candidateGroups.flatMap((entry) => {
1621
+ const refs = Object.values(
1622
+ entry.refs ?? {}
1623
+ );
1624
+ const resolved = refs.find((ref) => ref?.current)?.current ?? null;
1625
+ return resolved ? [resolved] : [];
1626
+ });
1529
1627
  },
1530
1628
  setGeometry: (id, geometry) => {
1531
1629
  set(
@@ -1537,7 +1635,7 @@ var VizijSlice = (set, get) => ({
1537
1635
  setValue: (id, namespace, value) => {
1538
1636
  set(
1539
1637
  (0, import_immer.produce)((state) => {
1540
- const lookupId = (0, import_utils6.getLookup)(namespace, id);
1638
+ const lookupId = (0, import_utils8.getLookup)(namespace, id);
1541
1639
  if (typeof value === "function") {
1542
1640
  const current = state.values.get(lookupId);
1543
1641
  if (current !== void 0) {
@@ -1566,7 +1664,7 @@ var VizijSlice = (set, get) => ({
1566
1664
  set(
1567
1665
  (0, import_immer.produce)((state) => {
1568
1666
  writes.forEach(({ id, namespace, value }) => {
1569
- const lookupId = (0, import_utils6.getLookup)(namespace, id);
1667
+ const lookupId = (0, import_utils8.getLookup)(namespace, id);
1570
1668
  state.values.set(lookupId, value);
1571
1669
  });
1572
1670
  })
@@ -1697,8 +1795,8 @@ var VizijSlice = (set, get) => ({
1697
1795
  setSlot: (parentId, parentNamespace, childId, childNamespace) => {
1698
1796
  set(
1699
1797
  (0, import_immer.produce)((state) => {
1700
- const parentLookupId = (0, import_utils6.getLookup)(parentNamespace, parentId);
1701
- const childLookupId = (0, import_utils6.getLookup)(childNamespace, childId);
1798
+ const parentLookupId = (0, import_utils8.getLookup)(parentNamespace, parentId);
1799
+ const childLookupId = (0, import_utils8.getLookup)(childNamespace, childId);
1702
1800
  state.slotConfig[parentLookupId] = childLookupId;
1703
1801
  })
1704
1802
  );
@@ -1717,12 +1815,13 @@ var VizijSlice = (set, get) => ({
1717
1815
  clearSlot: (parentId, parentNamespace) => {
1718
1816
  set(
1719
1817
  (0, import_immer.produce)((state) => {
1720
- const parentLookupId = (0, import_utils6.getLookup)(parentNamespace, parentId);
1818
+ const parentLookupId = (0, import_utils8.getLookup)(parentNamespace, parentId);
1721
1819
  delete state.slotConfig[parentLookupId];
1722
1820
  })
1723
1821
  );
1724
1822
  },
1725
1823
  setVizij: (scene, animatables) => {
1824
+ recordRenderCounter("rootReplacementCount");
1726
1825
  set({
1727
1826
  world: scene,
1728
1827
  animatables
@@ -1730,6 +1829,7 @@ var VizijSlice = (set, get) => ({
1730
1829
  },
1731
1830
  addWorldElements(world, animatables, replace) {
1732
1831
  if (replace) {
1832
+ recordRenderCounter("rootReplacementCount");
1733
1833
  set({ world, animatables });
1734
1834
  } else {
1735
1835
  set((state) => ({
@@ -1774,7 +1874,7 @@ var import_three = require("three");
1774
1874
  var import_jsx_runtime6 = require("react/jsx-runtime");
1775
1875
  function SelectionGlowEffect({
1776
1876
  enabled = false,
1777
- color = "#ff1010ff",
1877
+ color = "#ff1010",
1778
1878
  opacity = 0.9,
1779
1879
  thresholdAngle = 2
1780
1880
  }) {
@@ -1880,6 +1980,13 @@ function Vizij({
1880
1980
  onPointerMissed
1881
1981
  }) {
1882
1982
  const ctx = (0, import_react11.useContext)(VizijContext);
1983
+ (0, import_react11.useEffect)(() => {
1984
+ recordRenderCounter("canvasMountCount");
1985
+ recordRenderCounter("mountedCanvasCount");
1986
+ return () => {
1987
+ recordRenderCounter("mountedCanvasCount", -1);
1988
+ };
1989
+ }, []);
1883
1990
  if (ctx) {
1884
1991
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1885
1992
  import_fiber2.Canvas,
@@ -1948,7 +2055,7 @@ function InnerVizij({
1948
2055
  makeDefault: true,
1949
2056
  position: [0, 0, 100],
1950
2057
  near: 0.1,
1951
- far: 101
2058
+ far: 1e3
1952
2059
  }
1953
2060
  ),
1954
2061
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react11.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -2126,6 +2233,7 @@ var import_three5 = require("three");
2126
2233
 
2127
2234
  // src/functions/gltf-loading/import-group.ts
2128
2235
  var import_three4 = require("three");
2236
+ var import_utils11 = require("@vizij/utils");
2129
2237
 
2130
2238
  // src/functions/util.ts
2131
2239
  var import_react15 = require("react");
@@ -2135,8 +2243,10 @@ function namespaceArrayToRefs(namespaces) {
2135
2243
 
2136
2244
  // src/functions/gltf-loading/import-mesh.ts
2137
2245
  var import_three3 = require("three");
2246
+ var import_utils10 = require("@vizij/utils");
2138
2247
 
2139
2248
  // src/functions/gltf-loading/import-geometry.ts
2249
+ var import_utils9 = require("@vizij/utils");
2140
2250
  function sanitizeMorphKey(name, fallbackIndex, used) {
2141
2251
  const baseName = name && name.trim().length > 0 ? name.trim() : `morph_${fallbackIndex + 1}`;
2142
2252
  const slug = baseName.toLowerCase().replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "");
@@ -2160,7 +2270,7 @@ function importGeometry(geometry, mesh) {
2160
2270
  const usedKeys = /* @__PURE__ */ new Set();
2161
2271
  Object.entries(mesh.morphTargetDictionary ?? {}).forEach(
2162
2272
  ([name, index]) => {
2163
- const morphId = crypto.randomUUID();
2273
+ const morphId = (0, import_utils9.createBrowserSafeId)();
2164
2274
  const featureKey = sanitizeMorphKey(name, index, usedKeys);
2165
2275
  morphIds.push(featureKey);
2166
2276
  features[featureKey] = {
@@ -2196,7 +2306,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2196
2306
  let animatables = {};
2197
2307
  let newColorLookup = {};
2198
2308
  const translationAnimatable = {
2199
- id: crypto.randomUUID(),
2309
+ id: (0, import_utils10.createBrowserSafeId)(),
2200
2310
  name: `${mesh.name ?? "Mesh"} translation`,
2201
2311
  type: "vector3",
2202
2312
  default: { x: mesh.position.x, y: mesh.position.y, z: mesh.position.z },
@@ -2212,7 +2322,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2212
2322
  [translationAnimatable.id]: translationAnimatable
2213
2323
  };
2214
2324
  const rotationAnimatable = {
2215
- id: crypto.randomUUID(),
2325
+ id: (0, import_utils10.createBrowserSafeId)(),
2216
2326
  name: `${mesh.name ?? "Mesh"} rotation`,
2217
2327
  type: "euler",
2218
2328
  default: { x: mesh.rotation.x, y: mesh.rotation.y, z: mesh.rotation.z },
@@ -2225,7 +2335,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2225
2335
  };
2226
2336
  animatables = { ...animatables, [rotationAnimatable.id]: rotationAnimatable };
2227
2337
  const scaleAnimatable = {
2228
- id: crypto.randomUUID(),
2338
+ id: (0, import_utils10.createBrowserSafeId)(),
2229
2339
  name: `${mesh.name ?? "Mesh"} scale`,
2230
2340
  type: "vector3",
2231
2341
  default: { x: mesh.scale.x, y: mesh.scale.y, z: mesh.scale.z },
@@ -2236,56 +2346,120 @@ function importMesh(mesh, namespaces, colorLookup) {
2236
2346
  }
2237
2347
  };
2238
2348
  animatables = { ...animatables, [scaleAnimatable.id]: scaleAnimatable };
2239
- const color = mesh.material.color;
2240
- let useEmissive = false;
2241
- if (color.r === 0 && color.g === 0 && color.b === 0 && mesh.material.emissive) {
2242
- color.copy(mesh.material.emissive);
2243
- useEmissive = true;
2244
- }
2245
- const colorName = mesh.material.name;
2246
- const colorAnimatable = {
2247
- id: crypto.randomUUID(),
2248
- name: mesh.material.name ?? `${mesh.name ?? "Mesh"} color`,
2249
- type: "rgb",
2250
- default: { r: color.r, g: color.g, b: color.b },
2251
- constraints: {
2252
- min: [0, 0, 0],
2253
- max: [1, 1, 1]
2254
- },
2349
+ const material = mesh.material;
2350
+ const color = material.color;
2351
+ const colorName = material.name;
2352
+ const label = (suffix) => colorName ? `${colorName} ${suffix}` : `${mesh.name ?? "Mesh"} ${suffix}`;
2353
+ const makeNumberAnimatable = (suffix, value, constraints) => ({
2354
+ id: (0, import_utils10.createBrowserSafeId)(),
2355
+ name: label(suffix),
2356
+ type: "number",
2357
+ default: value,
2358
+ constraints,
2255
2359
  pub: {
2256
2360
  public: true,
2257
- output: mesh.material.name ? `${mesh.material.name} color` : `${mesh.name ?? "Mesh"} color`
2361
+ output: label(suffix)
2258
2362
  }
2259
- };
2260
- const opacityAnimatable = {
2261
- id: crypto.randomUUID(),
2262
- name: mesh.material.name ? `${mesh.material.name} opacity` : `${mesh.name ?? "Mesh"} opacity`,
2263
- type: "number",
2264
- default: mesh.material.opacity,
2363
+ });
2364
+ const makeColorAnimatable = (suffix, value) => ({
2365
+ id: (0, import_utils10.createBrowserSafeId)(),
2366
+ name: label(suffix),
2367
+ type: "rgb",
2368
+ default: { r: value.r, g: value.g, b: value.b },
2265
2369
  constraints: {
2266
- min: 0,
2267
- max: 1
2370
+ min: [0, 0, 0],
2371
+ max: [1, 1, 1]
2268
2372
  },
2269
2373
  pub: {
2270
2374
  public: true,
2271
- output: mesh.material.name ? `${mesh.material.name} opacity` : `${mesh.name ?? "Mesh"} opacity`
2375
+ output: label(suffix)
2272
2376
  }
2377
+ });
2378
+ const colorAnimatable = {
2379
+ ...makeColorAnimatable("color", color),
2380
+ name: material.name ?? `${mesh.name ?? "Mesh"} color`
2273
2381
  };
2274
- let colorId = colorAnimatable.id;
2275
- let opacityId = opacityAnimatable.id;
2382
+ const opacityAnimatable = makeNumberAnimatable("opacity", material.opacity, {
2383
+ min: 0,
2384
+ max: 1
2385
+ });
2386
+ const materialAnimatables = [
2387
+ colorAnimatable,
2388
+ opacityAnimatable
2389
+ ];
2390
+ let roughnessAnimatable;
2391
+ let metalnessAnimatable;
2392
+ let emissiveAnimatable;
2393
+ let emissiveIntensityAnimatable;
2394
+ let shininessAnimatable;
2395
+ let specularAnimatable;
2396
+ if (material.isMeshStandardMaterial) {
2397
+ roughnessAnimatable = makeNumberAnimatable(
2398
+ "roughness",
2399
+ material.roughness,
2400
+ {
2401
+ min: 0,
2402
+ max: 1
2403
+ }
2404
+ );
2405
+ metalnessAnimatable = makeNumberAnimatable(
2406
+ "metalness",
2407
+ material.metalness,
2408
+ {
2409
+ min: 0,
2410
+ max: 1
2411
+ }
2412
+ );
2413
+ emissiveAnimatable = makeColorAnimatable("emissive", material.emissive);
2414
+ emissiveIntensityAnimatable = makeNumberAnimatable(
2415
+ "emissive intensity",
2416
+ material.emissiveIntensity,
2417
+ { min: 0 }
2418
+ );
2419
+ materialAnimatables.push(
2420
+ roughnessAnimatable,
2421
+ metalnessAnimatable,
2422
+ emissiveAnimatable,
2423
+ emissiveIntensityAnimatable
2424
+ );
2425
+ } else if (mesh.material.isMeshPhongMaterial) {
2426
+ const phong = mesh.material;
2427
+ shininessAnimatable = makeNumberAnimatable("shininess", phong.shininess, {
2428
+ min: 0
2429
+ });
2430
+ emissiveAnimatable = makeColorAnimatable("emissive", phong.emissive);
2431
+ emissiveIntensityAnimatable = makeNumberAnimatable(
2432
+ "emissive intensity",
2433
+ phong.emissiveIntensity,
2434
+ { min: 0 }
2435
+ );
2436
+ specularAnimatable = makeColorAnimatable("specular", phong.specular);
2437
+ materialAnimatables.push(
2438
+ shininessAnimatable,
2439
+ emissiveAnimatable,
2440
+ emissiveIntensityAnimatable,
2441
+ specularAnimatable
2442
+ );
2443
+ }
2444
+ let materialIds;
2276
2445
  if (colorName && colorLookup[colorName]) {
2277
- colorId = colorLookup[colorName][0];
2278
- opacityId = colorLookup[colorName][1];
2279
- useEmissive = colorLookup[colorName][2];
2446
+ materialIds = colorLookup[colorName];
2280
2447
  } else {
2281
- animatables = { ...animatables, [colorAnimatable.id]: colorAnimatable };
2282
- animatables = { ...animatables, [opacityAnimatable.id]: opacityAnimatable };
2448
+ materialIds = {
2449
+ color: colorAnimatable.id,
2450
+ opacity: opacityAnimatable.id,
2451
+ roughness: roughnessAnimatable?.id,
2452
+ metalness: metalnessAnimatable?.id,
2453
+ emissive: emissiveAnimatable?.id,
2454
+ emissiveIntensity: emissiveIntensityAnimatable?.id,
2455
+ shininess: shininessAnimatable?.id,
2456
+ specular: specularAnimatable?.id
2457
+ };
2458
+ materialAnimatables.forEach((animatable) => {
2459
+ animatables = { ...animatables, [animatable.id]: animatable };
2460
+ });
2283
2461
  if (colorName) {
2284
- newColorLookup[colorName] = [
2285
- colorAnimatable.id,
2286
- opacityAnimatable.id,
2287
- useEmissive
2288
- ];
2462
+ newColorLookup[colorName] = materialIds;
2289
2463
  }
2290
2464
  }
2291
2465
  const [geometryFeatures, geometryAnimatables, morphTargets] = importGeometry(
@@ -2304,13 +2478,22 @@ function importMesh(mesh, namespaces, colorLookup) {
2304
2478
  world = { ...world, ...newWorldItems };
2305
2479
  animatables = { ...animatables, ...newAnimatables };
2306
2480
  children.push(childId);
2481
+ } else if (shouldImportAsGroupChild(child)) {
2482
+ const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
2483
+ ...colorLookup,
2484
+ ...newColorLookup
2485
+ });
2486
+ newColorLookup = { ...newColorLookup, ...newMeshColors };
2487
+ world = { ...world, ...newWorldItems };
2488
+ animatables = { ...animatables, ...newAnimatables };
2489
+ children.push(childId);
2307
2490
  }
2308
2491
  });
2309
2492
  const newShape = {
2310
2493
  id: mesh.uuid,
2311
2494
  name: mesh.name,
2312
2495
  geometry: mesh.geometry,
2313
- material: getShapeMaterial(mesh, useEmissive),
2496
+ material: getShapeMaterial(mesh),
2314
2497
  type: "shape",
2315
2498
  tags: [],
2316
2499
  morphTargets,
@@ -2318,8 +2501,19 @@ function importMesh(mesh, namespaces, colorLookup) {
2318
2501
  translation: { animated: true, value: translationAnimatable.id },
2319
2502
  rotation: { animated: true, value: rotationAnimatable.id },
2320
2503
  scale: { animated: true, value: scaleAnimatable.id },
2321
- color: { animated: true, value: colorId },
2322
- opacity: { animated: true, value: opacityId },
2504
+ color: { animated: true, value: materialIds.color },
2505
+ opacity: { animated: true, value: materialIds.opacity },
2506
+ ...materialIds.roughness ? { roughness: { animated: true, value: materialIds.roughness } } : {},
2507
+ ...materialIds.metalness ? { metalness: { animated: true, value: materialIds.metalness } } : {},
2508
+ ...materialIds.emissive ? { emissive: { animated: true, value: materialIds.emissive } } : {},
2509
+ ...materialIds.emissiveIntensity ? {
2510
+ emissiveIntensity: {
2511
+ animated: true,
2512
+ value: materialIds.emissiveIntensity
2513
+ }
2514
+ } : {},
2515
+ ...materialIds.shininess ? { shininess: { animated: true, value: materialIds.shininess } } : {},
2516
+ ...materialIds.specular ? { specular: { animated: true, value: materialIds.specular } } : {},
2323
2517
  ...geometryFeatures
2324
2518
  },
2325
2519
  children: children.length > 0 ? children : void 0,
@@ -2328,11 +2522,8 @@ function importMesh(mesh, namespaces, colorLookup) {
2328
2522
  world = { ...world, [newShape.id]: newShape };
2329
2523
  return [world, animatables, newShape.id, newColorLookup];
2330
2524
  }
2331
- function getShapeMaterial(mesh, useEmissive) {
2525
+ function getShapeMaterial(mesh) {
2332
2526
  const material = mesh.material;
2333
- if (useEmissive) {
2334
- return "basic" /* Basic */;
2335
- }
2336
2527
  if (material.isMeshStandardMaterial) {
2337
2528
  return "standard" /* Standard */;
2338
2529
  } else if (material.isMeshPhongMaterial) {
@@ -2347,6 +2538,24 @@ function getShapeMaterial(mesh, useEmissive) {
2347
2538
  return "standard" /* Standard */;
2348
2539
  }
2349
2540
  }
2541
+ function shouldImportAsGroupChild(child) {
2542
+ if (!child.isObject3D) {
2543
+ return false;
2544
+ }
2545
+ if (child.isMesh) {
2546
+ return false;
2547
+ }
2548
+ if (child.isCamera) {
2549
+ return false;
2550
+ }
2551
+ if (child.isLight) {
2552
+ return false;
2553
+ }
2554
+ if (child.isBone) {
2555
+ return false;
2556
+ }
2557
+ return true;
2558
+ }
2350
2559
 
2351
2560
  // src/functions/gltf-loading/import-group.ts
2352
2561
  import_three4.Object3D.DEFAULT_UP.set(0, 0, 1);
@@ -2356,7 +2565,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2356
2565
  let newColorLookup = {};
2357
2566
  const children = [];
2358
2567
  const translationAnimatable = {
2359
- id: crypto.randomUUID(),
2568
+ id: (0, import_utils11.createBrowserSafeId)(),
2360
2569
  name: `${group.name ?? "Group"} translation`,
2361
2570
  type: "vector3",
2362
2571
  default: { x: group.position.x, y: group.position.y, z: group.position.z },
@@ -2372,7 +2581,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2372
2581
  [translationAnimatable.id]: translationAnimatable
2373
2582
  };
2374
2583
  const rotationAnimatable = {
2375
- id: crypto.randomUUID(),
2584
+ id: (0, import_utils11.createBrowserSafeId)(),
2376
2585
  name: `${group.name ?? "Group"} rotation`,
2377
2586
  type: "euler",
2378
2587
  default: { x: group.rotation.x, y: group.rotation.y, z: group.rotation.z },
@@ -2385,7 +2594,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2385
2594
  };
2386
2595
  animatables = { ...animatables, [rotationAnimatable.id]: rotationAnimatable };
2387
2596
  const scaleAnimatable = {
2388
- id: crypto.randomUUID(),
2597
+ id: (0, import_utils11.createBrowserSafeId)(),
2389
2598
  name: `${group.name ?? "Group"} scale`,
2390
2599
  type: "vector3",
2391
2600
  default: { x: group.scale.x, y: group.scale.y, z: group.scale.z },
@@ -2406,7 +2615,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2406
2615
  world = { ...world, ...newWorldItems };
2407
2616
  animatables = { ...animatables, ...newAnimatables };
2408
2617
  children.push(childId);
2409
- } else if (child.isGroup || child.isObject3D && child.children.length !== 0) {
2618
+ } else if (shouldImportAsGroupChild2(child)) {
2410
2619
  const [newWorldItems, newAnimatables, childId, newMeshColors] = importGroup(child, namespaces, {
2411
2620
  ...colorLookup,
2412
2621
  ...newColorLookup
@@ -2435,6 +2644,24 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2435
2644
  world = { ...world, [newGroup.id]: newGroup };
2436
2645
  return [world, animatables, newGroup.id, newColorLookup];
2437
2646
  }
2647
+ function shouldImportAsGroupChild2(child) {
2648
+ if (!child.isObject3D) {
2649
+ return false;
2650
+ }
2651
+ if (child.isMesh) {
2652
+ return false;
2653
+ }
2654
+ if (child.isCamera) {
2655
+ return false;
2656
+ }
2657
+ if (child.isLight) {
2658
+ return false;
2659
+ }
2660
+ if (child.isBone) {
2661
+ return false;
2662
+ }
2663
+ return true;
2664
+ }
2438
2665
 
2439
2666
  // src/functions/gltf-loading/import-scene.ts
2440
2667
  import_three5.Object3D.DEFAULT_UP.set(0, 0, 1);
@@ -2560,7 +2787,10 @@ var MATERIAL_NAME_FEATURE_KEYS = [
2560
2787
  "opacity",
2561
2788
  "roughness",
2562
2789
  "metalness",
2563
- "shininess"
2790
+ "shininess",
2791
+ "emissive",
2792
+ "emissiveIntensity",
2793
+ "specular"
2564
2794
  ];
2565
2795
  var MATERIAL_NAME_SUFFIXES2 = [
2566
2796
  " color",
@@ -2569,7 +2799,10 @@ var MATERIAL_NAME_SUFFIXES2 = [
2569
2799
  " opacity",
2570
2800
  " roughness",
2571
2801
  " metalness",
2572
- " shininess"
2802
+ " shininess",
2803
+ " emissive intensity",
2804
+ " emissive",
2805
+ " specular"
2573
2806
  ];
2574
2807
  function applyStoredRenderableNames(object, data) {
2575
2808
  if (typeof data.name === "string" && data.name.length > 0) {
@@ -2708,10 +2941,10 @@ function deriveRootBounds(group) {
2708
2941
  }
2709
2942
 
2710
2943
  // src/functions/vizij-bundle.ts
2711
- var import_utils7 = require("@vizij/utils");
2944
+ var import_utils12 = require("@vizij/utils");
2712
2945
  var BUNDLE_KEYS = ["VIZIJ_bundle"];
2713
2946
  function cloneBundle(value) {
2714
- return (0, import_utils7.cloneDeepSafe)(value);
2947
+ return (0, import_utils12.cloneDeepSafe)(value);
2715
2948
  }
2716
2949
  function readExtensionValue(extensionContainer) {
2717
2950
  for (const key of BUNDLE_KEYS) {
@@ -2745,6 +2978,13 @@ function searchParserJsonForBundle(parserJson) {
2745
2978
  if (!parserJson || typeof parserJson !== "object") {
2746
2979
  return null;
2747
2980
  }
2981
+ const rootExtensions = parserJson && typeof parserJson === "object" ? parserJson.extensions : null;
2982
+ if (rootExtensions && typeof rootExtensions === "object") {
2983
+ const match = readExtensionValue(rootExtensions);
2984
+ if (match) {
2985
+ return cloneBundle(match.value);
2986
+ }
2987
+ }
2748
2988
  const nodes = Array.isArray(parserJson.nodes) ? parserJson.nodes : [];
2749
2989
  for (const node of nodes) {
2750
2990
  const extensions = node && typeof node === "object" ? node.extensions : null;
@@ -2782,12 +3022,29 @@ function applyVizijBundle(object, bundle) {
2782
3022
  const userData = object.userData && typeof object.userData === "object" ? object.userData : {};
2783
3023
  const originalExtensions = userData.gltfExtensions;
2784
3024
  let applied = false;
3025
+ const staleCarriers = [];
2785
3026
  if (bundle) {
2786
3027
  userData.gltfExtensions = {
2787
3028
  ...originalExtensions ?? {},
2788
3029
  VIZIJ_bundle: bundle
2789
3030
  };
2790
3031
  object.userData = userData;
3032
+ object.traverse((node) => {
3033
+ if (node === object) {
3034
+ return;
3035
+ }
3036
+ const extensions = node.userData?.gltfExtensions;
3037
+ if (!extensions || !(BUNDLE_KEYS[0] in extensions)) {
3038
+ return;
3039
+ }
3040
+ staleCarriers.push({ node, extensions });
3041
+ const { [BUNDLE_KEYS[0]]: _stale, ...rest } = extensions;
3042
+ if (Object.keys(rest).length > 0) {
3043
+ node.userData.gltfExtensions = rest;
3044
+ } else {
3045
+ delete node.userData.gltfExtensions;
3046
+ }
3047
+ });
2791
3048
  applied = true;
2792
3049
  }
2793
3050
  return () => {
@@ -2804,12 +3061,16 @@ function applyVizijBundle(object, bundle) {
2804
3061
  delete object.userData;
2805
3062
  }
2806
3063
  }
3064
+ for (const { node, extensions } of staleCarriers) {
3065
+ node.userData.gltfExtensions = extensions;
3066
+ }
3067
+ staleCarriers.length = 0;
2807
3068
  applied = false;
2808
3069
  };
2809
3070
  }
2810
3071
 
2811
3072
  // src/functions/gltf-loading/extract-animations.ts
2812
- var import_utils8 = require("@vizij/utils");
3073
+ var import_utils13 = require("@vizij/utils");
2813
3074
  var CHANNEL_PATH_TO_TRACK_PROPERTY = {
2814
3075
  translation: "position",
2815
3076
  rotation: "quaternion",
@@ -2825,7 +3086,7 @@ function clonePlainObject(value) {
2825
3086
  if (!value) {
2826
3087
  return void 0;
2827
3088
  }
2828
- return (0, import_utils8.cloneDeepSafe)(value);
3089
+ return (0, import_utils13.cloneDeepSafe)(value);
2829
3090
  }
2830
3091
  function inferValueSize(valueType) {
2831
3092
  switch (valueType) {
@@ -3180,24 +3441,87 @@ function extractVizijAnimations(parserJson, clips) {
3180
3441
 
3181
3442
  // src/functions/load-gltf.ts
3182
3443
  THREE5.Object3D.DEFAULT_UP.set(0, 0, 1);
3444
+ var GLB_MAGIC = 1179937895;
3445
+ var GLB_VERSION = 2;
3446
+ var GLB_HEADER_BYTES = 12;
3447
+ var GLB_CHUNK_HEADER_BYTES = 8;
3448
+ var GLB_JSON_CHUNK_TYPE = 1313821514;
3183
3449
  var EmptyModelError = class extends Error {
3184
3450
  constructor(message) {
3185
3451
  super(message);
3186
3452
  this.name = "EmptyModelError";
3187
3453
  }
3188
3454
  };
3455
+ function parseGlbJsonChunk(buffer) {
3456
+ if (buffer.byteLength < GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES) {
3457
+ return void 0;
3458
+ }
3459
+ const view = new DataView(buffer);
3460
+ const magic = view.getUint32(0, true);
3461
+ const version = view.getUint32(4, true);
3462
+ if (magic !== GLB_MAGIC || version !== GLB_VERSION) {
3463
+ return void 0;
3464
+ }
3465
+ const chunkLength = view.getUint32(GLB_HEADER_BYTES, true);
3466
+ const chunkType = view.getUint32(GLB_HEADER_BYTES + 4, true);
3467
+ if (chunkType !== GLB_JSON_CHUNK_TYPE) {
3468
+ return void 0;
3469
+ }
3470
+ const chunkStart = GLB_HEADER_BYTES + GLB_CHUNK_HEADER_BYTES;
3471
+ const chunkEnd = chunkStart + chunkLength;
3472
+ if (chunkEnd > buffer.byteLength) {
3473
+ return void 0;
3474
+ }
3475
+ try {
3476
+ const chunkBytes = new Uint8Array(buffer, chunkStart, chunkLength);
3477
+ const jsonText = new TextDecoder().decode(chunkBytes);
3478
+ return JSON.parse(jsonText);
3479
+ } catch {
3480
+ return void 0;
3481
+ }
3482
+ }
3483
+ async function resolveParserJson(parserJson, fallback) {
3484
+ if (parserJson && typeof parserJson === "object") {
3485
+ return parserJson;
3486
+ }
3487
+ if (fallback.arrayBuffer) {
3488
+ const fromArrayBuffer = parseGlbJsonChunk(fallback.arrayBuffer);
3489
+ if (fromArrayBuffer && typeof fromArrayBuffer === "object") {
3490
+ return fromArrayBuffer;
3491
+ }
3492
+ }
3493
+ if (fallback.blob) {
3494
+ try {
3495
+ const blobBuffer = typeof fallback.blob.arrayBuffer === "function" ? await fallback.blob.arrayBuffer() : await new Response(fallback.blob).arrayBuffer();
3496
+ const fromBlob = parseGlbJsonChunk(blobBuffer);
3497
+ if (fromBlob && typeof fromBlob === "object") {
3498
+ return fromBlob;
3499
+ }
3500
+ } catch {
3501
+ }
3502
+ }
3503
+ if (fallback.url && typeof fetch === "function") {
3504
+ try {
3505
+ const response = await fetch(fallback.url);
3506
+ if (response.ok) {
3507
+ const binary = await response.arrayBuffer();
3508
+ const fromUrl = parseGlbJsonChunk(binary);
3509
+ if (fromUrl && typeof fromUrl === "object") {
3510
+ return fromUrl;
3511
+ }
3512
+ }
3513
+ } catch {
3514
+ }
3515
+ }
3516
+ return parserJson;
3517
+ }
3189
3518
  async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
3190
- const modelLoader = new import_three_stdlib.GLTFLoader();
3191
- modelLoader.setDRACOLoader(new import_three_stdlib.DRACOLoader());
3192
- const modelData = await modelLoader.loadAsync(url);
3193
- const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3194
- const asset = parseScene(
3195
- modelData.scene,
3196
- actualizedNamespaces,
3519
+ const asset = await loadGLTFWithBundleInternal(
3520
+ url,
3521
+ namespaces,
3197
3522
  aggressiveImport,
3198
3523
  rootBounds,
3199
- modelData?.parser?.json,
3200
- modelData.animations
3524
+ "url"
3201
3525
  );
3202
3526
  return [asset.world, asset.animatables, asset.animations];
3203
3527
  }
@@ -3206,11 +3530,13 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
3206
3530
  if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
3207
3531
  const objectUrl = URL.createObjectURL(blob);
3208
3532
  try {
3209
- const asset = await loadGLTFWithBundle(
3533
+ const asset = await loadGLTFWithBundleInternal(
3210
3534
  objectUrl,
3211
3535
  actualizedNamespaces,
3212
3536
  aggressiveImport,
3213
- rootBounds
3537
+ rootBounds,
3538
+ "blob",
3539
+ { blob }
3214
3540
  );
3215
3541
  return [asset.world, asset.animatables, asset.animations];
3216
3542
  } finally {
@@ -3219,19 +3545,22 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
3219
3545
  }
3220
3546
  const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
3221
3547
  return new Promise((resolve, reject) => {
3222
- const loader = new import_three_stdlib.GLTFLoader();
3223
- loader.setDRACOLoader(new import_three_stdlib.DRACOLoader());
3548
+ const loader = createTrackedGLTFLoader();
3224
3549
  loader.parse(
3225
3550
  arrayBuffer,
3226
3551
  "",
3227
- (gltf) => {
3552
+ async (gltf) => {
3228
3553
  try {
3554
+ const parserJson = await resolveParserJson(
3555
+ gltf?.parser?.json,
3556
+ { arrayBuffer }
3557
+ );
3229
3558
  const asset = parseScene(
3230
3559
  gltf.scene,
3231
3560
  actualizedNamespaces,
3232
3561
  aggressiveImport,
3233
3562
  rootBounds,
3234
- gltf?.parser?.json,
3563
+ parserJson,
3235
3564
  gltf.animations
3236
3565
  );
3237
3566
  resolve([asset.world, asset.animatables, asset.animations]);
@@ -3258,32 +3587,59 @@ function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson,
3258
3587
  );
3259
3588
  const bundle = extractVizijBundle(scene, parserJson);
3260
3589
  const animations = extractVizijAnimations(parserJson, clips);
3261
- return { world, animatables, bundle, animations };
3590
+ return { world, animatables, bundle, animations, scene };
3262
3591
  }
3263
- async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds) {
3592
+ function createTrackedGLTFLoader() {
3593
+ recordRenderCounter("gltfLoadCount");
3594
+ recordRenderCounter("dracoLoaderCreationCount");
3264
3595
  const modelLoader = new import_three_stdlib.GLTFLoader();
3265
3596
  modelLoader.setDRACOLoader(new import_three_stdlib.DRACOLoader());
3597
+ return modelLoader;
3598
+ }
3599
+ async function loadGLTFWithBundleInternal(url, namespaces, aggressiveImport = false, rootBounds, sourceKind = "url", parserJsonFallback) {
3600
+ if (sourceKind === "url") {
3601
+ recordRenderCounter("gltfUrlLoadCount");
3602
+ } else {
3603
+ recordRenderCounter("gltfBlobLoadCount");
3604
+ }
3605
+ const modelLoader = createTrackedGLTFLoader();
3266
3606
  const modelData = await modelLoader.loadAsync(url);
3607
+ const parserJson = await resolveParserJson(modelData?.parser?.json, {
3608
+ url,
3609
+ ...parserJsonFallback
3610
+ });
3267
3611
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3268
3612
  return parseScene(
3269
3613
  modelData.scene,
3270
3614
  actualizedNamespaces,
3271
3615
  aggressiveImport,
3272
3616
  rootBounds,
3273
- modelData?.parser?.json,
3617
+ parserJson,
3274
3618
  modelData.animations
3275
3619
  );
3276
3620
  }
3621
+ async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
3622
+ return loadGLTFWithBundleInternal(
3623
+ url,
3624
+ namespaces,
3625
+ aggressiveImport,
3626
+ rootBounds,
3627
+ "url",
3628
+ parserJsonFallback
3629
+ );
3630
+ }
3277
3631
  async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = false, rootBounds) {
3278
3632
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3279
3633
  if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
3280
3634
  const objectUrl = URL.createObjectURL(blob);
3281
3635
  try {
3282
- return await loadGLTFWithBundle(
3636
+ return await loadGLTFWithBundleInternal(
3283
3637
  objectUrl,
3284
3638
  actualizedNamespaces,
3285
3639
  aggressiveImport,
3286
- rootBounds
3640
+ rootBounds,
3641
+ "blob",
3642
+ { blob }
3287
3643
  );
3288
3644
  } finally {
3289
3645
  URL.revokeObjectURL(objectUrl);
@@ -3291,19 +3647,22 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
3291
3647
  }
3292
3648
  const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
3293
3649
  return new Promise((resolve, reject) => {
3294
- const loader = new import_three_stdlib.GLTFLoader();
3295
- loader.setDRACOLoader(new import_three_stdlib.DRACOLoader());
3650
+ const loader = createTrackedGLTFLoader();
3296
3651
  loader.parse(
3297
3652
  arrayBuffer,
3298
3653
  "",
3299
- (gltf) => {
3654
+ async (gltf) => {
3300
3655
  try {
3656
+ const parserJson = await resolveParserJson(
3657
+ gltf?.parser?.json,
3658
+ { arrayBuffer }
3659
+ );
3301
3660
  const asset = parseScene(
3302
3661
  gltf.scene,
3303
3662
  actualizedNamespaces,
3304
3663
  aggressiveImport,
3305
3664
  rootBounds,
3306
- gltf?.parser?.json,
3665
+ parserJson,
3307
3666
  gltf.animations
3308
3667
  );
3309
3668
  resolve(asset);
@@ -3325,6 +3684,9 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
3325
3684
  // src/functions/load-gltf-blob.ts
3326
3685
  var import_three_stdlib2 = require("three-stdlib");
3327
3686
  var loadGltfFromBlob = (blob, namespaces) => {
3687
+ recordRenderCounter("gltfLoadCount");
3688
+ recordRenderCounter("gltfBlobLoadCount");
3689
+ recordRenderCounter("dracoLoaderCreationCount");
3328
3690
  return new Promise((resolve, reject) => {
3329
3691
  const loader = new import_three_stdlib2.GLTFLoader();
3330
3692
  loader.setDRACOLoader(new import_three_stdlib2.DRACOLoader());
@@ -3355,6 +3717,160 @@ var loadGltfFromBlob = (blob, namespaces) => {
3355
3717
  var import_three_stdlib3 = require("three-stdlib");
3356
3718
  var THREE6 = __toESM(require("three"));
3357
3719
  THREE6.Object3D.DEFAULT_UP.set(0, 0, 1);
3720
+ function normalizeExportError(error) {
3721
+ if (error instanceof Error) {
3722
+ return error;
3723
+ }
3724
+ if (typeof error === "string") {
3725
+ return new Error(error);
3726
+ }
3727
+ return new Error("Failed to export scene.");
3728
+ }
3729
+ function triggerBlobDownload(blob, filename) {
3730
+ const url = URL.createObjectURL(blob);
3731
+ const link = document.createElement("a");
3732
+ link.href = url;
3733
+ link.download = filename;
3734
+ link.style.display = "none";
3735
+ document.body.appendChild(link);
3736
+ link.click();
3737
+ document.body.removeChild(link);
3738
+ setTimeout(() => URL.revokeObjectURL(url), 0);
3739
+ }
3740
+ var GLB_MAGIC2 = 1179937895;
3741
+ var GLB_VERSION2 = 2;
3742
+ var GLB_JSON_CHUNK_TYPE2 = 1313821514;
3743
+ var GLB_HEADER_BYTES2 = 12;
3744
+ var GLB_CHUNK_HEADER_BYTES2 = 8;
3745
+ var GLB_JSON_PADDING_BYTE = 32;
3746
+ function isNearlyEqual(a, b, epsilon = 1e-6) {
3747
+ return Math.abs(a - b) <= epsilon;
3748
+ }
3749
+ function isIdentityTransformNode(node) {
3750
+ const translation = node.translation;
3751
+ if (Array.isArray(translation) && (translation.length !== 3 || !isNearlyEqual(Number(translation[0]), 0) || !isNearlyEqual(Number(translation[1]), 0) || !isNearlyEqual(Number(translation[2]), 0))) {
3752
+ return false;
3753
+ }
3754
+ const rotation = node.rotation;
3755
+ 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))) {
3756
+ return false;
3757
+ }
3758
+ const scale = node.scale;
3759
+ if (Array.isArray(scale) && (scale.length !== 3 || !isNearlyEqual(Number(scale[0]), 1) || !isNearlyEqual(Number(scale[1]), 1) || !isNearlyEqual(Number(scale[2]), 1))) {
3760
+ return false;
3761
+ }
3762
+ return true;
3763
+ }
3764
+ function isPassThroughWrapperNode(node) {
3765
+ if (!node || typeof node !== "object") {
3766
+ return false;
3767
+ }
3768
+ if (!Array.isArray(node.children) || node.children.length === 0) {
3769
+ return false;
3770
+ }
3771
+ const hasOnlyNumericChildren = node.children.every(
3772
+ (index) => Number.isInteger(index)
3773
+ );
3774
+ if (!hasOnlyNumericChildren) {
3775
+ return false;
3776
+ }
3777
+ if (node.mesh !== void 0 || node.camera !== void 0 || node.skin !== void 0) {
3778
+ return false;
3779
+ }
3780
+ if (node.extensions && typeof node.extensions === "object" && Object.keys(node.extensions).length > 0) {
3781
+ return false;
3782
+ }
3783
+ return isIdentityTransformNode(node);
3784
+ }
3785
+ function normalizeExportedSceneJson(json, fallbackSceneName) {
3786
+ if (!json || typeof json !== "object") {
3787
+ return false;
3788
+ }
3789
+ if (!Array.isArray(json.scenes) || !Array.isArray(json.nodes)) {
3790
+ return false;
3791
+ }
3792
+ const sceneIndexRaw = json.scene;
3793
+ const sceneIndex = typeof sceneIndexRaw === "number" && Number.isInteger(sceneIndexRaw) ? sceneIndexRaw : 0;
3794
+ const sceneDef = json.scenes[sceneIndex];
3795
+ if (!sceneDef || typeof sceneDef !== "object") {
3796
+ return false;
3797
+ }
3798
+ let changed = false;
3799
+ let wrapperNodeName;
3800
+ if (Array.isArray(sceneDef.nodes) && sceneDef.nodes.length === 1) {
3801
+ const wrapperIndex = sceneDef.nodes[0];
3802
+ if (Number.isInteger(wrapperIndex)) {
3803
+ const wrapperNode = json.nodes[wrapperIndex];
3804
+ if (isPassThroughWrapperNode(wrapperNode)) {
3805
+ sceneDef.nodes = [...wrapperNode.children];
3806
+ wrapperNodeName = typeof wrapperNode.name === "string" ? wrapperNode.name : void 0;
3807
+ changed = true;
3808
+ }
3809
+ }
3810
+ }
3811
+ const currentSceneName = typeof sceneDef.name === "string" ? sceneDef.name.trim() : "";
3812
+ if (currentSceneName === "AuxScene") {
3813
+ const nextSceneName = (wrapperNodeName?.trim() || fallbackSceneName?.trim() || "Scene").trim();
3814
+ if (nextSceneName.length > 0 && nextSceneName !== currentSceneName) {
3815
+ sceneDef.name = nextSceneName;
3816
+ changed = true;
3817
+ }
3818
+ }
3819
+ return changed;
3820
+ }
3821
+ function sanitizeExportedGlb(buffer, fallbackSceneName) {
3822
+ if (buffer.byteLength < GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2) {
3823
+ return buffer;
3824
+ }
3825
+ const originalBytes = new Uint8Array(buffer);
3826
+ const view = new DataView(buffer);
3827
+ const magic = view.getUint32(0, true);
3828
+ const version = view.getUint32(4, true);
3829
+ if (magic !== GLB_MAGIC2 || version !== GLB_VERSION2) {
3830
+ return buffer;
3831
+ }
3832
+ const jsonChunkLength = view.getUint32(GLB_HEADER_BYTES2, true);
3833
+ const jsonChunkType = view.getUint32(GLB_HEADER_BYTES2 + 4, true);
3834
+ if (jsonChunkType !== GLB_JSON_CHUNK_TYPE2) {
3835
+ return buffer;
3836
+ }
3837
+ const jsonChunkStart = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2;
3838
+ const jsonChunkEnd = jsonChunkStart + jsonChunkLength;
3839
+ if (jsonChunkEnd > originalBytes.length) {
3840
+ return buffer;
3841
+ }
3842
+ let jsonPayload;
3843
+ try {
3844
+ const jsonText = new TextDecoder().decode(
3845
+ originalBytes.slice(jsonChunkStart, jsonChunkEnd)
3846
+ );
3847
+ jsonPayload = JSON.parse(jsonText);
3848
+ } catch {
3849
+ return buffer;
3850
+ }
3851
+ const changed = normalizeExportedSceneJson(jsonPayload, fallbackSceneName);
3852
+ if (!changed) {
3853
+ return buffer;
3854
+ }
3855
+ const encodedJson = new TextEncoder().encode(JSON.stringify(jsonPayload));
3856
+ const paddedJsonLength = encodedJson.length + 3 & ~3;
3857
+ const paddedJson = new Uint8Array(paddedJsonLength);
3858
+ paddedJson.fill(GLB_JSON_PADDING_BYTE);
3859
+ paddedJson.set(encodedJson);
3860
+ const remainingChunks = originalBytes.slice(jsonChunkEnd);
3861
+ const totalLength = GLB_HEADER_BYTES2 + GLB_CHUNK_HEADER_BYTES2 + paddedJsonLength + remainingChunks.length;
3862
+ const sanitized = new ArrayBuffer(totalLength);
3863
+ const sanitizedBytes = new Uint8Array(sanitized);
3864
+ const sanitizedView = new DataView(sanitized);
3865
+ sanitizedView.setUint32(0, GLB_MAGIC2, true);
3866
+ sanitizedView.setUint32(4, GLB_VERSION2, true);
3867
+ sanitizedView.setUint32(8, totalLength, true);
3868
+ sanitizedView.setUint32(GLB_HEADER_BYTES2, paddedJsonLength, true);
3869
+ sanitizedView.setUint32(GLB_HEADER_BYTES2 + 4, GLB_JSON_CHUNK_TYPE2, true);
3870
+ sanitizedBytes.set(paddedJson, jsonChunkStart);
3871
+ sanitizedBytes.set(remainingChunks, jsonChunkStart + paddedJsonLength);
3872
+ return sanitized;
3873
+ }
3358
3874
  function exportScene(data, fileNameOrOptions = "scene.glb") {
3359
3875
  const options = typeof fileNameOrOptions === "string" ? { fileName: fileNameOrOptions } : fileNameOrOptions ?? {};
3360
3876
  const fileName = options.fileName ?? "scene.glb";
@@ -3369,7 +3885,15 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
3369
3885
  }
3370
3886
  }
3371
3887
  }));
3372
- const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(data, options.bundle) : () => {
3888
+ const sourceRoot = data;
3889
+ const exportRoot = sourceRoot instanceof THREE6.Scene ? sourceRoot : sourceRoot.clone(true);
3890
+ const exportTarget = exportRoot instanceof THREE6.Scene ? exportRoot : (() => {
3891
+ const scene = new THREE6.Scene();
3892
+ scene.name = data.name?.trim() || "Scene";
3893
+ scene.add(exportRoot);
3894
+ return scene;
3895
+ })();
3896
+ const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(exportRoot, options.bundle) : () => {
3373
3897
  };
3374
3898
  const binary = options.binary ?? true;
3375
3899
  const exporterOptions = {
@@ -3383,33 +3907,40 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
3383
3907
  }
3384
3908
  try {
3385
3909
  exporter.parse(
3386
- data,
3910
+ exportTarget,
3387
3911
  (gltf) => {
3388
3912
  detachBundle();
3389
3913
  if (!(gltf instanceof ArrayBuffer)) {
3390
- throw new Error("Failed to export scene!");
3914
+ const error = new Error("Failed to export scene.");
3915
+ options.onError?.(error);
3916
+ return;
3391
3917
  }
3392
- const link = document.createElement("a");
3393
- link.href = URL.createObjectURL(
3394
- new Blob([gltf], {
3395
- type: "application/octet-stream"
3396
- })
3918
+ const sanitizedGltf = sanitizeExportedGlb(
3919
+ gltf,
3920
+ data.name?.trim() || void 0
3397
3921
  );
3398
3922
  const trimmed = fileName.trim();
3399
3923
  const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
3400
3924
  const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
3401
- link.download = downloadName;
3402
- link.click();
3403
- URL.revokeObjectURL(link.href);
3925
+ triggerBlobDownload(
3926
+ new Blob([sanitizedGltf], {
3927
+ type: "application/octet-stream"
3928
+ }),
3929
+ downloadName
3930
+ );
3931
+ options.onComplete?.();
3404
3932
  },
3405
- () => {
3933
+ (error) => {
3406
3934
  detachBundle();
3935
+ options.onError?.(normalizeExportError(error));
3407
3936
  },
3408
3937
  exporterOptions
3409
3938
  );
3410
3939
  } catch (error) {
3411
3940
  detachBundle();
3412
- throw error;
3941
+ const normalizedError = normalizeExportError(error);
3942
+ options.onError?.(normalizedError);
3943
+ throw normalizedError;
3413
3944
  }
3414
3945
  }
3415
3946
  // Annotate the CommonJS export names for ESM import in node:
@@ -3429,6 +3960,7 @@ function exportScene(data, fileNameOrOptions = "scene.glb") {
3429
3960
  loadGLTFFromBlobWithBundle,
3430
3961
  loadGLTFWithBundle,
3431
3962
  loadGltfFromBlob,
3963
+ parseGlbJsonChunk,
3432
3964
  useDefaultVizijStore,
3433
3965
  useFeatures,
3434
3966
  useVizijStore,