@vizij/render 0.1.1 → 1.0.0

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Three.js + React renderer, scene store, and helper controllers for Vizij faces.
4
4
 
5
- This package exposes the `Vizij` canvas component along with hooks, stores, and GLTF helpers that power Vizij’s real-time character visualisation. It is the foundation that other packages (`@vizij/rig`, `@vizij/orchestrator-react`, etc.) build upon.
5
+ This package exposes the `Vizij` canvas component along with hooks, stores, and GLTF helpers that power Vizij’s real-time character visualisation. It is the foundation that other packages (`@vizij/runtime-react`, etc.) build upon.
6
6
 
7
7
  ---
8
8
 
@@ -23,7 +23,7 @@ This package exposes the `Vizij` canvas component along with hooks, stores, and
23
23
 
24
24
  - `Vizij` renders a fully managed `@react-three/fiber` canvas with sensible defaults for orthographic cameras and safe-area overlays.
25
25
  - A Zustand-powered store (`useVizijStore`) tracks renderables, controllers, and transient state. Hooks let you read or mutate slices without re-rendering entire scenes.
26
- - Utilities (`loadGLTF`, `loadGLTFBlob`, export helpers) streamline loading rigged GLTF assets and exporting scene snapshots. The tuple helpers now return `[world, animatables, animations]`, where `animations` contains parsed clip metadata for any channels embedded in the GLB.
26
+ - The loader (`loadGLTF`, `loadGLTFFromBlob` and their `WithBundle` forms) turns a rigged GLB into the world model; `buildSceneGraph` and `exportScene` turn the world model back into GLB bytes. Both run in Node as well as in a browser. The tuple helpers return `[world, animatables, animations]`, where `animations` contains parsed clip metadata for any channels embedded in the GLB.
27
27
  - Controllers wrap common behaviours (e.g., pointer interaction, safe-area visualisation) so you can compose features quickly.
28
28
 
29
29
  ---
@@ -96,23 +96,24 @@ The store tracks world graph entries, controllers, debug overlays, and renderabl
96
96
  ## Controllers & Helpers
97
97
 
98
98
  - Controllers under `src/controllers` encapsulate input handling, camera logic, and other behaviours. Compose them with your own React components.
99
- - `loadGLTF` / `loadGLTFBlob` simplify loading rig assets and extract animatable metadata used by `@vizij/rig`.
100
- - `export` helpers produce snapshots of the current scene (useful for tooling or exporting frames).
99
+ - `loadGLTF` / `loadGLTFFromBlob` load rig assets and extract the animatable metadata used by `@vizij/runtime-react`. A blob is parsed in memory, with nothing fetched.
100
+ - `buildSceneGraph(world, animatables, rootId)` builds the three.js graph of a world element with no renderer, and `exportScene(root, { bundle, animations })` encodes a graph as GLB bytes. Saving or uploading the bytes is the caller's job.
101
101
 
102
102
  All exports are re-exported through `src/index.tsx`, so a simple `import { loadGLTF } from "@vizij/render"` works.
103
103
 
104
104
  ### Dual-format Vizij bundles
105
105
 
106
- Vizij scenes persist authoring metadata inside GLBs so third-party tools see baked animation, while Vizij runtimes retain orchestrator graphs and clips.
106
+ Vizij scenes persist authoring metadata inside GLBs so third-party tools see baked animation, while Vizij runtimes retain the authored graphs and clips.
107
107
 
108
108
  - Every renderable still carries a `RobotData` extension in `userData` describing features and animatable bindings.
109
109
  - The exporter now writes a root-level `extensions.VIZIJ_bundle` block following the schema in [`src/types/vizij-bundle.ts`](./src/types/vizij-bundle.ts). It contains rig graphs, pose configs, stored Vizij clips, and provenance hashes.
110
- - Use `exportScene(group, { bundle, animations })` to embed both the Vizij bundle and optional baked `THREE.AnimationClip` instances. The helper attaches the bundle only for the export call and restores the original object.
110
+ - Use `exportScene(root, { bundle, animations })` to embed both the Vizij bundle and optional baked `THREE.AnimationClip` instances; it resolves to the GLB as an `ArrayBuffer`. The helper attaches the bundle only for the export call and restores the original object.
111
+ - A graph from `buildSceneGraph` carries each element's authored values: node translation, rotation and scale, morph weights and material factors equal the `RobotData` defaults written beside them, not a runtime pose.
111
112
  - When loading assets, prefer `loadGLTFWithBundle` / `loadGLTFFromBlobWithBundle` to retrieve `{ world, animatables, bundle, animations }`. The legacy tuple helpers return `[world, animatables, animations]` if you only need the renderer state.
112
113
  - The new `animations` field exposes `VizijAnimationClipData[]`, which maps each glTF animation channel back to Vizij animatable ids (`RobotData.features.*.value.id`). Each track provides component-aware `times`/`values` arrays so runtimes can register clips without re-parsing the GLB.
113
114
  - `extractVizijBundle(scene)` and `applyVizijBundle(scene, bundle)` (under `src/functions/vizij-bundle.ts`) let advanced tooling inspect or mutate bundles without triggering a fresh export.
