mujoco-react 10.3.0 → 10.5.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/dist/onnx.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ import * as ort from 'onnxruntime-web';
2
+ import { bq as PolicyActionChunk } from './types-CViUme8D.js';
3
+ import 'react';
4
+ import '@react-three/fiber';
5
+ import 'three';
6
+
7
+ /**
8
+ * @license
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ *
11
+ * Optional ONNX Runtime Web helpers for browser policy demos.
12
+ *
13
+ * This entry point is exported as `mujoco-react/onnx` so the main package does
14
+ * not import or bundle `onnxruntime-web`.
15
+ */
16
+
17
+ type OnnxPolicyDtype = 'float32' | 'float64' | 'int32' | 'int64' | 'bool' | string;
18
+ interface OnnxPolicyTensorSpec {
19
+ name: string;
20
+ shape: number[];
21
+ dtype: OnnxPolicyDtype;
22
+ }
23
+ interface OnnxPolicyImageSpec {
24
+ width: number;
25
+ height: number;
26
+ channels?: number;
27
+ layout?: 'CHW' | 'HWC' | string;
28
+ range?: readonly [number, number];
29
+ }
30
+ interface OnnxPolicyManifest {
31
+ model: string;
32
+ variants?: Record<string, string>;
33
+ fps?: number;
34
+ joints?: string[];
35
+ cameras?: string[];
36
+ image?: OnnxPolicyImageSpec;
37
+ chunk_size?: number;
38
+ n_action_steps?: number;
39
+ inputs: OnnxPolicyTensorSpec[];
40
+ output: OnnxPolicyTensorSpec & {
41
+ units?: string;
42
+ };
43
+ [key: string]: unknown;
44
+ }
45
+ interface LoadOnnxPolicyManifestResult<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest> {
46
+ manifest: TManifest;
47
+ manifestUrl: URL;
48
+ modelUrl: URL;
49
+ }
50
+ interface CreateOnnxPolicySessionOptions<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest> {
51
+ manifestUrl: string | URL;
52
+ variant?: string;
53
+ runtime: typeof ort;
54
+ sessionOptions?: ort.InferenceSession.SessionOptions;
55
+ fetcher?: typeof fetch;
56
+ readManifest?: (response: Response) => Promise<TManifest>;
57
+ }
58
+ interface OnnxPolicySession<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest> extends LoadOnnxPolicyManifestResult<TManifest> {
59
+ session: ort.InferenceSession;
60
+ }
61
+ declare function loadOnnxPolicyManifest<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest>(manifestUrlInput: string | URL, options?: Pick<CreateOnnxPolicySessionOptions<TManifest>, 'variant' | 'fetcher' | 'readManifest'>): Promise<LoadOnnxPolicyManifestResult<TManifest>>;
62
+ declare function createOnnxPolicySession<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest>(options: CreateOnnxPolicySessionOptions<TManifest>): Promise<OnnxPolicySession<TManifest>>;
63
+ declare function onnxTensorToPolicyActionChunk(tensor: ort.Tensor, actionSize?: number, maxActions?: number): PolicyActionChunk;
64
+
65
+ export { type CreateOnnxPolicySessionOptions, type LoadOnnxPolicyManifestResult, type OnnxPolicyDtype, type OnnxPolicyImageSpec, type OnnxPolicyManifest, type OnnxPolicySession, type OnnxPolicyTensorSpec, createOnnxPolicySession, loadOnnxPolicyManifest, onnxTensorToPolicyActionChunk };
package/dist/onnx.js ADDED
@@ -0,0 +1,58 @@
1
+ // src/onnx.ts
2
+ function asUrl(value, base = globalThis.location?.href) {
3
+ return value instanceof URL ? value : new URL(value, base);
4
+ }
5
+ function resolveModelPath(manifest, variant) {
6
+ if (variant && manifest.variants?.[variant]) return manifest.variants[variant];
7
+ return manifest.model;
8
+ }
9
+ async function loadOnnxPolicyManifest(manifestUrlInput, options = {}) {
10
+ const fetcher = options.fetcher ?? fetch;
11
+ const manifestUrl = asUrl(manifestUrlInput);
12
+ const response = await fetcher(manifestUrl);
13
+ if (!response.ok) {
14
+ throw new Error(`Unable to load ONNX policy manifest from ${manifestUrl.href} (${response.status}).`);
15
+ }
16
+ const manifest = options.readManifest ? await options.readManifest(response) : await response.json();
17
+ const modelPath = resolveModelPath(manifest, options.variant);
18
+ const modelUrl = asUrl(modelPath, manifestUrl.href);
19
+ return { manifest, manifestUrl, modelUrl };
20
+ }
21
+ async function createOnnxPolicySession(options) {
22
+ const fetcher = options.fetcher ?? fetch;
23
+ const resolved = await loadOnnxPolicyManifest(options.manifestUrl, options);
24
+ const response = await fetcher(resolved.modelUrl);
25
+ if (!response.ok) {
26
+ throw new Error(`Unable to load ONNX policy model from ${resolved.modelUrl.href} (${response.status}).`);
27
+ }
28
+ const modelBytes = await response.arrayBuffer();
29
+ const session = await options.runtime.InferenceSession.create(modelBytes, options.sessionOptions);
30
+ return {
31
+ ...resolved,
32
+ session
33
+ };
34
+ }
35
+ function onnxTensorToPolicyActionChunk(tensor, actionSize = tensor.dims.at(-1) ?? 1, maxActions) {
36
+ const rawData = Array.from(tensor.data, (value) => Number(value));
37
+ const actionCount = Math.floor(rawData.length / actionSize);
38
+ const cappedActionCount = maxActions === void 0 ? actionCount : Math.max(0, Math.min(actionCount, Math.floor(maxActions)));
39
+ const actions = [];
40
+ for (let actionIndex = 0; actionIndex < cappedActionCount; actionIndex += 1) {
41
+ const start = actionIndex * actionSize;
42
+ actions.push(rawData.slice(start, start + actionSize));
43
+ }
44
+ return actions;
45
+ }
46
+ /**
47
+ * @license
48
+ * SPDX-License-Identifier: Apache-2.0
49
+ *
50
+ * Optional ONNX Runtime Web helpers for browser policy demos.
51
+ *
52
+ * This entry point is exported as `mujoco-react/onnx` so the main package does
53
+ * not import or bundle `onnxruntime-web`.
54
+ */
55
+
56
+ export { createOnnxPolicySession, loadOnnxPolicyManifest, onnxTensorToPolicyActionChunk };
57
+ //# sourceMappingURL=onnx.js.map
58
+ //# sourceMappingURL=onnx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/onnx.ts"],"names":[],"mappings":"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","file":"onnx.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n *\n * Optional ONNX Runtime Web helpers for browser policy demos.\n *\n * This entry point is exported as `mujoco-react/onnx` so the main package does\n * not import or bundle `onnxruntime-web`.\n */\n\nimport type * as ort from 'onnxruntime-web';\nimport type { PolicyActionChunk } from './types';\n\nexport type OnnxPolicyDtype = 'float32' | 'float64' | 'int32' | 'int64' | 'bool' | string;\n\nexport interface OnnxPolicyTensorSpec {\n name: string;\n shape: number[];\n dtype: OnnxPolicyDtype;\n}\n\nexport interface OnnxPolicyImageSpec {\n width: number;\n height: number;\n channels?: number;\n layout?: 'CHW' | 'HWC' | string;\n range?: readonly [number, number];\n}\n\nexport interface OnnxPolicyManifest {\n model: string;\n variants?: Record<string, string>;\n fps?: number;\n joints?: string[];\n cameras?: string[];\n image?: OnnxPolicyImageSpec;\n chunk_size?: number;\n n_action_steps?: number;\n inputs: OnnxPolicyTensorSpec[];\n output: OnnxPolicyTensorSpec & {\n units?: string;\n };\n [key: string]: unknown;\n}\n\nexport interface LoadOnnxPolicyManifestResult<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest> {\n manifest: TManifest;\n manifestUrl: URL;\n modelUrl: URL;\n}\n\nexport interface CreateOnnxPolicySessionOptions<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest> {\n manifestUrl: string | URL;\n variant?: string;\n runtime: typeof ort;\n sessionOptions?: ort.InferenceSession.SessionOptions;\n fetcher?: typeof fetch;\n readManifest?: (response: Response) => Promise<TManifest>;\n}\n\nexport interface OnnxPolicySession<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest>\n extends LoadOnnxPolicyManifestResult<TManifest> {\n session: ort.InferenceSession;\n}\n\nfunction asUrl(value: string | URL, base = globalThis.location?.href) {\n return value instanceof URL ? value : new URL(value, base);\n}\n\nfunction resolveModelPath(manifest: OnnxPolicyManifest, variant: string | undefined) {\n if (variant && manifest.variants?.[variant]) return manifest.variants[variant];\n return manifest.model;\n}\n\nexport async function loadOnnxPolicyManifest<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest>(\n manifestUrlInput: string | URL,\n options: Pick<CreateOnnxPolicySessionOptions<TManifest>, 'variant' | 'fetcher' | 'readManifest'> = {}\n): Promise<LoadOnnxPolicyManifestResult<TManifest>> {\n const fetcher = options.fetcher ?? fetch;\n const manifestUrl = asUrl(manifestUrlInput);\n const response = await fetcher(manifestUrl);\n if (!response.ok) {\n throw new Error(`Unable to load ONNX policy manifest from ${manifestUrl.href} (${response.status}).`);\n }\n const manifest = options.readManifest\n ? await options.readManifest(response)\n : await response.json() as TManifest;\n const modelPath = resolveModelPath(manifest, options.variant);\n const modelUrl = asUrl(modelPath, manifestUrl.href);\n return { manifest, manifestUrl, modelUrl };\n}\n\nexport async function createOnnxPolicySession<TManifest extends OnnxPolicyManifest = OnnxPolicyManifest>(\n options: CreateOnnxPolicySessionOptions<TManifest>\n): Promise<OnnxPolicySession<TManifest>> {\n const fetcher = options.fetcher ?? fetch;\n const resolved = await loadOnnxPolicyManifest(options.manifestUrl, options);\n const response = await fetcher(resolved.modelUrl);\n if (!response.ok) {\n throw new Error(`Unable to load ONNX policy model from ${resolved.modelUrl.href} (${response.status}).`);\n }\n const modelBytes = await response.arrayBuffer();\n const session = await options.runtime.InferenceSession.create(modelBytes, options.sessionOptions);\n return {\n ...resolved,\n session,\n };\n}\n\nexport function onnxTensorToPolicyActionChunk(\n tensor: ort.Tensor,\n actionSize = tensor.dims.at(-1) ?? 1,\n maxActions?: number\n): PolicyActionChunk {\n const rawData = Array.from(tensor.data as ArrayLike<number>, (value) => Number(value));\n const actionCount = Math.floor(rawData.length / actionSize);\n const cappedActionCount = maxActions === undefined\n ? actionCount\n : Math.max(0, Math.min(actionCount, Math.floor(maxActions)));\n const actions: number[][] = [];\n for (let actionIndex = 0; actionIndex < cappedActionCount; actionIndex += 1) {\n const start = actionIndex * actionSize;\n actions.push(rawData.slice(start, start + actionSize));\n }\n return actions;\n}\n"]}
package/dist/spark.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _sparkjsdev_spark from '@sparkjsdev/spark';
3
- import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-BOhNDICK.js';
3
+ import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-CViUme8D.js';
4
4
  import 'react';
