@vizij/runtime-react 0.0.5 → 0.0.9

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/README.md CHANGED
@@ -12,6 +12,27 @@ pnpm add @vizij/runtime-react @vizij/render @vizij/orchestrator-react react reac
12
12
 
13
13
  Those three Vizij packages must stay in lock-step; always upgrade them together.
14
14
 
15
+ > **Bundler configuration:** The runtime depends on `@vizij/orchestrator-wasm`, `@vizij/node-graph-wasm`, and `@vizij/animation-wasm`, all of which emit `.wasm` assets. Enable async WebAssembly and treat `.wasm` files as emitted resources in your bundler. For Next.js:
16
+ >
17
+ > ```js
18
+ > // next.config.js
19
+ > module.exports = {
20
+ > webpack: (config) => {
21
+ > config.experiments = {
22
+ > ...(config.experiments ?? {}),
23
+ > asyncWebAssembly: true,
24
+ > };
25
+ > config.module.rules.push({
26
+ > test: /\.wasm$/,
27
+ > type: "asset/resource",
28
+ > });
29
+ > return config;
30
+ > },
31
+ > };
32
+ > ```
33
+
34
+ When overriding the default wasm location, pass string URLs to the underlying `init()` helpers so Webpack doesn’t wrap them in `RelativeURL`.
35
+
15
36
  ## Getting Started
16
37
 
