mujoco-react 10.4.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/README.md +73 -136
- package/dist/{chunk-FBXXXPLQ.js → chunk-KHZ5U36J.js} +157 -16
- package/dist/chunk-KHZ5U36J.js.map +1 -0
- package/dist/index.d.ts +179 -48
- package/dist/index.js +470 -17
- package/dist/index.js.map +1 -1
- package/dist/onnx.d.ts +65 -0
- package/dist/onnx.js +58 -0
- package/dist/onnx.js.map +1 -0
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-CdFZCYmy.d.ts → types-CViUme8D.d.ts} +141 -1
- package/package.json +14 -3
- package/src/components/CameraView.tsx +245 -0
- package/src/core/GenericIK.ts +16 -4
- package/src/core/MujocoSimProvider.tsx +37 -1
- package/src/core/SceneLoader.ts +3 -2
- package/src/hooks/useCameraStream.ts +115 -0
- package/src/hooks/useControlGroup.ts +0 -0
- package/src/hooks/useIkController.ts +3 -0
- package/src/hooks/usePolicyCameraTensors.ts +215 -0
- package/src/index.ts +44 -0
- package/src/onnx.ts +126 -0
- package/src/policyImageTensors.ts +150 -0
- package/src/rendering/cameraFrameCapture.ts +112 -15
- package/src/types.ts +28 -0
- package/dist/chunk-FBXXXPLQ.js.map +0 -1
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
|
package/dist/onnx.js.map
ADDED
|
@@ -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-
|
|
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-
|
|
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. */
|
|
@@ -1141,6 +1268,19 @@ interface MujocoSimAPI {
|
|
|
1141
1268
|
captureFrameBlob(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureBlobResult>;
|
|
1142
1269
|
captureCameraFrame(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
|
|
1143
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;
|
|
1144
1284
|
recordCameraSequence(options: CameraFrameSequenceOptions): Promise<CameraFrameSequenceResult>;
|
|
1145
1285
|
project2DTo3D(x: number, y: number, cameraPos: THREE.Vector3, lookAt: THREE.Vector3): {
|
|
1146
1286
|
point: THREE.Vector3;
|
|
@@ -1498,4 +1638,4 @@ interface ArrayJointStateResult {
|
|
|
1498
1638
|
velocity: React__default.RefObject<Float64Array>;
|
|
1499
1639
|
}
|
|
1500
1640
|
|
|
1501
|
-
export { type
|
|
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
|
+
"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 };
|