5
5
  import '@react-three/fiber';
6
6
  import 'three';
package/dist/spark.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-6AZEFI6A.js';
1
+ import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-KHZ5U36J.js';
2
2
  import { useThree } from '@react-three/fiber';
3
3
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import * as THREE from 'three';
@@ -2,6 +2,123 @@ import React__default, { ReactNode } from 'react';
2
2
  import { CanvasProps, ThreeElements } from '@react-three/fiber';
3
3
  import * as THREE from 'three';
4
4
 
5
+ /**
6
+ * @license
7
+ * SPDX-License-Identifier: Apache-2.0
8
+ *
9
+ * Helpers for turning browser camera captures into policy image tensors.
10
+ */
11
+ type PolicyImageTensorLayout = 'CHW' | 'HWC';
12
+ type PolicyImageTensorRange = readonly [number, number];
13
+ /**
14
+ * Row order of a raw pixel buffer. WebGL `readRenderTargetPixels` returns rows
15
+ * bottom-to-top (`'bottom-left'`); `ImageData` is top-to-bottom (`'top-left'`).
16
+ */
17
+ type PolicyImageTensorSourceOrigin = 'top-left' | 'bottom-left';
18
+ interface PolicyImageTensorOptions {
19
+ width: number;
20
+ height: number;
21
+ channels?: 3 | 4;
22
+ layout?: PolicyImageTensorLayout;
23
+ range?: PolicyImageTensorRange;
24
+ }
25
+ interface PolicyImageTensorPixelOptions extends PolicyImageTensorOptions {
26
+ /** Row order of the source buffer. Defaults to `'top-left'`. */
27
+ sourceOrigin?: PolicyImageTensorSourceOrigin;
28
+ /** Mirror horizontally while reading. */
29
+ flipX?: boolean;
30
+ }
31
+ interface PolicyImageTensorResult {
32
+ data: Float32Array;
33
+ shape: [number, number, number];
34
+ width: number;
35
+ height: number;
36
+ channels: 3 | 4;
37
+ layout: PolicyImageTensorLayout;
38
+ range: PolicyImageTensorRange;
39
+ }
40
+ /**
41
+ * Convert a raw RGBA pixel buffer (4 bytes per pixel) directly into a policy
42
+ * image tensor. This is the fast path that skips canvas encoding entirely —
43
+ * feed it the `Uint8Array` returned by `readRenderTargetPixels` (which is
44
+ * bottom-left origin, so pass `sourceOrigin: 'bottom-left'`).
45
+ */
46
+ declare function pixelsToPolicyImageTensor(pixels: Uint8Array | Uint8ClampedArray, options: PolicyImageTensorPixelOptions): PolicyImageTensorResult;
47
+ declare function imageDataToPolicyImageTensor(imageData: ImageData, options: PolicyImageTensorOptions): PolicyImageTensorResult;
48
+ declare function dataUrlToPolicyImageTensor(dataUrl: string, options: PolicyImageTensorOptions): Promise<PolicyImageTensorResult>;
49
+
50
+ /**
51
+ * @license
52
+ * SPDX-License-Identifier: Apache-2.0
53
+ *
54
+ * Offscreen camera-frame capture for R3F/MuJoCo scenes.
55
+ */
56
+
57
+ /** Options for capturing a camera frame straight into a policy image tensor. */
58
+ type CameraFrameCaptureTensorOptions = CameraFrameCaptureOptions & Pick<PolicyImageTensorOptions, 'channels' | 'layout' | 'range'>;
59
+ interface CameraFramePixelsResult {
60
+ /** Raw RGBA pixels, bottom-left origin (reused buffer — consume before the next capture). */
61
+ pixels: Uint8Array;
62
+ camera: THREE.Camera;
63
+ width: number;
64
+ height: number;
65
+ source: CameraFrameCaptureSource;
66
+ }
67
+ interface CameraFrameTensorResult extends PolicyImageTensorResult {
68
+ camera: THREE.Camera;
69
+ source: CameraFrameCaptureSource;
70
+ }
71
+ interface CameraFrameCaptureSession {
72
+ readonly width: number;
73
+ readonly height: number;
74
+ capture(options?: CameraFrameCaptureOptions): {
75
+ canvas: HTMLCanvasElement;
76
+ camera: THREE.Camera;
77
+ width: number;
78
+ height: number;
79
+ source: CameraFrameCaptureSource;
80
+ };
81
+ captureAsync(options?: CameraFrameCaptureOptions): Promise<{
82
+ canvas: HTMLCanvasElement;
83
+ camera: THREE.Camera;
84
+ width: number;
85
+ height: number;
86
+ source: CameraFrameCaptureSource;
87
+ }>;
88
+ captureDataUrl(options?: CameraFrameCaptureOptions): CameraFrameCaptureResult;
89
+ captureDataUrlAsync(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
90
+ captureBlob(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
91
+ /**
92
+ * Render and read raw RGBA pixels without any canvas/PNG round-trip. The
93
+ * returned buffer is reused between calls — copy or convert it before the
94
+ * next capture.
95
+ */
96
+ capturePixels(options?: CameraFrameCaptureOptions): CameraFramePixelsResult;
97
+ /** Render straight into a normalized policy image tensor (no canvas/PNG encode). */
98
+ captureTensor(options?: CameraFrameCaptureTensorOptions): CameraFrameTensorResult;
99
+ dispose(): void;
100
+ }
101
+ declare const CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCaptureRender";
102
+ declare const CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCapturePreRender";
103
+ declare const CAPTURE_EXCLUDE_KEY = "mujoco.capture.exclude";
104
+ declare function createCameraFrameCaptureSession(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): CameraFrameCaptureSession;
105
+ declare function renderCameraFrameToCanvas(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): {
106
+ canvas: HTMLCanvasElement;
107
+ camera: THREE.Camera;
108
+ width: number;
109
+ height: number;
110
+ source: CameraFrameCaptureSource;
111
+ };
112
+ declare function captureCameraFrame(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
113
+ declare function captureCameraFrameBlob(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
114
+ /**
115
+ * One-shot camera frame capture straight into a policy image tensor, skipping
116
+ * the canvas/PNG round-trip. For repeated captures (live inference, recording),
117
+ * create a session once with {@link createCameraFrameCaptureSession} and call
118
+ * `session.captureTensor()` so the render target and buffers are reused.
119
+ */
120
+ declare function captureCameraFrameTensor(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureTensorOptions): CameraFrameTensorResult;
121
+
5
122
  /**
6
123
  * @license
7
124
  * SPDX-License-Identifier: Apache-2.0
@@ -336,6 +453,10 @@ interface SceneObject {
336
453
  solref?: string;
337
454
  solimp?: string;
338
455
  condim?: number;
456
+ /** MuJoCo geom contact type bitmask. Defaults to 1 for generated objects. */
457
+ contype?: number;
458
+ /** MuJoCo geom contact affinity bitmask. Defaults to 1 for generated objects. */
459
+ conaffinity?: number;
339
460
  /** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */
340
461
  group?: number;
341
462
  }
@@ -392,6 +513,12 @@ interface IkConfig {
392
513
  * starting at index 0. Prefer inferred IK or `joints`/`actuators`.
393
514
  */
394
515
  numJoints?: number;
516
+ /**
517
+ * Optional solve-space joint limits in the same order as the resolved joints.
518
+ * Use this when MJCF limits are intentionally broad or when a setup/calibration
519
+ * tool should stay within a narrower envelope.
520
+ */
521
+ jointLimits?: ReadonlyArray<readonly [number, number] | null | undefined>;
395
522
  /** Custom IK solver. When omitted, uses built-in Damped Least-Squares solver. */
396
523
  ikSolveFn?: IKSolveFn;
397
524
  /** DLS damping. Default: 0.01. */
@@ -801,11 +928,27 @@ interface ObservationHandle {
801
928
  /** Read just the vector values for policy inference. */
802
929
  readValues(): Float32Array | Float64Array;
803
930
  }
931
+ interface DebugVirtualCamera {
932
+ name?: string;
933
+ position?: CameraFrameCaptureVector3;
934
+ lookAt?: CameraFrameCaptureVector3;
935
+ up?: CameraFrameCaptureVector3;
936
+ quaternion?: THREE.Quaternion | readonly [number, number, number, number];
937
+ fov?: number;
938
+ width?: number;
939
+ height?: number;
940
+ frustumDepth?: number;
941
+ markerScale?: number;
942
+ color?: THREE.ColorRepresentation;
943
+ aimColor?: THREE.ColorRepresentation;
944
+ }
804
945
  interface DebugProps {
805
946
  showGeoms?: boolean;
806
947
  showSites?: boolean;
807
948
  showJoints?: boolean;
808
949
  showCameras?: boolean;
950
+ /** Additional explicit virtual camera poses to visualize alongside MuJoCo XML cameras. */
951
+ virtualCameras?: readonly DebugVirtualCamera[];
809
952
  showContacts?: boolean;
810
953
  showCOM?: boolean;
811
954
  showInertia?: boolean;
@@ -1125,6 +1268,19 @@ interface MujocoSimAPI {
1125
1268
  captureFrameBlob(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureBlobResult>;
1126
1269
  captureCameraFrame(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
1127
1270
  captureCameraFrameBlob(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
1271
+ /** Capture a camera frame straight into a policy image tensor (no canvas/PNG encode). */
1272
+ captureCameraFrameTensor(options?: CameraFrameCaptureTensorOptions): CameraFrameTensorResult;
1273
+ /**
1274
+ * Create a reusable offscreen capture session bound to this scene. Reuse it
1275
+ * for live inference/recording so the render target and buffers persist
1276
+ * across frames; call `session.captureTensor()` / `capturePixels()` each step.
1277
+ */
1278
+ createCameraFrameCaptureSession(options?: CameraFrameCaptureOptions): CameraFrameCaptureSession;
1279
+ /**
1280
+ * Resolve a named MuJoCo camera/site/body into concrete capture options with
1281
+ * the current world pose. Useful for re-aiming a persistent session each step.
1282
+ */
1283
+ resolveCameraCaptureOptions(options?: CameraFrameCaptureOptions): CameraFrameCaptureOptions;
1128
1284
  recordCameraSequence(options: CameraFrameSequenceOptions): Promise<CameraFrameSequenceResult>;
1129
1285
  project2DTo3D(x: number, y: number, cameraPos: THREE.Vector3, lookAt: THREE.Vector3): {
1130
1286
  point: THREE.Vector3;
@@ -1482,4 +1638,4 @@ interface ArrayJointStateResult {
1482
1638
  velocity: React__default.RefObject<Float64Array>;
1483
1639
  }
1484
1640
 
1485
- export { type ScalarJointStateResult as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type VisualScenarioExecutionContext as E, type ScenarioLightingPreset as F, type SplatEnvironmentMetadataInput as G, type SplatEnvironmentMetadata as H, type IkConfig as I, type SplatSceneInput as J, type DebugProps as K, type GeomInfo as L, type MujocoContextValue as M, type ContactListenerProps as N, type ObservationConfig as O, type PhysicsStepCallback as P, type ActuatorInfo as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type Sites as U, type VisualScenarioEffectsProps as V, type SitePositionResult as W, type Sensors as X, type SensorHandle as Y, type SensorInfo as Z, type Joints as _, type MujocoCanvasProps as a, type Keyframes as a$, type ArrayJointStateResult as a0, type JointStateOptions as a1, type JointStateResult as a2, type Bodies as a3, type BodyStateResult as a4, type Geoms as a5, type Actuators as a6, type CtrlHandle as a7, type ContactInfo as a8, type KeyboardTeleopConfig as a9, type PolicyCameraFrameCaptureAPI as aA, type CameraFrameSequenceRecorderAPI as aB, type CameraFrameCaptureResult as aC, type CameraFrameCaptureBlobResult as aD, type ImagePointCoordinateSpace as aE, type ImagePointProjectionOptions as aF, type ImagePointProjectionResult as aG, type PolicyVector as aH, type BodyInfo as aI, type CameraFrameCaptureQuaternion as aJ, type CameraFrameCaptureVector3 as aK, type CameraFrameSequenceCameraSummary as aL, type CameraFrameSequenceFrame as aM, type CameraFrameSequenceSampleInput as aN, type CameraFrameSequenceStepInput as aO, type CameraInfo as aP, type ControlJointInfo as aQ, type FrameCaptureTarget as aR, type FrameCaptureTargetRef as aS, type IKSolveFn as aT, type IkGizmoDragInput as aU, type IkSolveInput as aV, type JointInfo as aW, type JointStateKind as aX, type KeyBinding as aY, type KeyboardIkTargetAction as aZ, type KeyboardIkTargetBinding as a_, type KeyboardIkTargetConfig as aa, type PolicyConfig as ab, type PolicyAPI as ac, type RemotePolicyConfig as ad, type RemotePolicyAPI as ae, type ObservationHandle as af, type ObservationOutput as ag, type TrajectoryInput as ah, type TrajectoryStateChangeInput as ai, type PlaybackState as aj, type TrajectoryFrame as ak, type FrameCaptureOptions as al, type FrameCaptureResult as am, type FrameCaptureBlobResult as an, type FrameCaptureAPI as ao, type CameraFrameCaptureOptions as ap, type CameraFrameCaptureAPI as aq, type Cameras as ar, type CameraFrameSequenceCamera as as, type CameraFrameCaptureSource as at, type CameraFrameSequenceOptions as au, type CameraFrameSequenceResult as av, type PolicyCameraFrameStream as aw, type PolicyCameraFrameCaptureOptions as ax, type PolicyCameraFrameCaptureResult as ay, type FrameCaptureStatus as az, type MujocoSimAPI as b, ModelActuators as b0, ModelBodies as b1, ModelCameras as b2, ModelGeoms as b3, ModelJoints as b4, ModelKeyframes as b5, type ModelOptions as b6, type ModelResource as b7, ModelResources as b8, ModelSensors as b9, type ScenarioMaterialConfig as bA, type SceneMarker as bB, type SceneObject as bC, type SensorResult as bD, type SiteInfo as bE, type SplatAssetConfig as bF, type SplatScenarioConfig as bG, type StateSnapshot as bH, type TrajectoryData as bI, type TrajectoryFrameCallbackInput as bJ, type VisualScenarioMaterialFilterInput as bK, type XmlPatch as bL, getContact as bM, registerModelResources as bN, ModelSites as ba, type Models as bb, type MujocoContact as bc, type MujocoContactArray as bd, type MujocoFrameCaptureOptions as be, type ObservationLayoutItem as bf, type PhysicsConfig as bg, type PhysicsStepInput as bh, type PolicyActionChunk as bi, type PolicyActionInput as bj, type PolicyInferenceInput as bk, type PolicyInferenceOutput as bl, type PolicyInferenceResult as bm, type PolicyObservationInput as bn, type RayHit as bo, type Register as bp, type RegisteredModelMap as bq, type RemotePolicyRequestInfo as br, type RemotePolicyRequestInput as bs, type RemotePolicyResponseInfo as bt, type RemotePolicyStatus as bu, type ResetCallbackInput as bv, type ResolvedScenarioCameraConfig as bw, type ResolvedScenarioMaterialConfig as bx, type ResourceSelector as by, type ScenarioCameraConfig as bz, type StepCallbackInput as c, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoRenderOptions as f, type MujocoModel as g, type MujocoData as h, type ControlGroupSelector as i, type ObservationResult as j, type IkContextValue as k, type IkGizmoProps as l, type SceneLightsProps as m, type ScenarioLightingProps as n, type SplatEnvironmentProps as o, type VisualScenarioConfig as p, type SplatRendererKind as q, type PairedSplatEnvironmentConfig as r, type SplatFormat as s, type SplatCollisionProxyConfig as t, type SplatEnvironmentReadiness as u, type SplatCollisionPrimitive as v, SplatEnvironmentReadinessStatus as w, type SplatSceneConfigInput as x, type SplatSceneConfigState as y, type VisualScenarioExecutionContextInput as z };
1641
+ export { type Joints as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type VisualScenarioExecutionContext as E, type ScenarioLightingPreset as F, type SplatEnvironmentMetadataInput as G, type SplatEnvironmentMetadata as H, type IkConfig as I, type SplatSceneInput as J, type CameraFrameCaptureOptions as K, type DebugProps as L, type MujocoContextValue as M, type GeomInfo as N, type ObservationConfig as O, type PhysicsStepCallback as P, type ContactListenerProps as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type ActuatorInfo as U, type VisualScenarioEffectsProps as V, type Sites as W, type SitePositionResult as X, type Sensors as Y, type SensorHandle as Z, type SensorInfo as _, type MujocoCanvasProps as a, type IKSolveFn as a$, type ScalarJointStateResult as a0, type ArrayJointStateResult as a1, type JointStateOptions as a2, type JointStateResult as a3, type Bodies as a4, type BodyStateResult as a5, type Geoms as a6, type Actuators as a7, type CtrlHandle as a8, type ContactInfo as a9, type PolicyCameraFrameCaptureAPI as aA, type CameraFrameCaptureTensorOptions as aB, type CameraFrameTensorResult as aC, type CameraFrameSequenceRecorderAPI as aD, type ImagePointCoordinateSpace as aE, type ImagePointProjectionOptions as aF, type ImagePointProjectionResult as aG, type PolicyVector as aH, type BodyInfo as aI, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY as aJ, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY as aK, CAPTURE_EXCLUDE_KEY as aL, type CameraFrameCaptureBlobResult as aM, type CameraFrameCaptureQuaternion as aN, type CameraFrameCaptureResult as aO, type CameraFrameCaptureSession as aP, type CameraFrameCaptureVector3 as aQ, type CameraFramePixelsResult as aR, type CameraFrameSequenceCameraSummary as aS, type CameraFrameSequenceFrame as aT, type CameraFrameSequenceSampleInput as aU, type CameraFrameSequenceStepInput as aV, type CameraInfo as aW, type ControlJointInfo as aX, type DebugVirtualCamera as aY, type FrameCaptureTarget as aZ, type FrameCaptureTargetRef as a_, type KeyboardTeleopConfig as aa, type KeyboardIkTargetConfig as ab, type PolicyConfig as ac, type PolicyAPI as ad, type RemotePolicyConfig as ae, type RemotePolicyAPI as af, type ObservationHandle as ag, type ObservationOutput as ah, type TrajectoryInput as ai, type TrajectoryStateChangeInput as aj, type PlaybackState as ak, type TrajectoryFrame as al, type FrameCaptureOptions as am, type FrameCaptureResult as an, type FrameCaptureBlobResult as ao, type FrameCaptureAPI as ap, type CameraFrameCaptureAPI as aq, type Cameras as ar, type CameraFrameSequenceCamera as as, type CameraFrameCaptureSource as at, type CameraFrameSequenceOptions as au, type CameraFrameSequenceResult as av, type PolicyCameraFrameStream as aw, type PolicyCameraFrameCaptureOptions as ax, type PolicyCameraFrameCaptureResult as ay, type FrameCaptureStatus as az, type MujocoSimAPI as b, captureCameraFrameBlob as b$, type IkGizmoDragInput as b0, type IkSolveInput as b1, type JointInfo as b2, type JointStateKind as b3, type KeyBinding as b4, type KeyboardIkTargetAction as b5, type KeyboardIkTargetBinding as b6, type Keyframes as b7, ModelActuators as b8, ModelBodies as b9, type PolicyInferenceResult as bA, type PolicyObservationInput as bB, type RayHit as bC, type Register as bD, type RegisteredModelMap as bE, type RemotePolicyRequestInfo as bF, type RemotePolicyRequestInput as bG, type RemotePolicyResponseInfo as bH, type RemotePolicyStatus as bI, type ResetCallbackInput as bJ, type ResolvedScenarioCameraConfig as bK, type ResolvedScenarioMaterialConfig as bL, type ResourceSelector as bM, type ScenarioCameraConfig as bN, type ScenarioMaterialConfig as bO, type SceneMarker as bP, type SceneObject as bQ, type SensorResult as bR, type SiteInfo as bS, type SplatAssetConfig as bT, type SplatScenarioConfig as bU, type StateSnapshot as bV, type TrajectoryData as bW, type TrajectoryFrameCallbackInput as bX, type VisualScenarioMaterialFilterInput as bY, type XmlPatch as bZ, captureCameraFrame as b_, ModelCameras as ba, ModelGeoms as bb, ModelJoints as bc, ModelKeyframes as bd, type ModelOptions as be, type ModelResource as bf, ModelResources as bg, ModelSensors as bh, ModelSites as bi, type Models as bj, type MujocoContact as bk, type MujocoContactArray as bl, type MujocoFrameCaptureOptions as bm, type ObservationLayoutItem as bn, type PhysicsConfig as bo, type PhysicsStepInput as bp, type PolicyActionChunk as bq, type PolicyActionInput as br, type PolicyImageTensorLayout as bs, type PolicyImageTensorOptions as bt, type PolicyImageTensorPixelOptions as bu, type PolicyImageTensorRange as bv, type PolicyImageTensorResult as bw, type PolicyImageTensorSourceOrigin as bx, type PolicyInferenceInput as by, type PolicyInferenceOutput as bz, type StepCallbackInput as c, captureCameraFrameTensor as c0, createCameraFrameCaptureSession as c1, dataUrlToPolicyImageTensor as c2, getContact as c3, imageDataToPolicyImageTensor as c4, pixelsToPolicyImageTensor as c5, registerModelResources as c6, renderCameraFrameToCanvas as c7, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoRenderOptions as f, type MujocoModel as g, type MujocoData as h, type ControlGroupSelector as i, type ObservationResult as j, type IkContextValue as k, type IkGizmoProps as l, type SceneLightsProps as m, type ScenarioLightingProps as n, type SplatEnvironmentProps as o, type VisualScenarioConfig as p, type SplatRendererKind as q, type PairedSplatEnvironmentConfig as r, type SplatFormat as s, type SplatCollisionProxyConfig as t, type SplatEnvironmentReadiness as u, type SplatCollisionPrimitive as v, SplatEnvironmentReadinessStatus as w, type SplatSceneConfigInput as x, type SplatSceneConfigState as y, type VisualScenarioExecutionContextInput as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "10.3.0",
3
+ "version": "10.5.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,13 @@
25
25
  "default": "./dist/spark.js"
26
26
  }
27
27
  },
28
- "./package.json": "./package.json"
28
+ "./package.json": "./package.json",
29
+ "./onnx": {
30
+ "import": {
31
+ "types": "./dist/onnx.d.ts",
32
+ "default": "./dist/onnx.js"
33
+ }
34
+ }
29
35
  },
30
36
  "bin": {
31
37
  "mujoco-react": "./bin/mujoco-react.mjs",
@@ -63,11 +69,15 @@
63
69
  "@react-three/fiber": ">=8",
64
70
  "@sparkjsdev/spark": ">=2.1.0",
65
71
  "react": ">=18",
66
- "three": ">=0.180.0"
72
+ "three": ">=0.180.0",
73
+ "onnxruntime-web": ">=1.20.0"
67
74
  },
68
75
  "peerDependenciesMeta": {
69
76
  "@sparkjsdev/spark": {
70
77
  "optional": true
78
+ },
79
+ "onnxruntime-web": {
80
+ "optional": true
71
81
  }
72
82
  },
73
83
  "dependencies": {
@@ -82,6 +92,7 @@
82
92
  "@types/node": "^25.9.1",
83
93
  "@types/react": "^19.0.0",
84
94
  "@types/three": "^0.181.0",
95
+ "onnxruntime-web": "^1.27.0",
85
96
  "react": "^19.2.0",
86
97
  "semantic-release": "^25.0.3",
87
98
  "three": "^0.181.0",
@@ -0,0 +1,245 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Live on-screen camera viewports for MuJoCo scenes. Each view renders the
6
+ * shared scene from a named MuJoCo camera/site/body into a `gl.scissor` region
7
+ * tracking a DOM element — no GPU readback, no PNG encoding.
8
+ *
9
+ * While at least one viewport is mounted the canvas switches from R3F's
10
+ * automatic render to a managed render loop (main scene full-frame, then each
11
+ * viewport). This is incompatible with `EffectComposer`/postprocessing or other
12
+ * custom render loops; use the offscreen capture APIs in those setups instead.
13
+ */
14
+
15
+ import {
16
+ createContext,
17
+ type CSSProperties,
18
+ type MutableRefObject,
19
+ type ReactNode,
20
+ type RefObject,
21
+ useCallback,
22
+ useContext,
23
+ useEffect,
24
+ useMemo,
25
+ useRef,
26
+ useState,
27
+ } from 'react';
28
+ import { useFrame, useThree } from '@react-three/fiber';
29
+ import type * as THREE from 'three';
30
+ import { useMujoco } from '../core/MujocoSimProvider';
31
+ import {
32
+ createCaptureCamera,
33
+ prepareCaptureCamera,
34
+ } from '../rendering/cameraFrameCapture';
35
+ import type {
36
+ Bodies,
37
+ Cameras,
38
+ CameraFrameCaptureOptions,
39
+ Sites,
40
+ } from '../types';
41
+
42
+ /** Camera selection + pose options for a live viewport. */
43
+ export type CameraViewportOptions = Pick<
44
+ CameraFrameCaptureOptions,
45
+ | 'camera'
46
+ | 'cameraName'
47
+ | 'siteName'
48
+ | 'bodyName'
49
+ | 'position'
50
+ | 'quaternion'
51
+ | 'lookAt'
52
+ | 'up'
53
+ | 'positionOffset'
54
+ | 'quaternionOffset'
55
+ | 'fov'
56
+ | 'near'
57
+ | 'far'
58
+ | 'projectionMatrix'
59
+ | 'mujocoCameraCompatibility'
60
+ >;
61
+
62
+ interface CameraViewportDescriptor {
63
+ getElement: () => HTMLElement | null;
64
+ getOptions: () => CameraViewportOptions;
65
+ camera: THREE.Camera | null;
66
+ }
67
+
68
+ interface CameraViewportRegistry {
69
+ register: (descriptor: CameraViewportDescriptor) => () => void;
70
+ }
71
+
72
+ const CameraViewportRegistryContext = createContext<CameraViewportRegistry | null>(null);
73
+
74
+ let nextViewportId = 0;
75
+
76
+ const VIEWPORT_RENDER_PRIORITY = 1;
77
+
78
+ function CameraViewportRenderer({
79
+ viewportsRef,
80
+ }: {
81
+ viewportsRef: MutableRefObject<Map<number, CameraViewportDescriptor>>;
82
+ }) {
83
+ const gl = useThree((state) => state.gl);
84
+ const scene = useThree((state) => state.scene);
85
+ const mainCamera = useThree((state) => state.camera);
86
+ const mujoco = useMujoco();
87
+
88
+ useFrame(() => {
89
+ const drawWidth = gl.domElement.width;
90
+ const drawHeight = gl.domElement.height;
91
+
92
+ // We own the render now: draw the main scene full-frame first.
93
+ gl.setScissorTest(false);
94
+ gl.setViewport(0, 0, drawWidth, drawHeight);
95
+ gl.render(scene, mainCamera);
96
+
97
+ const api = mujoco.api;
98
+ if (!api || viewportsRef.current.size === 0) return;
99
+
100
+ const dpr = gl.getPixelRatio();
101
+ const canvasRect = gl.domElement.getBoundingClientRect();
102
+
103
+ for (const descriptor of viewportsRef.current.values()) {
104
+ const element = descriptor.getElement();
105
+ if (!element) continue;
106
+
107
+ const rect = element.getBoundingClientRect();
108
+ const isOffscreen =
109
+ rect.bottom < canvasRect.top ||
110
+ rect.top > canvasRect.bottom ||
111
+ rect.right < canvasRect.left ||
112
+ rect.left > canvasRect.right;
113
+ if (isOffscreen) continue;
114
+
115
+ const width = Math.floor(rect.width * dpr);
116
+ const height = Math.floor(rect.height * dpr);
117
+ if (width <= 0 || height <= 0) continue;
118
+
119
+ const left = Math.floor((rect.left - canvasRect.left) * dpr);
120
+ const bottom = Math.floor((canvasRect.bottom - rect.bottom) * dpr);
121
+
122
+ let resolved: CameraFrameCaptureOptions;
123
+ try {
124
+ resolved = api.resolveCameraCaptureOptions(descriptor.getOptions());
125
+ } catch {
126
+ continue;
127
+ }
128
+
129
+ if (!descriptor.camera) {
130
+ descriptor.camera = createCaptureCamera(resolved, mainCamera, width, height);
131
+ } else {
132
+ prepareCaptureCamera(descriptor.camera, resolved, mainCamera, width, height);
133
+ }
134
+
135
+ gl.setViewport(left, bottom, width, height);
136
+ gl.setScissor(left, bottom, width, height);
137
+ gl.setScissorTest(true);
138
+ gl.render(scene, descriptor.camera);
139
+ }
140
+
141
+ gl.setScissorTest(false);
142
+ gl.setViewport(0, 0, drawWidth, drawHeight);
143
+ }, VIEWPORT_RENDER_PRIORITY);
144
+
145
+ return null;
146
+ }
147
+
148
+ /**
149
+ * Provides the live-viewport registry and mounts the managed render loop only
150
+ * while at least one viewport is active. Mounted internally by the MuJoCo
151
+ * provider; you do not need to add it yourself.
152
+ */
153
+ export function CameraViewportProvider({ children }: { children?: ReactNode }) {
154
+ const viewportsRef = useRef<Map<number, CameraViewportDescriptor>>(new Map());
155
+ const [count, setCount] = useState(0);
156
+
157
+ const register = useCallback((descriptor: CameraViewportDescriptor) => {
158
+ const id = nextViewportId++;
159
+ viewportsRef.current.set(id, descriptor);
160
+ setCount((value) => value + 1);
161
+ return () => {
162
+ viewportsRef.current.delete(id);
163
+ setCount((value) => value - 1);
164
+ };
165
+ }, []);
166
+
167
+ const value = useMemo<CameraViewportRegistry>(() => ({ register }), [register]);
168
+
169
+ return (
170
+ <CameraViewportRegistryContext.Provider value={value}>
171
+ {children}
172
+ {count > 0 && <CameraViewportRenderer viewportsRef={viewportsRef} />}
173
+ </CameraViewportRegistryContext.Provider>
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Render the live MuJoCo scene from a named camera into the region covered by
179
+ * `elementRef`'s DOM element. Call this inside `<MujocoCanvas>` with a ref to a
180
+ * DOM element you position anywhere (it does not need to be in the R3F tree).
181
+ */
182
+ export function useCameraViewport<T extends HTMLElement = HTMLElement>(
183
+ elementRef: RefObject<T | null>,
184
+ options: CameraViewportOptions
185
+ ) {
186
+ const registry = useContext(CameraViewportRegistryContext);
187
+ if (!registry) {
188
+ throw new Error('useCameraViewport must be used inside <MujocoCanvas>.');
189
+ }
190
+
191
+ const optionsRef = useRef(options);
192
+ optionsRef.current = options;
193
+
194
+ useEffect(() => {
195
+ const descriptor: CameraViewportDescriptor = {
196
+ getElement: () => elementRef.current,
197
+ getOptions: () => optionsRef.current,
198
+ camera: null,
199
+ };
200
+ return registry.register(descriptor);
201
+ }, [registry, elementRef]);
202
+ }
203
+
204
+ export interface CameraViewProps extends CameraViewportOptions {
205
+ className?: string;
206
+ style?: CSSProperties;
207
+ }
208
+
209
+ /**
210
+ * Drop-in live camera pane. Renders an absolutely-positioned overlay `<div>`
211
+ * over the canvas showing the selected MuJoCo camera. Position it with
212
+ * `style`/`className` (the canvas's parent should be positioned).
213
+ */
214
+ export function CameraView({ className, style, ...options }: CameraViewProps) {
215
+ const gl = useThree((state) => state.gl);
216
+ const elementRef = useRef<HTMLDivElement | null>(null);
217
+ if (!elementRef.current && typeof document !== 'undefined') {
218
+ elementRef.current = document.createElement('div');
219
+ }
220
+
221
+ useEffect(() => {
222
+ const element = elementRef.current;
223
+ const parent = gl.domElement.parentElement;
224
+ if (!element || !parent) return;
225
+ element.style.position = 'absolute';
226
+ element.style.overflow = 'hidden';
227
+ parent.appendChild(element);
228
+ return () => {
229
+ parent.removeChild(element);
230
+ };
231
+ }, [gl]);
232
+
233
+ useEffect(() => {
234
+ const element = elementRef.current;
235
+ if (!element) return;
236
+ element.className = className ?? '';
237
+ if (style) Object.assign(element.style, style);
238
+ }, [className, style]);
239
+
240
+ useCameraViewport(elementRef, options);
241
+
242
+ return null;
243
+ }
244
+
245
+ export type { Bodies, Cameras, Sites };