@vizij/render 0.1.1 → 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.mjs CHANGED
@@ -974,6 +974,48 @@ function InnerRenderedShape({
974
974
  );
975
975
  }
976
976
  },
977
+ roughness: (value) => {
978
+ const standard = material.current;
979
+ if (standard?.roughness !== void 0 && instanceOfRawNumber4(value)) {
980
+ standard.roughness = value;
981
+ standard.needsUpdate = true;
982
+ }
983
+ },
984
+ metalness: (value) => {
985
+ const standard = material.current;
986
+ if (standard?.metalness !== void 0 && instanceOfRawNumber4(value)) {
987
+ standard.metalness = value;
988
+ standard.needsUpdate = true;
989
+ }
990
+ },
991
+ shininess: (value) => {
992
+ const phong = material.current;
993
+ if (phong?.shininess !== void 0 && instanceOfRawNumber4(value)) {
994
+ phong.shininess = value;
995
+ phong.needsUpdate = true;
996
+ }
997
+ },
998
+ emissive: (value) => {
999
+ const standard = material.current;
1000
+ if (standard?.emissive && instanceOfRawRGB3(value)) {
1001
+ standard.emissive.setRGB(value.r, value.g, value.b);
1002
+ standard.needsUpdate = true;
1003
+ }
1004
+ },
1005
+ emissiveIntensity: (value) => {
1006
+ const standard = material.current;
1007
+ if (standard?.emissiveIntensity !== void 0 && instanceOfRawNumber4(value)) {
1008
+ standard.emissiveIntensity = value;
1009
+ standard.needsUpdate = true;
1010
+ }
1011
+ },
1012
+ specular: (value) => {
1013
+ const phong = material.current;
1014
+ if (phong?.specular && instanceOfRawRGB3(value)) {
1015
+ phong.specular.setRGB(value.r, value.g, value.b);
1016
+ phong.needsUpdate = true;
1017
+ }
1018
+ },
977
1019
  ...morphTargetSettings[2]
978
1020
  },
979
1021
  shape
@@ -1094,7 +1136,10 @@ var MATERIAL_FEATURE_KEYS = [
1094
1136
  "opacity",
1095
1137
  "roughness",
1096
1138
  "metalness",
1097
- "shininess"
1139
+ "shininess",
1140
+ "emissive",
1141
+ "emissiveIntensity",
1142
+ "specular"
1098
1143
  ];