17
38
  ```tsx
package/dist/index.js CHANGED
@@ -291,6 +291,122 @@ function convertBundleAnimations(entries) {
291
291
  clip: entry.clip
292
292
  }));
293
293
  }
294
+ function resolveChannelId(track) {
295
+ if (typeof track.componentId !== "string" || track.componentId.length === 0) {
296
+ return null;
297
+ }
298
+ if (typeof track.component === "string" && track.component.length > 0) {
299
+ return `${track.componentId}:${track.component}`;
300
+ }
301
+ const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : void 0;
302
+ const valueSize = track.valueSize != null ? Number(track.valueSize) : void 0;
303
+ if (Number.isInteger(rawIndex) && Number.isFinite(rawIndex) && rawIndex >= 0 && Number.isFinite(valueSize) && valueSize > 1) {
304
+ return `${track.componentId}:${rawIndex}`;
305
+ }
306
+ return track.componentId;
307
+ }
308
+ function convertExtractedAnimations(clips) {
309
+ if (!Array.isArray(clips) || clips.length === 0) {
310
+ return [];
311
+ }
312
+ const assets = [];
313
+ clips.forEach((clip) => {
314
+ const clipTracks = Array.isArray(clip.tracks) ? clip.tracks : [];
315
+ if (clipTracks.length === 0) {
316
+ return;
317
+ }
318
+ const convertedTracks = [];
319
+ clipTracks.forEach((track) => {
320
+ const channelId = resolveChannelId(track);
321
+ if (!channelId) {
322
+ return;
323
+ }
324
+ const rawTimes = Array.isArray(track.times) ? track.times : [];
325
+ const rawValues = Array.isArray(track.values) ? track.values : [];
326
+ if (rawTimes.length === 0 || rawValues.length === 0) {
327
+ return;
328
+ }
329
+ const times = [];
330
+ for (const entry of rawTimes) {
331
+ const time = Number(entry);
332
+ if (!Number.isFinite(time)) {
333
+ return;
334
+ }
335
+ times.push(time);
336
+ }
337
+ const values = [];
338
+ for (const entry of rawValues) {
339
+ const value = Number(entry);
340
+ if (!Number.isFinite(value)) {
341
+ return;
342
+ }
343
+ values.push(value);
344
+ }
345
+ const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
346
+ const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
347
+ if (values.length !== times.length * valueSize) {
348
+ return;
349
+ }
350
+ const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
351
+ const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
352
+ const keyframes = [];
353
+ times.forEach((time, index) => {
354
+ const base = index * valueSize + componentIndex;
355
+ const value = values[base];
356
+ if (!Number.isFinite(value)) {
357
+ return;
358
+ }
359
+ keyframes.push({ time, value });
360
+ });
361
+ if (keyframes.length === 0) {
362
+ return;
363
+ }
364
+ convertedTracks.push({
365
+ channel: channelId,
366
+ keyframes
367
+ });
368
+ });
369
+ if (convertedTracks.length === 0) {
370
+ return;
371
+ }
372
+ const durationFromTracks = convertedTracks.reduce((maxTime, track) => {
373
+ const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
374
+ if (keyframes.length === 0) {
375
+ return maxTime;
376
+ }
377
+ const lastKeyframe = keyframes[keyframes.length - 1];
378
+ const time = Number(lastKeyframe?.time ?? 0);
379
+ if (!Number.isFinite(time)) {
380
+ return maxTime;
381
+ }
382
+ return time > maxTime ? time : maxTime;
383
+ }, 0);
384
+ const duration = typeof clip.duration === "number" && Number.isFinite(clip.duration) ? clip.duration : durationFromTracks;
385
+ const clipId = typeof clip.id === "string" && clip.id.length > 0 ? clip.id : typeof clip.name === "string" && clip.name.length > 0 ? clip.name : `gltf-animation-${assets.length}`;
386
+ const metadata = clip.metadata && typeof clip.metadata === "object" && !Array.isArray(clip.metadata) ? clip.metadata : void 0;
387
+ assets.push({
388
+ id: clipId,
389
+ clip: {
390
+ id: clipId,
391
+ name: typeof clip.name === "string" ? clip.name : clipId,
392
+ duration,
393
+ tracks: convertedTracks,
394
+ metadata
395
+ }
396
+ });
397
+ });
398
+ return assets;
399
+ }
400
+ function pickExtractedAnimations(asset) {
401
+ if (!asset || typeof asset !== "object") {
402
+ return void 0;
403
+ }
404
+ const animations = asset.animations;
405
+ if (!Array.isArray(animations)) {
406
+ return void 0;
407
+ }
408
+ return animations;
409
+ }
294
410
  function mergeAnimationLists(explicit, fromBundle) {
295
411
  if (!explicit?.length && fromBundle.length === 0) {
296
412
  return void 0;
@@ -314,7 +430,7 @@ function mergeAnimationLists(explicit, fromBundle) {
314
430
  }
315
431
  return changed ? merged : explicit;
316
432
  }
317
- function mergeAssetBundle(base, extracted) {
433
+ function mergeAssetBundle(base, extracted, extractedAnimations) {
318
434
  const resolvedBundle = base.bundle ?? extracted ?? null;
319
435
  const rigFromBundle = convertBundleGraph(
320
436
  pickBundleGraph(resolvedBundle, ["rig"])
@@ -354,10 +470,17 @@ function mergeAssetBundle(base, extracted) {
354
470
  const animationsFromBundle = convertBundleAnimations(
355
471
  resolvedBundle?.animations
356
472
  );
357
- const resolvedAnimations = mergeAnimationLists(
473
+ let resolvedAnimations = mergeAnimationLists(
358
474
  base.animations,
359
475
  animationsFromBundle
360
476
  );
477
+ const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
478
+ if (animationsFromAsset.length > 0) {
479
+ resolvedAnimations = mergeAnimationLists(
480
+ resolvedAnimations,
481
+ animationsFromAsset
482
+ );
483
+ }
361
484
  const merged = {
362
485
  ...base
363
486
  };
@@ -430,6 +553,7 @@ function VizijRuntimeProviderInner({
430
553
  }
431
554
  return null;
432
555
  });
556
+ const [extractedAnimations, setExtractedAnimations] = (0, import_react2.useState)([]);
433
557
  (0, import_react2.useEffect)(() => {
434
558
  if (initialAssetBundle.bundle) {
435
559
  setExtractedBundle(initialAssetBundle.bundle);
@@ -442,8 +566,12 @@ function VizijRuntimeProviderInner({
442
566
  }
443
567
  }, [initialAssetBundle]);
444
568
  const assetBundle = (0, import_react2.useMemo)(
445
- () => mergeAssetBundle(initialAssetBundle, extractedBundle),
446
- [initialAssetBundle, extractedBundle]
569
+ () => mergeAssetBundle(
570
+ initialAssetBundle,
571
+ extractedBundle,
572
+ extractedAnimations
573
+ ),
574
+ [initialAssetBundle, extractedBundle, extractedAnimations]
447
575
  );
448
576
  const {
449
577
  ready,
@@ -562,11 +690,13 @@ function VizijRuntimeProviderInner({
562
690
  outputPaths: [],
563
691
  controllers: { graphs: [], anims: [] }
564
692
  }));
693
+ setExtractedAnimations([]);
565
694
  const loadAssets = async () => {
566
695
  try {
567
696
  let world;
568
697
  let animatables;
569
698
  let bundle = baseBundle;
699
+ let gltfAnimations;
570
700
  if (glbAsset.kind === "url") {
571
701
  const loaded = await (0, import_render.loadGLTFWithBundle)(
572
702
  glbAsset.src,
@@ -577,6 +707,7 @@ function VizijRuntimeProviderInner({
577
707
  world = loaded.world;
578
708
  animatables = loaded.animatables;
579
709
  bundle = loaded.bundle ?? bundle;
710
+ gltfAnimations = pickExtractedAnimations(loaded);
580
711
  } else if (glbAsset.kind === "blob") {
581
712
  const loaded = await (0, import_render.loadGLTFFromBlobWithBundle)(
582
713
  glbAsset.blob,
@@ -587,15 +718,18 @@ function VizijRuntimeProviderInner({
587
718
  world = loaded.world;
588
719
  animatables = loaded.animatables;
589
720
  bundle = loaded.bundle ?? bundle;
721
+ gltfAnimations = pickExtractedAnimations(loaded);
590
722
  } else {
591
723
  world = glbAsset.world;
592
724
  animatables = glbAsset.animatables;
593
725
  bundle = glbAsset.bundle ?? bundle;
726
+ gltfAnimations = void 0;
594
727
  }
595
728
  if (cancelled) {
596
729
  return;
597
730
  }
598
731
  setExtractedBundle(bundle ?? null);
732
+ setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
599
733
  const rootId = findRootId(world);
600
734
  store.getState().addWorldElements(world, animatables, true);
601
735
  reportStatus((prev) => ({
@@ -634,7 +768,8 @@ function VizijRuntimeProviderInner({
634
768
  pushError,
635
769
  reportStatus,
636
770
  resetErrors,
637
- setExtractedBundle
771
+ setExtractedBundle,
772
+ setExtractedAnimations
638
773
  ]);
639
774
  (0, import_react2.useEffect)(() => {
640
775
  if (!ready && autoCreate) {
package/dist/index.mjs CHANGED
@@ -284,6 +284,122 @@ function convertBundleAnimations(entries) {
284
284
  clip: entry.clip
285
285
  }));
286
286
  }
287
+ function resolveChannelId(track) {
288
+ if (typeof track.componentId !== "string" || track.componentId.length === 0) {
289
+ return null;
290
+ }
291
+ if (typeof track.component === "string" && track.component.length > 0) {
292
+ return `${track.componentId}:${track.component}`;
293
+ }
294
+ const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : void 0;
295
+ const valueSize = track.valueSize != null ? Number(track.valueSize) : void 0;
296
+ if (Number.isInteger(rawIndex) && Number.isFinite(rawIndex) && rawIndex >= 0 && Number.isFinite(valueSize) && valueSize > 1) {
297
+ return `${track.componentId}:${rawIndex}`;
298
+ }
299
+ return track.componentId;
300
+ }
301
+ function convertExtractedAnimations(clips) {
302
+ if (!Array.isArray(clips) || clips.length === 0) {
303
+ return [];
304
+ }
305
+ const assets = [];
306
+ clips.forEach((clip) => {
307
+ const clipTracks = Array.isArray(clip.tracks) ? clip.tracks : [];
308
+ if (clipTracks.length === 0) {
309
+ return;
310
+ }
311
+ const convertedTracks = [];
312
+ clipTracks.forEach((track) => {
313
+ const channelId = resolveChannelId(track);
314
+ if (!channelId) {
315
+ return;
316
+ }
317
+ const rawTimes = Array.isArray(track.times) ? track.times : [];
318
+ const rawValues = Array.isArray(track.values) ? track.values : [];
319
+ if (rawTimes.length === 0 || rawValues.length === 0) {
320
+ return;
321
+ }
322
+ const times = [];
323
+ for (const entry of rawTimes) {
324
+ const time = Number(entry);
325
+ if (!Number.isFinite(time)) {
326
+ return;
327
+ }
328
+ times.push(time);
329
+ }
330
+ const values = [];
331
+ for (const entry of rawValues) {
332
+ const value = Number(entry);
333
+ if (!Number.isFinite(value)) {
334
+ return;
335
+ }
336
+ values.push(value);
337
+ }
338
+ const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
339
+ const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
340
+ if (values.length !== times.length * valueSize) {
341
+ return;
342
+ }
343
+ const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
344
+ const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
345
+ const keyframes = [];
346
+ times.forEach((time, index) => {
347
+ const base = index * valueSize + componentIndex;
348
+ const value = values[base];
349
+ if (!Number.isFinite(value)) {
350
+ return;
351
+ }
352
+ keyframes.push({ time, value });
353
+ });
354
+ if (keyframes.length === 0) {
355
+ return;
356
+ }
357
+ convertedTracks.push({
358
+ channel: channelId,
359
+ keyframes
360
+ });
361
+ });
362
+ if (convertedTracks.length === 0) {
363
+ return;
364
+ }
365
+ const durationFromTracks = convertedTracks.reduce((maxTime, track) => {
366
+ const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
367
+ if (keyframes.length === 0) {
368
+ return maxTime;
369
+ }
370
+ const lastKeyframe = keyframes[keyframes.length - 1];
371
+ const time = Number(lastKeyframe?.time ?? 0);
372
+ if (!Number.isFinite(time)) {
373
+ return maxTime;
374
+ }
375
+ return time > maxTime ? time : maxTime;
376
+ }, 0);
377
+ const duration = typeof clip.duration === "number" && Number.isFinite(clip.duration) ? clip.duration : durationFromTracks;
378
+ const clipId = typeof clip.id === "string" && clip.id.length > 0 ? clip.id : typeof clip.name === "string" && clip.name.length > 0 ? clip.name : `gltf-animation-${assets.length}`;
379
+ const metadata = clip.metadata && typeof clip.metadata === "object" && !Array.isArray(clip.metadata) ? clip.metadata : void 0;
380
+ assets.push({
381
+ id: clipId,
382
+ clip: {
383
+ id: clipId,
384
+ name: typeof clip.name === "string" ? clip.name : clipId,
385
+ duration,
386
+ tracks: convertedTracks,
387
+ metadata
388
+ }
389
+ });
390
+ });
391
+ return assets;
392
+ }
393
+ function pickExtractedAnimations(asset) {
394
+ if (!asset || typeof asset !== "object") {
395
+ return void 0;
396
+ }
397
+ const animations = asset.animations;
398
+ if (!Array.isArray(animations)) {
399
+ return void 0;
400
+ }
401
+ return animations;
402
+ }
287
403
  function mergeAnimationLists(explicit, fromBundle) {
288
404
  if (!explicit?.length && fromBundle.length === 0) {
289
405
  return void 0;
@@ -307,7 +423,7 @@ function mergeAnimationLists(explicit, fromBundle) {
307
423
  }
308
424
  return changed ? merged : explicit;
309
425
  }
310
- function mergeAssetBundle(base, extracted) {
426
+ function mergeAssetBundle(base, extracted, extractedAnimations) {
311
427
  const resolvedBundle = base.bundle ?? extracted ?? null;
312
428
  const rigFromBundle = convertBundleGraph(
313
429
  pickBundleGraph(resolvedBundle, ["rig"])
@@ -347,10 +463,17 @@ function mergeAssetBundle(base, extracted) {
347
463
  const animationsFromBundle = convertBundleAnimations(
348
464
  resolvedBundle?.animations
349
465
  );
350
- const resolvedAnimations = mergeAnimationLists(
466
+ let resolvedAnimations = mergeAnimationLists(
351
467
  base.animations,
352
468
  animationsFromBundle
353
469
  );
470
+ const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
471
+ if (animationsFromAsset.length > 0) {
472
+ resolvedAnimations = mergeAnimationLists(
473
+ resolvedAnimations,
474
+ animationsFromAsset
475
+ );
476
+ }
354
477
  const merged = {
355
478
  ...base
356
479
  };
@@ -423,6 +546,7 @@ function VizijRuntimeProviderInner({
423
546
  }
424
547
  return null;
425
548
  });
549
+ const [extractedAnimations, setExtractedAnimations] = useState([]);
426
550
  useEffect(() => {
427
551
  if (initialAssetBundle.bundle) {
428
552
  setExtractedBundle(initialAssetBundle.bundle);
@@ -435,8 +559,12 @@ function VizijRuntimeProviderInner({
435
559
  }
436
560
  }, [initialAssetBundle]);
437
561
  const assetBundle = useMemo(
438
- () => mergeAssetBundle(initialAssetBundle, extractedBundle),
439
- [initialAssetBundle, extractedBundle]
562
+ () => mergeAssetBundle(
563
+ initialAssetBundle,
564
+ extractedBundle,
565
+ extractedAnimations
566
+ ),
567
+ [initialAssetBundle, extractedBundle, extractedAnimations]
440
568
  );
441
569
  const {
442
570
  ready,
@@ -555,11 +683,13 @@ function VizijRuntimeProviderInner({
555
683
  outputPaths: [],
556
684
  controllers: { graphs: [], anims: [] }
557
685
  }));
686
+ setExtractedAnimations([]);
558
687
  const loadAssets = async () => {
559
688
  try {
560
689
  let world;
561
690
  let animatables;
562
691
  let bundle = baseBundle;
692
+ let gltfAnimations;
563
693
  if (glbAsset.kind === "url") {
564
694
  const loaded = await loadGLTFWithBundle(
565
695
  glbAsset.src,
@@ -570,6 +700,7 @@ function VizijRuntimeProviderInner({
570
700
  world = loaded.world;
571
701
  animatables = loaded.animatables;
572
702
  bundle = loaded.bundle ?? bundle;
703
+ gltfAnimations = pickExtractedAnimations(loaded);
573
704
  } else if (glbAsset.kind === "blob") {
574
705
  const loaded = await loadGLTFFromBlobWithBundle(
575
706
  glbAsset.blob,
@@ -580,15 +711,18 @@ function VizijRuntimeProviderInner({
580
711
  world = loaded.world;
581
712
  animatables = loaded.animatables;
582
713
  bundle = loaded.bundle ?? bundle;
714
+ gltfAnimations = pickExtractedAnimations(loaded);
583
715
  } else {
584
716
  world = glbAsset.world;
585
717
  animatables = glbAsset.animatables;
586
718
  bundle = glbAsset.bundle ?? bundle;
719
+ gltfAnimations = void 0;
587
720
  }
588
721
  if (cancelled) {
589
722
  return;
590
723
  }
591
724
  setExtractedBundle(bundle ?? null);
725
+ setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
592
726
  const rootId = findRootId(world);
593
727
  store.getState().addWorldElements(world, animatables, true);
594
728
  reportStatus((prev) => ({
@@ -627,7 +761,8 @@ function VizijRuntimeProviderInner({
627
761
  pushError,
628
762
  reportStatus,
629
763
  resetErrors,
630
- setExtractedBundle
764
+ setExtractedBundle,
765
+ setExtractedAnimations
631
766
  ]);
632
767
  useEffect(() => {
633
768
  if (!ready && autoCreate) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vizij/runtime-react",
3
3
  "description": "Runtime provider that bridges Vizij renderer assets with orchestrator controllers for React apps.",
4
- "version": "0.0.5",
4
+ "version": "0.0.9",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -25,9 +25,9 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@vizij/value-json": "^0.1.0",
28
- "@vizij/utils": "^0.0.2",
29
28
  "@vizij/orchestrator-react": "^0.0.2",
30
- "@vizij/render": "^0.0.4"
29
+ "@vizij/render": "^0.0.4",
30
+ "@vizij/utils": "^0.0.2"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": ">=18",