114
115
 
115
- With this structure, authoring tools can round-trip orchestrator assets while shipping native glTF animations for viewers that do not understand Vizij.
116
+ With this structure, authoring tools can round-trip Vizij runtime assets while shipping native glTF animations for viewers that do not understand Vizij.
116
117
 
117
118
  ---
118
119
 
@@ -132,14 +133,17 @@ pnpm --filter "@vizij/render" size
132
133
 
133
134
  ## Publishing
134
135
 
135
- Use the shared workflow at [`.github/workflows/publish-npm.yml`](../../.github/workflows/publish-npm.yml).
136
+ Use the shared workflow at [`.github/workflows/publish-npm.yml`](../../../.github/workflows/publish-npm.yml).
136
137
 
137
138
  1. Align dependency versions (`three`, `@react-three/*`, `zustand`, Vizij packages) and generate a changeset:
139
+
138
140
  ```bash
139
141
  pnpm changeset
140
142
  pnpm version:packages
141
143
  ```
144
+
142
145
  2. Validate locally:
146
+
143
147
  ```bash
144
148
  pnpm install
145
149
  pnpm --filter "@vizij/render" build
@@ -148,13 +152,14 @@ Use the shared workflow at [`.github/workflows/publish-npm.yml`](../../.github/w
148
152
  pnpm --filter "@vizij/render" lint
149
153
  pnpm --filter "@vizij/render" exec npm pack --dry-run
150
154
  ```