1099
1144
  var MATERIAL_NAME_SUFFIXES = [
1100
1145
  " color",
@@ -1103,7 +1148,10 @@ var MATERIAL_NAME_SUFFIXES = [
1103
1148
  " opacity",
1104
1149
  " roughness",
1105
1150
  " metalness",
1106
- " shininess"
1151
+ " shininess",
1152
+ " emissive intensity",
1153
+ " emissive",
1154
+ " specular"
1107
1155
  ];
1108
1156
  function deriveMaterialName(shape, values) {
1109
1157
  for (const key of MATERIAL_FEATURE_KEYS) {
@@ -1217,12 +1265,13 @@ import { createRef } from "react";
1217
1265
  import { mapValues as mapValues2 } from "lodash";
1218
1266
 
1219
1267
  // src/functions/create-world-element.ts
1268
+ import { createBrowserSafeId } from "@vizij/utils";
1220
1269
  function createDefaultGroup(partialBody) {
1221
1270
  const translation = partialBody.features?.translation || { animated: false, value: { x: 0, y: 0 } };
1222
1271
  const rotation = partialBody.features?.rotation || { animated: false, value: { x: 0, y: 0, z: 0 } };
1223
1272
  const scale = partialBody.features?.scale || { animated: false, value: { x: 1, y: 1, z: 1 } };
1224
1273
  return {
1225
- id: partialBody.id || crypto.randomUUID(),
1274
+ id: partialBody.id || createBrowserSafeId(),
1226
1275
  name: partialBody.name || "new-body",
1227
1276
  type: partialBody.type || "group",
1228
1277
  tags: partialBody.tags || [],
@@ -1283,7 +1332,32 @@ function removeFromTree(state, nodesToRemove) {
1283
1332
  });
1284
1333
  }
1285
1334
 
1335
+ // src/memoryInvestigation.ts
1336
+ function getGlobalMemoryInvestigation() {
1337
+ return globalThis;
1338
+ }
1339
+ function updateRenderState(updater) {
1340
+ const globalObj = getGlobalMemoryInvestigation();
1341
+ if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
1342
+ return;
1343
+ }
1344
+ if (!globalObj.__vizijMemoryDebugState) {
1345
+ globalObj.__vizijMemoryDebugState = {};
1346
+ }
1347
+ if (!globalObj.__vizijMemoryDebugState.render) {
1348
+ globalObj.__vizijMemoryDebugState.render = {};
1349
+ }
1350
+ updater(globalObj.__vizijMemoryDebugState.render);
1351
+ }
1352
+ function recordRenderCounter(key, delta = 1) {
1353
+ updateRenderState((renderState) => {
1354
+ const current = typeof renderState[key] === "number" ? Number(renderState[key]) : 0;
1355
+ renderState[key] = current + delta;
1356
+ });
1357
+ }
1358
+
1286
1359
  // src/functions/create-animatable.ts
1360
+ import { createBrowserSafeId as createBrowserSafeId2 } from "@vizij/utils";
1287
1361
  function createAnimatable(value) {
1288
1362
  if (!value.type) {
1289
1363
  return null;
@@ -1293,7 +1367,7 @@ function createAnimatable(value) {
1293
1367
  }
1294
1368
  if (value.type === "euler") {
1295
1369
  const newAnimatable = {
1296
- id: value.id ?? crypto.randomUUID(),
1370
+ id: value.id ?? createBrowserSafeId2(),
1297
1371
  name: `${value.name} Rotation`,
1298
1372
  type: value.type,
1299
1373
  default: value.default ?? { x: 0, y: 0, z: 0 },
@@ -1310,7 +1384,7 @@ function createAnimatable(value) {
1310
1384
  return newAnimatable;
1311
1385
  } else if (value.type === "vector3") {
1312
1386
  const newAnimatable = {
1313
- id: value.id ?? crypto.randomUUID(),
1387
+ id: value.id ?? createBrowserSafeId2(),
1314
1388
  name: `${value.name} Vector3`,
1315
1389
  type: value.type,
1316
1390
  default: value.default ?? { x: 0, y: 0, z: 0 },
@@ -1327,7 +1401,7 @@ function createAnimatable(value) {
1327
1401
  return newAnimatable;
1328
1402
  } else if (value.type === "string") {
1329
1403
  const newAnimatable = {
1330
- id: value.id ?? crypto.randomUUID(),
1404
+ id: value.id ?? createBrowserSafeId2(),
1331
1405
  name: value.name,
1332
1406
  type: value.type,
1333
1407
  default: value.default ?? "Hello World",
@@ -1340,7 +1414,7 @@ function createAnimatable(value) {
1340
1414
  return newAnimatable;
1341
1415
  } else if (value.type === "number") {
1342
1416
  const newAnimatable = {
1343
- id: value.id ?? crypto.randomUUID(),
1417
+ id: value.id ?? createBrowserSafeId2(),
1344
1418
  name: value.name,
1345
1419
  type: value.type,
1346
1420
  default: value.default ?? 0,
@@ -1353,7 +1427,7 @@ function createAnimatable(value) {
1353
1427
  return newAnimatable;
1354
1428
  } else if (value.type === "boolean") {
1355
1429
  const newAnimatable = {
1356
- id: value.id ?? crypto.randomUUID(),
1430
+ id: value.id ?? createBrowserSafeId2(),
1357
1431
  name: value.name,
1358
1432
  type: value.type,
1359
1433
  default: value.default ?? false,
@@ -1366,7 +1440,7 @@ function createAnimatable(value) {
1366
1440
  return newAnimatable;
1367
1441
  } else if (value.type === "rgb") {
1368
1442
  const newAnimatable = {
1369
- id: value.id ?? crypto.randomUUID(),
1443
+ id: value.id ?? createBrowserSafeId2(),
1370
1444
  name: value.name,
1371
1445
  type: value.type,
1372
1446
  default: value.default ?? { r: 0, g: 0, b: 0 },
@@ -1383,7 +1457,7 @@ function createAnimatable(value) {
1383
1457
  return newAnimatable;
1384
1458
  } else if (value.type === "hsl") {
1385
1459
  const newAnimatable = {
1386
- id: value.id ?? crypto.randomUUID(),
1460
+ id: value.id ?? createBrowserSafeId2(),
1387
1461
  name: value.name,
1388
1462
  type: value.type,
1389
1463
  default: value.default ?? { h: 0, s: 0, l: 0 },
@@ -1715,6 +1789,7 @@ var VizijSlice = (set, get) => ({
1715
1789
  );
1716
1790
  },
1717
1791
  setVizij: (scene, animatables) => {
1792
+ recordRenderCounter("rootReplacementCount");
1718
1793
  set({
1719
1794
  world: scene,
1720
1795
  animatables
@@ -1722,6 +1797,7 @@ var VizijSlice = (set, get) => ({
1722
1797
  },
1723
1798
  addWorldElements(world, animatables, replace) {
1724
1799
  if (replace) {
1800
+ recordRenderCounter("rootReplacementCount");
1725
1801
  set({ world, animatables });
1726
1802
  } else {
1727
1803
  set((state) => ({
@@ -1774,7 +1850,7 @@ import {
1774
1850
  import { jsx as jsx6 } from "react/jsx-runtime";
1775
1851
  function SelectionGlowEffect({
1776
1852
  enabled = false,
1777
- color = "#ff1010ff",
1853
+ color = "#ff1010",
1778
1854
  opacity = 0.9,
1779
1855
  thresholdAngle = 2
1780
1856
  }) {
@@ -1880,6 +1956,13 @@ function Vizij({
1880
1956
  onPointerMissed
1881
1957
  }) {
1882
1958
  const ctx = useContext3(VizijContext);
1959
+ useEffect7(() => {
1960
+ recordRenderCounter("canvasMountCount");
1961
+ recordRenderCounter("mountedCanvasCount");
1962
+ return () => {
1963
+ recordRenderCounter("mountedCanvasCount", -1);
1964
+ };
1965
+ }, []);
1883
1966
  if (ctx) {
1884
1967
  return /* @__PURE__ */ jsx7(
1885
1968
  Canvas,
@@ -1948,7 +2031,7 @@ function InnerVizij({
1948
2031
  makeDefault: true,
1949
2032
  position: [0, 0, 100],
1950
2033
  near: 0.1,
1951
- far: 101
2034
+ far: 1e3
1952
2035
  }
1953
2036
  ),
1954
2037
  /* @__PURE__ */ jsx7(Suspense, { fallback: null, children: /* @__PURE__ */ jsx7(
@@ -2126,6 +2209,7 @@ import { Object3D as Object3D7 } from "three";
2126
2209
 
2127
2210
  // src/functions/gltf-loading/import-group.ts
2128
2211
  import { Object3D as Object3D6 } from "three";
2212
+ import { createBrowserSafeId as createBrowserSafeId5 } from "@vizij/utils";
2129
2213
 
2130
2214
  // src/functions/util.ts
2131
2215
  import { createRef as createRef2 } from "react";
@@ -2135,8 +2219,12 @@ function namespaceArrayToRefs(namespaces) {
2135
2219
 
2136
2220
  // src/functions/gltf-loading/import-mesh.ts
2137
2221
  import { Object3D as Object3D5 } from "three";
2222
+ import { createBrowserSafeId as createBrowserSafeId4 } from "@vizij/utils";
2138
2223
 
2139
2224
  // src/functions/gltf-loading/import-geometry.ts
2225
+ import {
2226
+ createBrowserSafeId as createBrowserSafeId3
2227
+ } from "@vizij/utils";
2140
2228
  function sanitizeMorphKey(name, fallbackIndex, used) {
2141
2229
  const baseName = name && name.trim().length > 0 ? name.trim() : `morph_${fallbackIndex + 1}`;
2142
2230
  const slug = baseName.toLowerCase().replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "");
@@ -2160,7 +2248,7 @@ function importGeometry(geometry, mesh) {
2160
2248
  const usedKeys = /* @__PURE__ */ new Set();
2161
2249
  Object.entries(mesh.morphTargetDictionary ?? {}).forEach(
2162
2250
  ([name, index]) => {
2163
- const morphId = crypto.randomUUID();
2251
+ const morphId = createBrowserSafeId3();
2164
2252
  const featureKey = sanitizeMorphKey(name, index, usedKeys);
2165
2253
  morphIds.push(featureKey);
2166
2254
  features[featureKey] = {
@@ -2196,7 +2284,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2196
2284
  let animatables = {};
2197
2285
  let newColorLookup = {};
2198
2286
  const translationAnimatable = {
2199
- id: crypto.randomUUID(),
2287
+ id: createBrowserSafeId4(),
2200
2288
  name: `${mesh.name ?? "Mesh"} translation`,
2201
2289
  type: "vector3",
2202
2290
  default: { x: mesh.position.x, y: mesh.position.y, z: mesh.position.z },
@@ -2212,7 +2300,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2212
2300
  [translationAnimatable.id]: translationAnimatable
2213
2301
  };
2214
2302
  const rotationAnimatable = {
2215
- id: crypto.randomUUID(),
2303
+ id: createBrowserSafeId4(),
2216
2304
  name: `${mesh.name ?? "Mesh"} rotation`,
2217
2305
  type: "euler",
2218
2306
  default: { x: mesh.rotation.x, y: mesh.rotation.y, z: mesh.rotation.z },
@@ -2225,7 +2313,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2225
2313
  };
2226
2314
  animatables = { ...animatables, [rotationAnimatable.id]: rotationAnimatable };
2227
2315
  const scaleAnimatable = {
2228
- id: crypto.randomUUID(),
2316
+ id: createBrowserSafeId4(),
2229
2317
  name: `${mesh.name ?? "Mesh"} scale`,
2230
2318
  type: "vector3",
2231
2319
  default: { x: mesh.scale.x, y: mesh.scale.y, z: mesh.scale.z },
@@ -2236,56 +2324,120 @@ function importMesh(mesh, namespaces, colorLookup) {
2236
2324
  }
2237
2325
  };
2238
2326
  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
- },
2327
+ const material = mesh.material;
2328
+ const color = material.color;
2329
+ const colorName = material.name;
2330
+ const label = (suffix) => colorName ? `${colorName} ${suffix}` : `${mesh.name ?? "Mesh"} ${suffix}`;
2331
+ const makeNumberAnimatable = (suffix, value, constraints) => ({
2332
+ id: createBrowserSafeId4(),
2333
+ name: label(suffix),
2334
+ type: "number",
2335
+ default: value,
2336
+ constraints,
2255
2337
  pub: {
2256
2338
  public: true,
2257
- output: mesh.material.name ? `${mesh.material.name} color` : `${mesh.name ?? "Mesh"} color`
2339
+ output: label(suffix)
2258
2340
  }
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,
2341
+ });
2342
+ const makeColorAnimatable = (suffix, value) => ({
2343
+ id: createBrowserSafeId4(),
2344
+ name: label(suffix),
2345
+ type: "rgb",
2346
+ default: { r: value.r, g: value.g, b: value.b },
2265
2347
  constraints: {
2266
- min: 0,
2267
- max: 1
2348
+ min: [0, 0, 0],
2349
+ max: [1, 1, 1]
2268
2350
  },
2269
2351
  pub: {
2270
2352
  public: true,
2271
- output: mesh.material.name ? `${mesh.material.name} opacity` : `${mesh.name ?? "Mesh"} opacity`
2353
+ output: label(suffix)
2272
2354
  }
2355
+ });
2356
+ const colorAnimatable = {
2357
+ ...makeColorAnimatable("color", color),
2358
+ name: material.name ?? `${mesh.name ?? "Mesh"} color`
2273
2359
  };
2274
- let colorId = colorAnimatable.id;
2275
- let opacityId = opacityAnimatable.id;
2360
+ const opacityAnimatable = makeNumberAnimatable("opacity", material.opacity, {
2361
+ min: 0,
2362
+ max: 1
2363
+ });
2364
+ const materialAnimatables = [
2365
+ colorAnimatable,
2366
+ opacityAnimatable
2367
+ ];
2368
+ let roughnessAnimatable;
2369
+ let metalnessAnimatable;
2370
+ let emissiveAnimatable;
2371
+ let emissiveIntensityAnimatable;
2372
+ let shininessAnimatable;
2373
+ let specularAnimatable;
2374
+ if (material.isMeshStandardMaterial) {
2375
+ roughnessAnimatable = makeNumberAnimatable(
2376
+ "roughness",
2377
+ material.roughness,
2378
+ {
2379
+ min: 0,
2380
+ max: 1
2381
+ }
2382
+ );
2383
+ metalnessAnimatable = makeNumberAnimatable(
2384
+ "metalness",
2385
+ material.metalness,
2386
+ {
2387
+ min: 0,
2388
+ max: 1
2389
+ }
2390
+ );
2391
+ emissiveAnimatable = makeColorAnimatable("emissive", material.emissive);
2392
+ emissiveIntensityAnimatable = makeNumberAnimatable(
2393
+ "emissive intensity",
2394
+ material.emissiveIntensity,
2395
+ { min: 0 }
2396
+ );
2397
+ materialAnimatables.push(
2398
+ roughnessAnimatable,
2399
+ metalnessAnimatable,
2400
+ emissiveAnimatable,
2401
+ emissiveIntensityAnimatable
2402
+ );
2403
+ } else if (mesh.material.isMeshPhongMaterial) {
2404
+ const phong = mesh.material;
2405
+ shininessAnimatable = makeNumberAnimatable("shininess", phong.shininess, {
2406
+ min: 0
2407
+ });
2408
+ emissiveAnimatable = makeColorAnimatable("emissive", phong.emissive);
2409
+ emissiveIntensityAnimatable = makeNumberAnimatable(
2410
+ "emissive intensity",
2411
+ phong.emissiveIntensity,
2412
+ { min: 0 }
2413
+ );
2414
+ specularAnimatable = makeColorAnimatable("specular", phong.specular);
2415
+ materialAnimatables.push(
2416
+ shininessAnimatable,
2417
+ emissiveAnimatable,
2418
+ emissiveIntensityAnimatable,
2419
+ specularAnimatable
2420
+ );
2421
+ }
2422
+ let materialIds;
2276
2423
  if (colorName && colorLookup[colorName]) {
2277
- colorId = colorLookup[colorName][0];
2278
- opacityId = colorLookup[colorName][1];
2279
- useEmissive = colorLookup[colorName][2];
2424
+ materialIds = colorLookup[colorName];
2280
2425
  } else {
2281
- animatables = { ...animatables, [colorAnimatable.id]: colorAnimatable };
2282
- animatables = { ...animatables, [opacityAnimatable.id]: opacityAnimatable };
2426
+ materialIds = {
2427
+ color: colorAnimatable.id,
2428
+ opacity: opacityAnimatable.id,
2429
+ roughness: roughnessAnimatable?.id,
2430
+ metalness: metalnessAnimatable?.id,
2431
+ emissive: emissiveAnimatable?.id,
2432
+ emissiveIntensity: emissiveIntensityAnimatable?.id,
2433
+ shininess: shininessAnimatable?.id,
2434
+ specular: specularAnimatable?.id
2435
+ };
2436
+ materialAnimatables.forEach((animatable) => {
2437
+ animatables = { ...animatables, [animatable.id]: animatable };
2438
+ });
2283
2439
  if (colorName) {
2284
- newColorLookup[colorName] = [
2285
- colorAnimatable.id,
2286
- opacityAnimatable.id,
2287
- useEmissive
2288
- ];
2440
+ newColorLookup[colorName] = materialIds;
2289
2441
  }
2290
2442
  }
2291
2443
  const [geometryFeatures, geometryAnimatables, morphTargets] = importGeometry(
@@ -2319,7 +2471,7 @@ function importMesh(mesh, namespaces, colorLookup) {
2319
2471
  id: mesh.uuid,
2320
2472
  name: mesh.name,
2321
2473
  geometry: mesh.geometry,
2322
- material: getShapeMaterial(mesh, useEmissive),
2474
+ material: getShapeMaterial(mesh),
2323
2475
  type: "shape",
2324
2476
  tags: [],
2325
2477
  morphTargets,
@@ -2327,8 +2479,19 @@ function importMesh(mesh, namespaces, colorLookup) {
2327
2479
  translation: { animated: true, value: translationAnimatable.id },
2328
2480
  rotation: { animated: true, value: rotationAnimatable.id },
2329
2481
  scale: { animated: true, value: scaleAnimatable.id },
2330
- color: { animated: true, value: colorId },
2331
- opacity: { animated: true, value: opacityId },
2482
+ color: { animated: true, value: materialIds.color },
2483
+ opacity: { animated: true, value: materialIds.opacity },
2484
+ ...materialIds.roughness ? { roughness: { animated: true, value: materialIds.roughness } } : {},
2485
+ ...materialIds.metalness ? { metalness: { animated: true, value: materialIds.metalness } } : {},
2486
+ ...materialIds.emissive ? { emissive: { animated: true, value: materialIds.emissive } } : {},
2487
+ ...materialIds.emissiveIntensity ? {
2488
+ emissiveIntensity: {
2489
+ animated: true,
2490
+ value: materialIds.emissiveIntensity
2491
+ }
2492
+ } : {},
2493
+ ...materialIds.shininess ? { shininess: { animated: true, value: materialIds.shininess } } : {},
2494
+ ...materialIds.specular ? { specular: { animated: true, value: materialIds.specular } } : {},
2332
2495
  ...geometryFeatures
2333
2496
  },
2334
2497
  children: children.length > 0 ? children : void 0,
@@ -2337,11 +2500,8 @@ function importMesh(mesh, namespaces, colorLookup) {
2337
2500
  world = { ...world, [newShape.id]: newShape };
2338
2501
  return [world, animatables, newShape.id, newColorLookup];
2339
2502
  }
2340
- function getShapeMaterial(mesh, useEmissive) {
2503
+ function getShapeMaterial(mesh) {
2341
2504
  const material = mesh.material;
2342
- if (useEmissive) {
2343
- return "basic" /* Basic */;
2344
- }
2345
2505
  if (material.isMeshStandardMaterial) {
2346
2506
  return "standard" /* Standard */;
2347
2507
  } else if (material.isMeshPhongMaterial) {
@@ -2383,7 +2543,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2383
2543
  let newColorLookup = {};
2384
2544
  const children = [];
2385
2545
  const translationAnimatable = {
2386
- id: crypto.randomUUID(),
2546
+ id: createBrowserSafeId5(),
2387
2547
  name: `${group.name ?? "Group"} translation`,
2388
2548
  type: "vector3",
2389
2549
  default: { x: group.position.x, y: group.position.y, z: group.position.z },
@@ -2399,7 +2559,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2399
2559
  [translationAnimatable.id]: translationAnimatable
2400
2560
  };
2401
2561
  const rotationAnimatable = {
2402
- id: crypto.randomUUID(),
2562
+ id: createBrowserSafeId5(),
2403
2563
  name: `${group.name ?? "Group"} rotation`,
2404
2564
  type: "euler",
2405
2565
  default: { x: group.rotation.x, y: group.rotation.y, z: group.rotation.z },
@@ -2412,7 +2572,7 @@ function importGroup(group, namespaces, colorLookup, rootBounds) {
2412
2572
  };
2413
2573
  animatables = { ...animatables, [rotationAnimatable.id]: rotationAnimatable };
2414
2574
  const scaleAnimatable = {
2415
- id: crypto.randomUUID(),
2575
+ id: createBrowserSafeId5(),
2416
2576
  name: `${group.name ?? "Group"} scale`,
2417
2577
  type: "vector3",
2418
2578
  default: { x: group.scale.x, y: group.scale.y, z: group.scale.z },
@@ -2605,7 +2765,10 @@ var MATERIAL_NAME_FEATURE_KEYS = [
2605
2765
  "opacity",
2606
2766
  "roughness",
2607
2767
  "metalness",
2608
- "shininess"
2768
+ "shininess",
2769
+ "emissive",
2770
+ "emissiveIntensity",
2771
+ "specular"
2609
2772
  ];
2610
2773
  var MATERIAL_NAME_SUFFIXES2 = [
2611
2774
  " color",
@@ -2614,7 +2777,10 @@ var MATERIAL_NAME_SUFFIXES2 = [
2614
2777
  " opacity",
2615
2778
  " roughness",
2616
2779
  " metalness",
2617
- " shininess"
2780
+ " shininess",
2781
+ " emissive intensity",
2782
+ " emissive",
2783
+ " specular"
2618
2784
  ];
2619
2785
  function applyStoredRenderableNames(object, data) {
2620
2786
  if (typeof data.name === "string" && data.name.length > 0) {
@@ -2834,12 +3000,29 @@ function applyVizijBundle(object, bundle) {
2834
3000
  const userData = object.userData && typeof object.userData === "object" ? object.userData : {};
2835
3001
  const originalExtensions = userData.gltfExtensions;
2836
3002
  let applied = false;
3003
+ const staleCarriers = [];
2837
3004
  if (bundle) {
2838
3005
  userData.gltfExtensions = {
2839
3006
  ...originalExtensions ?? {},
2840
3007
  VIZIJ_bundle: bundle
2841
3008
  };
2842
3009
  object.userData = userData;
3010
+ object.traverse((node) => {
3011
+ if (node === object) {
3012
+ return;
3013
+ }
3014
+ const extensions = node.userData?.gltfExtensions;
3015
+ if (!extensions || !(BUNDLE_KEYS[0] in extensions)) {
3016
+ return;
3017
+ }
3018
+ staleCarriers.push({ node, extensions });
3019
+ const { [BUNDLE_KEYS[0]]: _stale, ...rest } = extensions;
3020
+ if (Object.keys(rest).length > 0) {
3021
+ node.userData.gltfExtensions = rest;
3022
+ } else {
3023
+ delete node.userData.gltfExtensions;
3024
+ }
3025
+ });
2843
3026
  applied = true;
2844
3027
  }
2845
3028
  return () => {
@@ -2856,6 +3039,10 @@ function applyVizijBundle(object, bundle) {
2856
3039
  delete object.userData;
2857
3040
  }
2858
3041
  }
3042
+ for (const { node, extensions } of staleCarriers) {
3043
+ node.userData.gltfExtensions = extensions;
3044
+ }
3045
+ staleCarriers.length = 0;
2859
3046
  applied = false;
2860
3047
  };
2861
3048
  }
@@ -3307,20 +3494,12 @@ async function resolveParserJson(parserJson, fallback) {
3307
3494
  return parserJson;
3308
3495
  }
3309
3496
  async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
3310
- const modelLoader = new GLTFLoader();
3311
- modelLoader.setDRACOLoader(new DRACOLoader());
3312
- const modelData = await modelLoader.loadAsync(url);
3313
- const parserJson = await resolveParserJson(modelData?.parser?.json, {
3314
- url
3315
- });
3316
- const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3317
- const asset = parseScene(
3318
- modelData.scene,
3319
- actualizedNamespaces,
3497
+ const asset = await loadGLTFWithBundleInternal(
3498
+ url,
3499
+ namespaces,
3320
3500
  aggressiveImport,
3321
3501
  rootBounds,
3322
- parserJson,
3323
- modelData.animations
3502
+ "url"
3324
3503
  );
3325
3504
  return [asset.world, asset.animatables, asset.animations];
3326
3505
  }
@@ -3329,11 +3508,12 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
3329
3508
  if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
3330
3509
  const objectUrl = URL.createObjectURL(blob);
3331
3510
  try {
3332
- const asset = await loadGLTFWithBundle(
3511
+ const asset = await loadGLTFWithBundleInternal(
3333
3512
  objectUrl,
3334
3513
  actualizedNamespaces,
3335
3514
  aggressiveImport,
3336
3515
  rootBounds,
3516
+ "blob",
3337
3517
  { blob }
3338
3518
  );
3339
3519
  return [asset.world, asset.animatables, asset.animations];
@@ -3343,8 +3523,7 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
3343
3523
  }
3344
3524
  const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
3345
3525
  return new Promise((resolve, reject) => {
3346
- const loader = new GLTFLoader();
3347
- loader.setDRACOLoader(new DRACOLoader());
3526
+ const loader = createTrackedGLTFLoader();
3348
3527
  loader.parse(
3349
3528
  arrayBuffer,
3350
3529
  "",
@@ -3388,9 +3567,20 @@ function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson,
3388
3567
  const animations = extractVizijAnimations(parserJson, clips);
3389
3568
  return { world, animatables, bundle, animations, scene };
3390
3569
  }
3391
- async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
3570
+ function createTrackedGLTFLoader() {
3571
+ recordRenderCounter("gltfLoadCount");
3572
+ recordRenderCounter("dracoLoaderCreationCount");
3392
3573
  const modelLoader = new GLTFLoader();
3393
3574
  modelLoader.setDRACOLoader(new DRACOLoader());
3575
+ return modelLoader;
3576
+ }
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
+ }
3583
+ const modelLoader = createTrackedGLTFLoader();
3394
3584
  const modelData = await modelLoader.loadAsync(url);
3395
3585
  const parserJson = await resolveParserJson(modelData?.parser?.json, {
3396
3586
  url,
@@ -3406,16 +3596,27 @@ async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, roo
3406
3596
  modelData.animations
3407
3597
  );
3408
3598
  }
3599
+ async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds, parserJsonFallback) {
3600
+ return loadGLTFWithBundleInternal(
3601
+ url,
3602
+ namespaces,
3603
+ aggressiveImport,
3604
+ rootBounds,
3605
+ "url",
3606
+ parserJsonFallback
3607
+ );
3608
+ }
3409
3609
  async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = false, rootBounds) {
3410
3610
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
3411
3611
  if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
3412
3612
  const objectUrl = URL.createObjectURL(blob);
3413
3613
  try {
3414
- return await loadGLTFWithBundle(
3614
+ return await loadGLTFWithBundleInternal(
3415
3615
  objectUrl,
3416
3616
  actualizedNamespaces,
3417
3617
  aggressiveImport,
3418
3618
  rootBounds,
3619
+ "blob",
3419
3620
  { blob }
3420
3621
  );
3421
3622
  } finally {
@@ -3424,8 +3625,7 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
3424
3625
  }
3425
3626
  const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
3426
3627
  return new Promise((resolve, reject) => {
3427
- const loader = new GLTFLoader();
3428
- loader.setDRACOLoader(new DRACOLoader());
3628
+ const loader = createTrackedGLTFLoader();
3429
3629
  loader.parse(
3430
3630
  arrayBuffer,
3431
3631
  "",
@@ -3462,6 +3662,9 @@ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = f
3462
3662
  // src/functions/load-gltf-blob.ts
3463
3663
  import { DRACOLoader as DRACOLoader2, GLTFLoader as GLTFLoader2 } from "three-stdlib";
3464
3664
  var loadGltfFromBlob = (blob, namespaces) => {
3665
+ recordRenderCounter("gltfLoadCount");
3666
+ recordRenderCounter("gltfBlobLoadCount");
3667
+ recordRenderCounter("dracoLoaderCreationCount");
3465
3668
  return new Promise((resolve, reject) => {
3466
3669
  const loader = new GLTFLoader2();
3467
3670
  loader.setDRACOLoader(new DRACOLoader2());