151
- 3. Tag the release as `npm-render-vX.Y.Z` and push the tag. The workflow will publish with provenance metadata.
155
+
156
+ 3. Merge the version bump to `main`. CI publishes any `@vizij/*` version not yet on npm (with provenance) — no release tag. See the root [README → Publishing & Versioning](../../../README.md#publishing--versioning).
152
157
 
153
158
  ---
154
159
 
155
160
  ## Related Packages
156
161
 
157
- - [`@vizij/rig`](../@vizij/rig/README.md) – Hooks that consume the renderer to load rigged models.
158
- - [`@vizij/animation-react`](../@vizij/animation-react/README.md) – React bindings that feed animation values back into the renderer.
162
+ - [`@vizij/runtime-react`](../runtime-react/README.md) – Hooks that consume the renderer to load rigged models.
163
+ - [`@vizij/animation-react`](../animation-react/README.md) – React bindings that feed animation values back into the renderer.
159
164
 
160
165
  Questions or contributions? Open an issue so we can keep the renderer API and docs sharp for the whole Vizij ecosystem. 🎨
package/dist/index.d.mts CHANGED
@@ -2,49 +2,11 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { CSSProperties, ComponentProps, ReactNode, RefObject, MouseEvent } from 'react';
4
4
  import { Canvas, ThreeEvent } from '@react-three/fiber';
5
- import * as zustand from 'zustand';
6
5
  import * as THREE from 'three';
7
- import { Mesh, Group as Group$1, BufferGeometry, ShapeGeometry, AnimationClip, Object3D } from 'three';
6
+ import { BufferGeometry, ShapeGeometry, ToneMapping, Group as Group$1, Mesh, AnimationClip, Object3D } from 'three';
7
+ import * as zustand from 'zustand';
8
8
  import { RawValue, AnimatableValue, RawVector2 } from '@vizij/utils';
9
9
 
10
- interface VizijProps {
11
- style?: CSSProperties;
12
- className?: string;
13
- rootId: string;
14
- namespace?: string;
15
- showSafeArea?: boolean;
16
- showSelectionGlow?: boolean;
17
- onPointerMissed?: ComponentProps<typeof Canvas>["onPointerMissed"];
18
- }
19
- /**
20
- * Renders the Vizij component.
21
- *
22
- * @param style - The style object for the Vizij component.
23
- *
24
- * @param className - The CSS class name for the Vizij component
25
- *
26
- * @param rootId - The root identifier for the Vizij component.
27
- *
28
- * @param namespace - The namespace for the Vizij component
29
- *
30
- * @param showSafeArea - Whether to show the safe area.
31
- *
32
- * @returns The rendered ReactNode.
33
- */
34
- declare function Vizij({ style, className, rootId, namespace, showSafeArea, showSelectionGlow, onPointerMissed, }: VizijProps): ReactNode;
35
- interface InnerVizijProps {
36
- rootId: string;
37
- namespace: string;
38
- container?: {
39
- width: number;
40
- height: number;
41
- resolution: number;
42
- };
43
- showSafeArea?: boolean;
44
- showSelectionGlow?: boolean;
45
- }
46
- declare function InnerVizij({ rootId, namespace, container, showSafeArea, showSelectionGlow, }: InnerVizijProps): react_jsx_runtime.JSX.Element;
47
-
48
10
  /**
49
11
  * A wrapping type to reference an attribute that is animatable.
50
12
  *
@@ -92,7 +54,6 @@ interface RenderableBase {
92
54
  name: string;
93
55
  tags: string[];
94
56
  type: string;
95
- refs: Record<string, RefObject<any>>;
96
57
  features: Record<string, Feature>;
97
58
  }
98
59
 
@@ -109,19 +70,17 @@ interface Stored<T extends Omit<RenderableBase, "refs">> {
109
70
  type StoredRenderable = Stored<RenderableBase>;
110
71
 
111
72
  /**
112
- * An object for creating hierarchies/bodies
73
+ * An object for creating rectangles
113
74
  *
114
- * @param id - The id of the ellipse
115
- * @param name - The name of the ellipse
116
- * @param tags - The tags of the ellipse
75
+ * @param id - The id of the rectangle
76
+ * @param name - The name of the rectangle
77
+ * @param tags - The tags of the rectangle
117
78
  * @param type - Type flag
118
- * @param refs - The reference to the group[s] in the scene, for each namespace
119
- * @param features - The features of the ellipse (translation, rotation, and scale)
120
- * @param children - The children of the ellipse (list of ids for other bodies or shapes)
79
+ * @param features - The features of the rectangle (translation, rotation, and scale)
80
+ * @param children - The children of the rectangle (list of ids for other bodies or shapes)
121
81
  */
122
- interface Ellipse extends RenderableBase {
123
- type: "ellipse";
124
- refs: Record<string, RefObject<Mesh>>;
82
+ interface Rectangle extends RenderableBase {
83
+ type: "rectangle";
125
84
  features: {
126
85
  height: Feature;
127
86
  width: Feature;
@@ -130,28 +89,27 @@ interface Ellipse extends RenderableBase {
130
89
  fillColor?: Feature;
131
90
  strokeColor?: Feature;
132
91
  strokeWidth?: Feature;
92
+ strokeRadius?: Feature;
133
93
  strokeOffset?: Feature;
134
94
  translation: Feature;
135
95
  rotation: Feature;
136
96
  };
137
97
  }
138
- type EllipseFeature = keyof Ellipse["features"];
139
- type StoredEllipse = Stored<Ellipse>;
98
+ type RectangleFeature = keyof Rectangle["features"];
99
+ type StoredRectangle = Stored<Rectangle>;
140
100
 
141
101
  /**
142
- * An object for creating rectangles
102
+ * An object for creating hierarchies/bodies
143
103
  *
144
- * @param id - The id of the rectangle
145
- * @param name - The name of the rectangle
146
- * @param tags - The tags of the rectangle
104
+ * @param id - The id of the ellipse
105
+ * @param name - The name of the ellipse
106
+ * @param tags - The tags of the ellipse
147
107
  * @param type - Type flag
148
- * @param refs - The reference to the group[s] in the scene, for each namespace
149
- * @param features - The features of the rectangle (translation, rotation, and scale)
150
- * @param children - The children of the rectangle (list of ids for other bodies or shapes)
108
+ * @param features - The features of the ellipse (translation, rotation, and scale)
109
+ * @param children - The children of the ellipse (list of ids for other bodies or shapes)
151
110
  */
152
- interface Rectangle extends RenderableBase {
153
- type: "rectangle";
154
- refs: Record<string, RefObject<Mesh>>;
111
+ interface Ellipse extends RenderableBase {
112
+ type: "ellipse";
155
113
  features: {
156
114
  height: Feature;
157
115
  width: Feature;
@@ -160,14 +118,13 @@ interface Rectangle extends RenderableBase {
160
118
  fillColor?: Feature;
161
119
  strokeColor?: Feature;
162
120
  strokeWidth?: Feature;
163
- strokeRadius?: Feature;
164
121
  strokeOffset?: Feature;
165
122
  translation: Feature;
166
123
  rotation: Feature;
167
124
  };
168
125
  }
169
- type RectangleFeature = keyof Rectangle["features"];
170
- type StoredRectangle = Stored<Rectangle>;
126
+ type EllipseFeature = keyof Ellipse["features"];
127
+ type StoredEllipse = Stored<Ellipse>;
171
128
 
172
129
  /**
173
130
  * An object for creating hierarchies/groups
@@ -176,14 +133,12 @@ type StoredRectangle = Stored<Rectangle>;
176
133
  * @param name - The name of the group
177
134
  * @param tags - The tags of the group
178
135
  * @param type - Type flag
179
- * @param refs - The reference to the group[s] in the vizij, for each namespace
180
136
  * @param features - The features of the group (translation, rotation, and scale)
181
137
  * @param root - Whether the group is a root node
182
138
  * @param children - The children of the group (list of ids for other groups or shapes)
183
139
  */
184
140
  interface Group extends RenderableBase {
185
141
  type: "group";
186
- refs: Record<string, RefObject<Group$1>>;
187
142
  root: boolean;
188
143
  features: {
189
144
  translation: Feature;
@@ -209,7 +164,6 @@ type StoredGroup = Stored<Group>;
209
164
  * @property name - Human-readable name for the shape
210
165
  * @property tags - List of tags for categorizing and filtering
211
166
  * @property type - Always "shape"
212
- * @property refs - Map of React refs to Three.js Mesh objects
213
167
  * @property features - Visual and transformation properties that can be animated
214
168
  * @property material - The type of Three.js material to use for rendering
215
169
  * @property geometry - The Three.js geometry defining the shape's structure
@@ -220,13 +174,15 @@ type StoredGroup = Stored<Group>;
220
174
  */
221
175
  interface Shape extends RenderableBase {
222
176
  type: "shape";
223
- refs: Record<string, RefObject<Mesh>>;
224
177
  features: {
225
178
  shininess?: Feature;
226
179
  opacity?: Feature;
227
180
  roughness?: Feature;
228
181
  metalness?: Feature;
229
182
  color?: Feature;
183
+ emissive?: Feature;
184
+ emissiveIntensity?: Feature;
185
+ specular?: Feature;
230
186
  translation: Feature;
231
187
  rotation: Feature;
232
188
  scale?: Feature;
@@ -252,8 +208,6 @@ declare enum ShapeMaterial {
252
208
  type ShapeFeature = keyof Shape["features"];
253
209
  type StoredShape = Stored<Omit<Shape, "geometry">>;
254
210
 
255
- type World = Record<string, Group | Ellipse | Rectangle | Shape>;
256
-
257
211
  type RenderableFeature = GroupFeature | EllipseFeature;
258
212
 
259
213
  /**
@@ -276,6 +230,57 @@ interface Selection {
276
230
  };
277
231
  }
278
232
 
233
+ type World = Record<string, Group | Ellipse | Rectangle | Shape>;
234
+
235
+ /**
236
+ * Vizij-level tone-mapping selection.
237
+ *
238
+ * This is a stable, serializable string enum rather than a raw three.js
239
+ * constant so it can be persisted in the `VIZIJ_bundle` extension and round-trip
240
+ * through import/export without leaking three's numeric enum values into stored
241
+ * data.
242
+ *
243
+ * - `"none"` — no tone mapping. Values are written to the framebuffer as-is and
244
+ * clip at 1.0. This matches plain web/canvas rendering and is the historical
245
+ * vizij default (chosen so studio colors match the canvas).
246
+ * - `"agx"` — AgX, Blender 4.x's default view transform. Compresses
247
+ * over-1.0 emissive into soft, slightly-desaturated color; matches faces
248
+ * authored/exported from Blender.
249
+ * - `"aces"` — ACES Filmic, a punchier cinematic curve.
250
+ * - `"neutral"` — Khronos PBR Neutral, a mild curve that preserves hue/saturation
251
+ * while taming highlights.
252
+ */
253
+ type ToneMappingMode = "none" | "agx" | "aces" | "neutral";
254
+ /**
255
+ * The set of supported tone-mapping modes, in display order. Useful for
256
+ * building selection UIs.
257
+ */
258
+ declare const TONE_MAPPING_MODES: ToneMappingMode[];
259
+ /**
260
+ * Human-readable labels for each tone-mapping mode.
261
+ */
262
+ declare const TONE_MAPPING_LABELS: Record<ToneMappingMode, string>;
263
+ /**
264
+ * The default tone-mapping mode used when a face/scene has not specified one.
265
+ * `"none"` preserves the historical web-matching behavior.
266
+ */
267
+ declare const DEFAULT_TONE_MAPPING_MODE: ToneMappingMode;
268
+ /**
269
+ * Type guard: is `value` a valid {@link ToneMappingMode}?
270
+ */
271
+ declare function isToneMappingMode(value: unknown): value is ToneMappingMode;
272
+ /**
273
+ * Resolve a {@link ToneMappingMode} (or an unknown/undefined stored value) to
274
+ * the corresponding three.js tone-mapping constant. Falls back to
275
+ * {@link DEFAULT_TONE_MAPPING_MODE} for missing/invalid input.
276
+ */
277
+ declare function resolveToneMapping(mode: unknown): ToneMapping;
278
+ /**
279
+ * Normalize an unknown/stored value into a valid {@link ToneMappingMode},
280
+ * falling back to {@link DEFAULT_TONE_MAPPING_MODE}.
281
+ */
282
+ declare function normalizeToneMappingMode(value: unknown): ToneMappingMode;
283
+
279
284
  type VizijBundleVersion = 1;
280
285
  type VizijBundleGraphKind = "rig" | "pose" | "pose-driver" | "animation-bridge" | "low-level" | string;
281
286
  type VizijPoseId = string;
@@ -288,6 +293,40 @@ interface VizijBundleGraphMetadata {
288
293
  exportedAt?: string;
289
294
  [key: string]: unknown;
290
295
  }
296
+ /**
297
+ * One path in a profile, with its type and constraints — arora's `KeyInfo`
298
+ * shape, so a profile round-trips through the descriptor the runtime already
299
+ * speaks.
300
+ */
301
+ interface VizijProfileKey {
302
+ path: string;
303
+ kind?: string;
304
+ value_type?: string;
305
+ min?: number;
306
+ max?: number;
307
+ default_value?: unknown;
308
+ /** Standard metadata: the FACS action unit, ARKit blendshape, and tier. */
309
+ meta?: {
310
+ au?: number;
311
+ arkit?: string;
312
+ tier?: string;
313
+ };
314
+ }
315
+ /**
316
+ * A profile the face declares it speaks: a set of paths and their types — the
317
+ * vocabulary its graphs are authored against.
318
+ *
319
+ * A profile is names and types, not a graph, so it sits beside `graphs` rather
320
+ * than inside it. Declaring it on the asset is what lets a reader know which
321
+ * vocabulary to hold the face to.
322
+ */
323
+ interface VizijBundleProfile {
324
+ id: string;
325
+ version: string;
326
+ title?: string;
327
+ description?: string;
328
+ keys: VizijProfileKey[];
329
+ }
291
330
  interface VizijBundleGraphEntry {
292
331
  id: VizijGraphId;
293
332
  kind: VizijBundleGraphKind;
@@ -357,6 +396,19 @@ interface VizijBundleAnimationEntry {
357
396
  [key: string]: unknown;
358
397
  };
359
398
  }
399
+ type VizijStarredKind = "driver" | "pose";
400
+ /**
401
+ * A starred reference collected into the "Starred" control surface. Points at a
402
+ * real driver (standard-input id) or pose (pose id) by stable id so both the
403
+ * source panel and the Starred panel render the same underlying object.
404
+ */
405
+ interface VizijStarredItem {
406
+ kind: VizijStarredKind;
407
+ id: string;
408
+ }
409
+ interface VizijBundleStarredSection {
410
+ items: VizijStarredItem[];
411
+ }
360
412
  interface VizijSpeechConfig {
361
413
  /** TTS voice name (e.g., "Ruth") */
362
414
  voice?: string;
@@ -385,8 +437,22 @@ interface VizijBundleExtension {
385
437
  version: VizijBundleVersion;
386
438
  exportedAt?: string;
387
439
  graphs?: VizijBundleGraphEntry[];
440
+ /** The profiles this face declares it speaks (see {@link VizijBundleProfile}). */
441
+ profiles?: VizijBundleProfile[];
388
442
  poses?: VizijBundlePoseSection | null;
389
443
  animations?: VizijBundleAnimationEntry[];
444
+ /**
445
+ * Scene-level view transform this face should be rendered with. Travels with
446
+ * the asset so a face authored in Blender (which typically wants `"agx"`)
447
+ * renders consistently wherever it is loaded. Omitted/unknown falls back to
448
+ * the renderer default (`"none"` — web match).
449
+ */
450
+ toneMapping?: ToneMappingMode;
451
+ /**
452
+ * Designer-curated set of starred drivers/poses (the "Starred" control
453
+ * surface). References real functionality by stable id.
454
+ */
455
+ starred?: VizijBundleStarredSection | null;
390
456
  /**
391
457
  * Bundle-level metadata. May include `speechConfig: VizijSpeechConfig`
392
458
  * for configuring the STT/LLM/TTS speech pipeline.
@@ -437,8 +503,59 @@ interface VizijAnimationClipData {
437
503
  tracks: VizijAnimationTrackData[];
438
504
  }
439
505
 
506
+ interface VizijProps {
507
+ style?: CSSProperties;
508
+ className?: string;
509
+ rootId: string;
510
+ namespace?: string;
511
+ showSafeArea?: boolean;
512
+ showSelectionGlow?: boolean;
513
+ /**
514
+ * View-transform applied by the WebGL renderer. Defaults to `"none"`
515
+ * (no tone mapping — matches plain web/canvas rendering). Faces authored in
516
+ * Blender typically want `"agx"` to compress over-driven emissive. Applied
517
+ * reactively, so it can be changed at runtime without remounting the canvas.
518
+ */
519
+ toneMapping?: ToneMappingMode;
520
+ onPointerMissed?: ComponentProps<typeof Canvas>["onPointerMissed"];
521
+ }
522
+ /**
523
+ * Renders the Vizij component.
524
+ *
525
+ * @param style - The style object for the Vizij component.
526
+ *
527
+ * @param className - The CSS class name for the Vizij component
528
+ *
529
+ * @param rootId - The root identifier for the Vizij component.
530
+ *
531
+ * @param namespace - The namespace for the Vizij component
532
+ *
533
+ * @param showSafeArea - Whether to show the safe area.
534
+ *
535
+ * @returns The rendered ReactNode.
536
+ */
537
+ declare function Vizij({ style, className, rootId, namespace, showSafeArea, showSelectionGlow, toneMapping, onPointerMissed, }: VizijProps): ReactNode;
538
+ interface InnerVizijProps {
539
+ rootId: string;
540
+ namespace: string;
541
+ container?: {
542
+ width: number;
543
+ height: number;
544
+ resolution: number;
545
+ };
546
+ showSafeArea?: boolean;
547
+ showSelectionGlow?: boolean;
548
+ toneMapping?: ToneMappingMode;
549
+ }
550
+ declare function InnerVizij({ rootId, namespace, container, showSafeArea, showSelectionGlow, toneMapping, }: InnerVizijProps): react_jsx_runtime.JSX.Element;
551
+
440
552
  interface VizijData {
441
553
  world: World;
554
+ /**
555
+ * The mounted three.js object of each world element, by element id and then
556
+ * namespace. It lives beside `world` so that the world stays plain data.
557
+ */
558
+ refs: Record<string, Record<string, RefObject<Group$1 | Mesh | null>>>;
442
559
  animatables: Record<string, AnimatableValue>;
443
560
  values: Map<string, RawValue | undefined>;
444
561
  renderHit: boolean;
@@ -481,7 +598,7 @@ interface VizijActions {
481
598
  setChildren: (id: string, children: string[]) => void;
482
599
  setGeometry: (id: string, geometry: THREE.BufferGeometry) => void;
483
600
  setMaterial: (id: string, material: string) => void;
484
- setReference: (id: string, namespace: string, object: RefObject<Group$1 | Mesh>) => void;
601
+ setReference: (id: string, namespace: string, object: RefObject<Group$1 | Mesh | null>) => void;
485
602
  createGroup: (root: boolean) => void;
486
603
  createAnimatable: (elementId: string, featureName: string, value: Partial<AnimatableValue>) => void;
487
604
  createStatic: (elementId: string, featureName: string, value: RawValue) => void;
@@ -491,6 +608,7 @@ type VizijStoreGetter = () => VizijData & VizijActions;
491
608
 
492
609
  declare const VizijSlice: (set: VizijStoreSetter, get: VizijStoreGetter) => {
493
610
  world: {};
611
+ refs: {};
494
612
  animatables: {};
495
613
  values: Map<any, any>;
496
614
  renderHit: boolean;
@@ -534,7 +652,7 @@ declare const VizijSlice: (set: VizijStoreSetter, get: VizijStoreGetter) => {
534
652
  setVizij: (scene: World, animatables: Record<string, AnimatableValue>) => void;
535
653
  addWorldElements(world: World, animatables: Record<string, AnimatableValue>, replace?: boolean): void;
536
654
  setPreferences: (preferences: Partial<VizijData["preferences"]>) => void;
537
- setReference: (id: string, namespace: string, ref: RefObject<Group$1 | Mesh>) => void;
655
+ setReference: (id: string, namespace: string, ref: RefObject<Group$1 | Mesh | null>) => void;
538
656
  };
539
657
  declare const useDefaultVizijStore: zustand.UseBoundStore<Omit<zustand.StoreApi<VizijData & VizijActions>, "subscribe"> & {
540
658
  subscribe: {
@@ -601,15 +719,14 @@ declare class EmptyModelError extends Error {
601
719
  }
602
720
  type ParserJsonFallbackSource = {
603
721
  url?: string;
604
- blob?: Blob;
605
722
  arrayBuffer?: ArrayBuffer;
606
723
  };
607
724
  declare function parseGlbJsonChunk(buffer: ArrayBuffer): unknown | undefined;
608
- declare function loadGLTF(url: string, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
725
+ declare function loadGLTF(url: string, aggressiveImport?: boolean, rootBounds?: {
609
726
  center: RawVector2;
610
727
  size: RawVector2;
611
728
  }): Promise<[World, Record<string, AnimatableValue>, VizijAnimationClipData[]]>;
612
- declare function loadGLTFFromBlob(blob: Blob, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
729
+ declare function loadGLTFFromBlob(blob: Blob, aggressiveImport?: boolean, rootBounds?: {
613
730
  center: RawVector2;
614
731
  size: RawVector2;
615
732
  }): Promise<[World, Record<string, AnimatableValue>, VizijAnimationClipData[]]>;
@@ -620,34 +737,121 @@ type LoadedVizijAsset = {
620
737
  animations: VizijAnimationClipData[];
621
738
  scene: Group$1;
622
739
  };
623
- declare function loadGLTFWithBundle(url: string, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
740
+ declare function loadGLTFWithBundle(url: string, aggressiveImport?: boolean, rootBounds?: {
624
741
  center: RawVector2;
625
742
  size: RawVector2;
626
743
  }, parserJsonFallback?: ParserJsonFallbackSource): Promise<LoadedVizijAsset>;
627
- declare function loadGLTFFromBlobWithBundle(blob: Blob, namespaces: string[], aggressiveImport?: boolean, rootBounds?: {
744
+ /**
745
+ * Load a GLB or self-contained glTF from `blob`.
746
+ *
747
+ * The bytes are read into memory and parsed there; nothing is fetched, so this
748
+ * runs the same in a browser and in Node.
749
+ */
750
+ declare function loadGLTFFromBlobWithBundle(blob: Blob, aggressiveImport?: boolean, rootBounds?: {
628
751
  center: RawVector2;
629
752
  size: RawVector2;
630
753
  }): Promise<LoadedVizijAsset>;
631
754
 
632
- /**
633
- * Loads a GLTF model from a Blob and returns the Three.js scene containing the model.
634
- *
635
- * @param blob - The Blob containing the GLTF data.
636
- * @returns A Promise that resolves with the THREE.Scene containing the loaded model.
637
- */
638
- declare const loadGltfFromBlob: (blob: Blob, namespaces: string[]) => Promise<[World, Record<string, AnimatableValue>]>;
755
+ declare function deriveRootBounds(group: Group$1): {
756
+ center: RawVector2;
757
+ size: RawVector2;
758
+ } | null;
639
759
 
640
760
  type ExportSceneOptions = {
641
- fileName?: string;
761
+ /** Written into the GLB as the `VIZIJ_bundle` extension of the root node. */
642
762
  bundle?: VizijBundleExtension | null;
763
+ /** Baked clips, written as glTF animations. */
643
764
  animations?: AnimationClip[];
644
- binary?: boolean;
645
- onError?: (error: Error) => void;
646
- onComplete?: () => void;
647
765
  };
648
- declare function exportScene(data: Group$1, fileNameOrOptions?: string | ExportSceneOptions): void;
766
+ /**
767
+ * Rewrite a GLB's JSON chunk in place.
768
+ *
769
+ * `mutate` returns whether it changed anything; when it did not, the original
770
+ * buffer is handed back untouched rather than re-encoded. Any malformed input
771
+ * — wrong magic, missing JSON chunk, unparseable payload — returns the buffer
772
+ * as-is, since a patch is never worth corrupting an export over.
773
+ *
774
+ * Exists so a caller can amend an already-exported GLB instead of exporting
775
+ * twice: `GLTFExporter` is the expensive part, and running it again to change
776
+ * a few bytes of metadata froze the UI thread on real assets.
777
+ */
778
+ declare function patchGlbJson(buffer: ArrayBuffer, mutate: (json: Record<string, unknown>) => boolean): ArrayBuffer;
779
+ /**
780
+ * Merge `metadata` into the `VIZIJ_bundle` extension already written into
781
+ * `glb`, wherever it landed — the exporter attaches it to a node, but a
782
+ * root-level extension is read back too, so both are handled.
783
+ */
784
+ declare function patchVizijBundleMetadata(glb: ArrayBuffer, metadata: Record<string, unknown>): ArrayBuffer;
785
+ /**
786
+ * Encode `root` and its descendants as a GLB.
787
+ *
788
+ * Every node is written with its local translation, rotation and scale, every
789
+ * mesh with its morph weights, and each object's `userData` as glTF
790
+ * extensions and extras, which is where `RobotData` travels. A root that is
791
+ * not a `Scene` is cloned into one, so the caller's graph is left as it was.
792
+ *
793
+ * Only textures need a DOM; a scene without images encodes in Node.
794
+ */
795
+ declare function exportScene(root: THREE.Object3D, options?: ExportSceneOptions): Promise<ArrayBuffer>;
796
+
797
+ /**
798
+ * Build the three.js graph of the world element `rootId` and its descendants,
799
+ * for `exportScene`.
800
+ *
801
+ * Each group becomes a `THREE.Group` and each shape a `THREE.Mesh` carrying a
802
+ * copy of its geometry and a material of its kind, as the renderer mounts
803
+ * them, with the element's `RobotData` in `userData`. No renderer is involved,
804
+ * so this runs in Node.
805
+ *
806
+ * Every feature takes its authored value: a static feature its value, an
807
+ * animated one its animatable's `default` in `animatables`. Node translation,
808
+ * rotation and scale, morph weights and material factors in the exported file
809
+ * are therefore the same numbers as the `RobotData` defaults, never a runtime
810
+ * pose. A caller that wants other values in the file passes them as the
811
+ * defaults of `animatables`.
812
+ *
813
+ * Throws when an element is missing from `world`, when an animated feature
814
+ * names an animatable missing from `animatables`, and for ellipse and
815
+ * rectangle elements, which have no exported form.
816
+ */
817
+ declare function buildSceneGraph(world: World, animatables: Record<string, AnimatableValue>, rootId: string): THREE.Object3D;
649
818
 
650
819
  declare function extractVizijBundle(object: Object3D, parserJson?: unknown): VizijBundleExtension | null;
651
820
  declare function applyVizijBundle(object: Object3D, bundle: VizijBundleExtension | null): () => void;
652
821
 
653
- export { type AnimatedFeature, type Ellipse, type EllipseFeature, EmptyModelError, type ExportSceneOptions, type Feature, type Group, type GroupFeature, InnerVizij, type InnerVizijProps, type LoadedVizijAsset, type Rectangle, type RectangleFeature, type RenderableBase, type RenderableFeature, type Selection, type Shape, type ShapeFeature, ShapeMaterial, type StaticFeature, type Stored, type StoredAnimatedFeature, type StoredEllipse, type StoredFeatures, type StoredGroup, type StoredRectangle, type StoredRenderable, type StoredShape, Vizij, type VizijActions, type VizijAnimationClipData, type VizijAnimationId, type VizijAnimationTrackData, type VizijBundleAnimationClip, type VizijBundleAnimationEntry, type VizijBundleAnimationKeyframe, type VizijBundleAnimationTrack, type VizijBundleExtension, type VizijBundleGraphEntry, type VizijBundleGraphKind, type VizijBundleGraphMetadata, type VizijBundlePoseSection, type VizijBundleVersion, VizijContext, type VizijData, type VizijGraphId, type VizijPoseDefinition, type VizijPoseId, type VizijPoseRigConfig, type VizijProps, VizijSlice, type VizijSpeechConfig, type VizijStore, type VizijStoreGetter, type VizijStoreSetter, type World, applyVizijBundle, createVizijStore, exportScene, extractVizijBundle, loadGLTF, loadGLTFFromBlob, loadGLTFFromBlobWithBundle, loadGLTFWithBundle, loadGltfFromBlob, parseGlbJsonChunk, useDefaultVizijStore, useFeatures, useVizijStore, useVizijStoreGetter, useVizijStoreSetter, useVizijStoreSubscription };
822
+ /**
823
+ * Canonical morph-target key derivation.
824
+ *
825
+ * Morph feature keys are part of the animation binding contract: a propsrig
826
+ * input path is built from `(elementName, featureKey, component)`, and for a
827
+ * morph the feature key is derived from the morph's name. Import (building
828
+ * animatables from geometry) and animation channel resolution must therefore
829
+ * agree exactly, so both use the helpers here rather than re-deriving the rule.
830
+ *
831
+ * Note the slug is normalized twice on the path route: keys produced here are
832
+ * later passed through `normalizeStandardRigGroup` when the propsrig path is
833
+ * built, which independently lowercases and collapses separators. Casing here
834
+ * is therefore not load-bearing for path resolution — but it *is* the stored
835
+ * feature key on the renderable (and in exported `RobotData`), so changing it
836
+ * still changes the shape of authored data. Unit tests pin the rule directly;
837
+ * the corpus test cannot see a casing change because of the double pass.
838
+ */
839
+ /**
840
+ * Allocates the feature key for a single morph target, deduplicating against
841
+ * keys already handed out for the same mesh.
842
+ *
843
+ * `used` is mutated: callers iterating a mesh's morph targets must share one
844
+ * set so collisions resolve deterministically in iteration order.
845
+ */
846
+ declare function sanitizeMorphKey(name: string, fallbackIndex: number, used: Set<string>): string;
847
+ /**
848
+ * Derives feature keys for a mesh's morph targets in order, applying the same
849
+ * dedupe sequence `sanitizeMorphKey` applies during import.
850
+ *
851
+ * Pass morph names in the mesh's own target order (glTF
852
+ * `meshes[].extras.targetNames`, or `morphTargetDictionary` insertion order)
853
+ * so the result matches the keys import produced for that mesh.
854
+ */
855
+ declare function deriveMorphFeatureKeys(names: ReadonlyArray<string>): string[];
856
+
857
+ export { type AnimatedFeature, DEFAULT_TONE_MAPPING_MODE, type Ellipse, type EllipseFeature, EmptyModelError, type ExportSceneOptions, type Feature, type Group, type GroupFeature, InnerVizij, type InnerVizijProps, type LoadedVizijAsset, type Rectangle, type RectangleFeature, type RenderableBase, type RenderableFeature, type Selection, type Shape, type ShapeFeature, ShapeMaterial, type StaticFeature, type Stored, type StoredAnimatedFeature, type StoredEllipse, type StoredFeatures, type StoredGroup, type StoredRectangle, type StoredRenderable, type StoredShape, TONE_MAPPING_LABELS, TONE_MAPPING_MODES, type ToneMappingMode, Vizij, type VizijActions, type VizijAnimationClipData, type VizijAnimationId, type VizijAnimationTrackData, type VizijBundleAnimationClip, type VizijBundleAnimationEntry, type VizijBundleAnimationKeyframe, type VizijBundleAnimationTrack, type VizijBundleExtension, type VizijBundleGraphEntry, type VizijBundleGraphKind, type VizijBundleGraphMetadata, type VizijBundlePoseSection, type VizijBundleProfile, type VizijBundleStarredSection, type VizijBundleVersion, VizijContext, type VizijData, type VizijGraphId, type VizijPoseDefinition, type VizijPoseId, type VizijPoseRigConfig, type VizijProfileKey, type VizijProps, VizijSlice, type VizijSpeechConfig, type VizijStarredItem, type VizijStarredKind, type VizijStore, type VizijStoreGetter, type VizijStoreSetter, type World, applyVizijBundle, buildSceneGraph, createVizijStore, deriveMorphFeatureKeys, deriveRootBounds, exportScene, extractVizijBundle, isToneMappingMode, loadGLTF, loadGLTFFromBlob, loadGLTFFromBlobWithBundle, loadGLTFWithBundle, normalizeToneMappingMode, parseGlbJsonChunk, patchGlbJson, patchVizijBundleMetadata, resolveToneMapping, sanitizeMorphKey, useDefaultVizijStore, useFeatures, useVizijStore, useVizijStoreGetter, useVizijStoreSetter, useVizijStoreSubscription };