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/README.md +75 -137
- package/dist/{chunk-6AZEFI6A.js → chunk-KHZ5U36J.js} +157 -16
- package/dist/chunk-KHZ5U36J.js.map +1 -0
- package/dist/index.d.ts +180 -49
- package/dist/index.js +627 -19
- 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-BOhNDICK.d.ts → types-CViUme8D.d.ts} +157 -1
- package/package.json +14 -3
- package/src/components/CameraView.tsx +245 -0
- package/src/components/Debug.tsx +174 -3
- 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 +45 -0
- package/src/onnx.ts +126 -0
- package/src/policyImageTensors.ts +150 -0
- package/src/rendering/cameraFrameCapture.ts +112 -15
- package/src/types.ts +45 -0
- package/dist/chunk-6AZEFI6A.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img
|
|
2
|
+
<img width="941" height="598" alt="Screenshot 2026-06-24 at 5 44 13 PM" src="https://github.com/user-attachments/assets/09999f02-c093-473e-aaee-519fe2e4cdd4" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# mujoco-react
|
|
@@ -860,6 +860,7 @@ Visualization overlays:
|
|
|
860
860
|
| `showJoints` | `boolean?` | `false` | Joint axes |
|
|
861
861
|
| `showContacts` | `boolean?` | `false` | Contact force vectors |
|
|
862
862
|
| `showCameras` | `boolean?` | `false` | MuJoCo camera positions, frustums, and forward rays |
|
|
863
|
+
| `virtualCameras` | `DebugVirtualCamera[]?` | `[]` | Explicit virtual policy/offscreen render camera poses to draw alongside MuJoCo cameras |
|
|
863
864
|
| `showCOM` | `boolean?` | `false` | Center of mass markers |
|
|
864
865
|
| `showInertia` | `boolean?` | `false` | Inertia ellipsoids |
|
|
865
866
|
| `showTendons` | `boolean?` | `false` | Tendon paths |
|
|
@@ -868,7 +869,7 @@ Visualization overlays:
|
|
|
868
869
|
| `contactColor` | `string?` | `"#ff4444"` | Color for contact force arrows |
|
|
869
870
|
| `comColor` | `string?` | `"#ff0000"` | Color for COM markers |
|
|
870
871
|
|
|
871
|
-
Camera debug overlays use the live MuJoCo `cam_xpos` / `cam_xmat` frame, so the frustum matches mounted camera captures and follows parent body motion.
|
|
872
|
+
Camera debug overlays use the live MuJoCo `cam_xpos` / `cam_xmat` frame, so the frustum matches mounted camera captures and follows parent body motion. Use `virtualCameras` for synthetic fixed policy/offscreen render viewpoints that are not declared as MJCF `<camera>` elements. Debug camera overlays are excluded from camera captures.
|
|
872
873
|
|
|
873
874
|
### `<TendonRenderer />`
|
|
874
875
|
|
|
@@ -1094,29 +1095,20 @@ const video = useVideoRecorder({ fps: 30, mimeType: "video/webm" });
|
|
|
1094
1095
|
// video.start(), video.stop() -> returns Blob
|
|
1095
1096
|
```
|
|
1096
1097
|
|
|
1097
|
-
###
|
|
1098
|
+
### Camera Frames, Streams, and Tensors
|
|
1098
1099
|
|
|
1099
|
-
|
|
1100
|
+
A camera pose — the viewer camera, a MuJoCo `cameraName`/`siteName`/`bodyName`, or
|
|
1101
|
+
an explicit `position`+`lookAt` — can be turned into three outputs: a **snapshot**
|
|
1102
|
+
(PNG/JPEG), a **live on-screen stream**, or a **policy tensor**.
|
|
1100
1103
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
the canvas or want to capture a custom container.
|
|
1110
|
-
|
|
1111
|
-
Use `captureCameraFrame()` / `captureCameraFrameBlob()` when dataset generation
|
|
1112
|
-
needs an offscreen camera render at a stable resolution without moving the
|
|
1113
|
-
user's interactive viewport. Pass `cameraName`, `siteName`, or `bodyName` to
|
|
1114
|
-
record true MuJoCo-mounted camera frames; the returned image includes
|
|
1115
|
-
`source.kind` so dataset pipelines can reject fallback or synthetic fixed poses.
|
|
1116
|
-
For named MuJoCo cameras, set `mujocoCameraCompatibility` when you want the
|
|
1117
|
-
Three.js offscreen camera to inherit the MJCF camera's `resolution`, `fovy`,
|
|
1118
|
-
near/far clipping, and calibrated intrinsics when the WASM model exposes
|
|
1119
|
-
`cam_intrinsic` plus `cam_sensorsize`:
|
|
1104
|
+
**Snapshots.** `captureFrame()`/`captureFrameBlob()` grab the live canvas (or use
|
|
1105
|
+
`useFrameCapture()` / the standalone `captureFrame()` when you own the canvas).
|
|
1106
|
+
`captureCameraFrame()`/`captureCameraFrameBlob()` render a chosen camera offscreen
|
|
1107
|
+
at a stable resolution without moving the viewport; pass `cameraName`/`siteName`/
|
|
1108
|
+
`bodyName` for true mounted frames (`source.kind` lets dataset pipelines reject
|
|
1109
|
+
fallback/synthetic poses). Set `mujocoCameraCompatibility` to inherit a MJCF
|
|
1110
|
+
camera's `resolution`, `fovy`, clipping, and calibrated intrinsics; use
|
|
1111
|
+
`visualOverrides`/`renderIsolation` for canonical training captures.
|
|
1120
1112
|
|
|
1121
1113
|
```tsx
|
|
1122
1114
|
const frame = await apiRef.current?.captureCameraFrame({
|
|
@@ -1127,138 +1119,84 @@ const frame = await apiRef.current?.captureCameraFrame({
|
|
|
1127
1119
|
});
|
|
1128
1120
|
```
|
|
1129
1121
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
space, and `renderIsolation` to render with an independent offscreen
|
|
1135
|
-
`WebGLRenderer` instead of inheriting viewer renderer state.
|
|
1136
|
-
|
|
1137
|
-
Use `recordCameraSequence()` / `useCameraSequenceRecorder()` to step policy
|
|
1138
|
-
rollouts and capture synchronized per-camera frames from one or more MuJoCo
|
|
1139
|
-
camera configs. Sequence recording requires mounted MuJoCo camera, site, or
|
|
1140
|
-
body selectors by default; use still capture APIs for synthetic debug poses.
|
|
1141
|
-
|
|
1142
|
-
For LeRobot-style datasets, prefer the named-camera wrapper. It resolves task
|
|
1143
|
-
camera keys to MuJoCo cameras/sites/bodies, records the sequence, and returns
|
|
1144
|
-
the plan and readiness summary alongside frame provenance:
|
|
1122
|
+
**Live streams.** `useCameraStream(canvasRef, options)` renders a camera into a
|
|
1123
|
+
DOM `<canvas>` every frame (offscreen render → blit: no PNG round-trip, no render-
|
|
1124
|
+
loop takeover, splat scenes stream safely). Call it inside `<MujocoCanvas>`; the
|
|
1125
|
+
canvas can live anywhere in your DOM:
|
|
1145
1126
|
|
|
1146
1127
|
```tsx
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
left_wrist: [{ siteName: "left_wrist_camera_optical_frame" }],
|
|
1152
|
-
right_wrist: [{ siteName: "right_wrist_camera_optical_frame" }],
|
|
1153
|
-
},
|
|
1154
|
-
defaults: {
|
|
1155
|
-
width: 640,
|
|
1156
|
-
height: 480,
|
|
1157
|
-
type: "image/png",
|
|
1158
|
-
fov: 45,
|
|
1159
|
-
near: 0.01,
|
|
1160
|
-
far: 100,
|
|
1161
|
-
},
|
|
1162
|
-
frames: 16,
|
|
1163
|
-
stepsPerFrame: 1,
|
|
1164
|
-
retainFrames: false,
|
|
1165
|
-
requireMountedSources: true,
|
|
1166
|
-
onFrame: ({ frameIndex, cameras }) => {
|
|
1167
|
-
queueLeRobotImages(frameIndex, cameras);
|
|
1168
|
-
},
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
sequence.readiness.ready; // true when every requested stream resolved
|
|
1172
|
-
sequence.plan.missingKeys; // unresolved task cameras, if requireAll is false
|
|
1173
|
-
sequence.cameraSummaries.head.source; // mounted source provenance
|
|
1174
|
-
|
|
1175
|
-
const manifest = createMountedCameraFrameSequenceManifest(sequence);
|
|
1176
|
-
manifest.streamSummaries.head.complete; // per-camera frame coverage
|
|
1177
|
-
manifest.status; // "complete", "partial", or "missing"
|
|
1128
|
+
function WristStream({ canvasRef }) {
|
|
1129
|
+
useCameraStream(canvasRef, { cameraName: "wrist_cam", width: 256, height: 192 });
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1178
1132
|
```
|
|
1179
1133
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1134
|
+
For a transparent picture-in-picture overlay, use `<CameraView>` (or
|
|
1135
|
+
`useCameraViewport` to track your own element). It scissors into the main canvas —
|
|
1136
|
+
cheaper, but it takes over the render loop while mounted (incompatible with
|
|
1137
|
+
postprocessing, occluded by opaque DOM), so prefer `useCameraStream` for panel
|
|
1138
|
+
tiles:
|
|
1184
1139
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1140
|
+
```tsx
|
|
1141
|
+
<MujocoCanvas config={config}>
|
|
1142
|
+
<CameraView cameraName="wrist_cam" style={{ right: 16, bottom: 16, width: 240, height: 180 }} />
|
|
1143
|
+
</MujocoCanvas>
|
|
1144
|
+
```
|
|
1188
1145
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
LeRobot camera streams:
|
|
1146
|
+
**Policy tensors.** For in-browser inference, capture straight into a
|
|
1147
|
+
`Float32Array` — no canvas, no PNG. `usePolicyCameraTensors` keeps one reusable
|
|
1148
|
+
session per camera and re-aims it to the live pose each step:
|
|
1193
1149
|
|
|
1194
1150
|
```tsx
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
right_wrist: { siteName: "right_wrist_camera_optical_frame" },
|
|
1202
|
-
},
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
async function recordDatasetEpisode() {
|
|
1206
|
-
const cameraKeys = ["head", "left_wrist", "right_wrist"];
|
|
1207
|
-
const readiness = recorder.checkReadiness(cameraKeys);
|
|
1208
|
-
if (!readiness.ready) return;
|
|
1209
|
-
|
|
1210
|
-
await recorder.record({
|
|
1211
|
-
cameraKeys,
|
|
1212
|
-
frames: 16,
|
|
1213
|
-
retainFrames: false,
|
|
1214
|
-
onFrame: ({ frameIndex, cameras }) => {
|
|
1215
|
-
queueLeRobotImages(frameIndex, cameras);
|
|
1216
|
-
},
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1151
|
+
const cams = usePolicyCameraTensors({
|
|
1152
|
+
streams: [
|
|
1153
|
+
{ key: "wrist", cameraName: "wrist_cam", width: 96, height: 96, layout: "CHW" },
|
|
1154
|
+
{ key: "front", cameraName: "front", width: 96, height: 96, layout: "CHW" },
|
|
1155
|
+
],
|
|
1156
|
+
});
|
|
1219
1157
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1158
|
+
useAfterPhysicsStep(() => {
|
|
1159
|
+
const { tensors } = cams.capture();
|
|
1160
|
+
const wrist = new ort.Tensor("float32", tensors.wrist.data, [1, ...tensors.wrist.shape]);
|
|
1161
|
+
});
|
|
1226
1162
|
```
|
|
1227
1163
|
|
|
1228
|
-
`
|
|
1229
|
-
`
|
|
1230
|
-
|
|
1164
|
+
Use `usePolicyCameraTensorsFromMountedStreams` for dataset-name resolution,
|
|
1165
|
+
`captureCameraFrameTensor()` for one-offs, or `createCameraFrameCaptureSession()`
|
|
1166
|
+
+ `pixelsToPolicyImageTensor()` for lower-level control. The optional
|
|
1167
|
+
`mujoco-react/onnx` entry point wraps ONNX Runtime Web: `createOnnxPolicySession()`
|
|
1168
|
+
loads a manifest plus model, and `onnxTensorToPolicyActionChunk()` decodes the
|
|
1169
|
+
output into action chunks.
|
|
1231
1170
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
`wrist`. It also handles normalized/prefix/suffix matches such as
|
|
1238
|
-
`left_wrist` -> `left_wrist_camera_optical_frame`, and token-contained
|
|
1239
|
-
imported-model names such as `observation.images.head` ->
|
|
1240
|
-
`robot_head_camera`. It returns both the capture selector and the mounted-source
|
|
1241
|
-
provenance that should be stored beside the dataset:
|
|
1171
|
+
**Dataset recording.** `recordMountedCameraFrameSequence()` steps a rollout and
|
|
1172
|
+
captures synchronized per-camera frames, resolving LeRobot-style task camera keys
|
|
1173
|
+
to MuJoCo cameras/sites/bodies. It requires every requested `cameraKey` by default
|
|
1174
|
+
(set `requireAll: false` for exploratory tooling) and returns a plan, readiness,
|
|
1175
|
+
and per-camera source provenance:
|
|
1242
1176
|
|
|
1243
1177
|
```tsx
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
await api.recordCameraSequence({
|
|
1178
|
+
const sequence = await recordMountedCameraFrameSequence(api, {
|
|
1179
|
+
cameraKeys: ["head", "left_wrist", "right_wrist"],
|
|
1180
|
+
aliases: {
|
|
1181
|
+
head: [{ siteName: "head_camera_rgb_optical_frame" }],
|
|
1182
|
+
left_wrist: [{ siteName: "left_wrist_camera_optical_frame" }],
|
|
1183
|
+
right_wrist: [{ siteName: "right_wrist_camera_optical_frame" }],
|
|
1184
|
+
},
|
|
1185
|
+
defaults: { width: 640, height: 480, type: "image/png", fov: 45 },
|
|
1253
1186
|
frames: 16,
|
|
1254
|
-
|
|
1255
|
-
cameras: [{ key: "head", width: 640, height: 480, ...resolved.selector }],
|
|
1187
|
+
onFrame: ({ frameIndex, cameras }) => queueLeRobotImages(frameIndex, cameras),
|
|
1256
1188
|
});
|
|
1189
|
+
|
|
1190
|
+
const manifest = createMountedCameraFrameSequenceManifest(sequence); // dataset-facing manifest
|
|
1257
1191
|
```
|
|
1258
1192
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1193
|
+
Inside `<MujocoCanvas>`, `useMountedCameraSequenceRecorder()` exposes the same
|
|
1194
|
+
planning/recording with React status and a `checkReadiness()` preflight gate.
|
|
1195
|
+
`resolveMountedCameraFrameSource()` maps a single dataset feature name to a MuJoCo
|
|
1196
|
+
source (aliases first, then camera > site > body, then normalized prefix/suffix
|
|
1197
|
+
matches) when you need the selector before recording. The lower-level
|
|
1198
|
+
`recordCameraSequence()` / `useCameraSequenceRecorder()` take explicit camera
|
|
1199
|
+
configs.
|
|
1262
1200
|
|
|
1263
1201
|
### `useCtrlNoise(config)`
|
|
1264
1202
|
|
|
@@ -85,6 +85,95 @@ var SplatEnvironmentReadinessStatus = {
|
|
|
85
85
|
UnsupportedFormat: "unsupported-format",
|
|
86
86
|
Ready: "ready"
|
|
87
87
|
};
|
|
88
|
+
|
|
89
|
+
// src/policyImageTensors.ts
|
|
90
|
+
function resolveTensorOptions(options) {
|
|
91
|
+
return {
|
|
92
|
+
channels: 3,
|
|
93
|
+
layout: "CHW",
|
|
94
|
+
range: [0, 1],
|
|
95
|
+
...options
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function normalizeChannel(value, range) {
|
|
99
|
+
const [min, max] = range;
|
|
100
|
+
if (min === 0 && max === 255) return value;
|
|
101
|
+
return min + value / 255 * (max - min);
|
|
102
|
+
}
|
|
103
|
+
function pixelsToPolicyImageTensor(pixels, options) {
|
|
104
|
+
const resolved = resolveTensorOptions(options);
|
|
105
|
+
const { width, height, channels, layout, range } = resolved;
|
|
106
|
+
const expected = width * height * 4;
|
|
107
|
+
if (pixels.length < expected) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Pixel buffer of length ${pixels.length} is too small for ${width}x${height} RGBA data (${expected} bytes).`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const flipY = options.sourceOrigin === "bottom-left";
|
|
113
|
+
const flipX = options.flipX ?? false;
|
|
114
|
+
const pixelCount = width * height;
|
|
115
|
+
const data = new Float32Array(pixelCount * channels);
|
|
116
|
+
for (let y = 0; y < height; y += 1) {
|
|
117
|
+
const sourceY = flipY ? height - y - 1 : y;
|
|
118
|
+
for (let x = 0; x < width; x += 1) {
|
|
119
|
+
const sourceX = flipX ? width - x - 1 : x;
|
|
120
|
+
const source = (sourceY * width + sourceX) * 4;
|
|
121
|
+
const target = y * width + x;
|
|
122
|
+
for (let channel = 0; channel < channels; channel += 1) {
|
|
123
|
+
const value = normalizeChannel(pixels[source + channel], range);
|
|
124
|
+
if (layout === "CHW") {
|
|
125
|
+
data[channel * pixelCount + target] = value;
|
|
126
|
+
} else {
|
|
127
|
+
data[target * channels + channel] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
data,
|
|
134
|
+
shape: layout === "CHW" ? [channels, height, width] : [height, width, channels],
|
|
135
|
+
width,
|
|
136
|
+
height,
|
|
137
|
+
channels,
|
|
138
|
+
layout,
|
|
139
|
+
range
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function imageDataToPolicyImageTensor(imageData, options) {
|
|
143
|
+
const resolved = resolveTensorOptions(options);
|
|
144
|
+
if (imageData.width !== resolved.width || imageData.height !== resolved.height) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`ImageData size ${imageData.width}x${imageData.height} does not match tensor size ${resolved.width}x${resolved.height}.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return pixelsToPolicyImageTensor(imageData.data, {
|
|
150
|
+
...resolved,
|
|
151
|
+
sourceOrigin: "top-left"
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function decodeImageSource(dataUrl) {
|
|
155
|
+
const image = new Image();
|
|
156
|
+
image.decoding = "async";
|
|
157
|
+
image.src = dataUrl;
|
|
158
|
+
await image.decode();
|
|
159
|
+
return image;
|
|
160
|
+
}
|
|
161
|
+
async function dataUrlToPolicyImageTensor(dataUrl, options) {
|
|
162
|
+
const resolved = resolveTensorOptions(options);
|
|
163
|
+
const image = await decodeImageSource(dataUrl);
|
|
164
|
+
const canvas = document.createElement("canvas");
|
|
165
|
+
canvas.width = resolved.width;
|
|
166
|
+
canvas.height = resolved.height;
|
|
167
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
168
|
+
if (!context) {
|
|
169
|
+
throw new Error("Unable to create a 2D canvas context for policy image tensor conversion.");
|
|
170
|
+
}
|
|
171
|
+
context.drawImage(image, 0, 0, resolved.width, resolved.height);
|
|
172
|
+
return imageDataToPolicyImageTensor(
|
|
173
|
+
context.getImageData(0, 0, resolved.width, resolved.height),
|
|
174
|
+
resolved
|
|
175
|
+
);
|
|
176
|
+
}
|
|
88
177
|
var CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCaptureRender";
|
|
89
178
|
var CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCapturePreRender";
|
|
90
179
|
var CAPTURE_EXCLUDE_KEY = "mujoco.capture.exclude";
|
|
@@ -465,7 +554,7 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
465
554
|
);
|
|
466
555
|
return captureOptions;
|
|
467
556
|
}
|
|
468
|
-
function
|
|
557
|
+
function renderCaptureToTarget(captureOptions, readback) {
|
|
469
558
|
const previousState = saveRendererState(sessionRenderer);
|
|
470
559
|
const previousSceneState = applyCaptureVisualOverrides(
|
|
471
560
|
sessionRenderer,
|
|
@@ -494,6 +583,15 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
494
583
|
}
|
|
495
584
|
sessionRenderer.clear();
|
|
496
585
|
sessionRenderer.render(scene, camera);
|
|
586
|
+
readback();
|
|
587
|
+
} finally {
|
|
588
|
+
restoreObjectVisibility(hidden);
|
|
589
|
+
if (previousSceneState) restoreSceneVisualState(scene, previousSceneState);
|
|
590
|
+
restoreRendererState(sessionRenderer, previousState);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function renderPreparedCapture(captureOptions) {
|
|
594
|
+
renderCaptureToTarget(captureOptions, () => {
|
|
497
595
|
readRenderTargetToCanvas(
|
|
498
596
|
sessionRenderer,
|
|
499
597
|
target,
|
|
@@ -506,22 +604,44 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
506
604
|
sessionRenderer.outputColorSpace,
|
|
507
605
|
captureOptions.flipX ?? false
|
|
508
606
|
);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
restoreObjectVisibility(hidden);
|
|
518
|
-
if (previousSceneState) restoreSceneVisualState(scene, previousSceneState);
|
|
519
|
-
restoreRendererState(sessionRenderer, previousState);
|
|
520
|
-
}
|
|
607
|
+
});
|
|
608
|
+
return {
|
|
609
|
+
canvas,
|
|
610
|
+
camera,
|
|
611
|
+
width,
|
|
612
|
+
height,
|
|
613
|
+
source: getCameraFrameCaptureSource(captureOptions)
|
|
614
|
+
};
|
|
521
615
|
}
|
|
522
616
|
function capture(nextOptions = {}) {
|
|
523
617
|
return renderPreparedCapture(resolveCaptureOptions(nextOptions));
|
|
524
618
|
}
|
|
619
|
+
function capturePixels(nextOptions = {}) {
|
|
620
|
+
const captureOptions = resolveCaptureOptions(nextOptions);
|
|
621
|
+
renderCaptureToTarget(captureOptions, () => {
|
|
622
|
+
sessionRenderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
|
|
623
|
+
});
|
|
624
|
+
return {
|
|
625
|
+
pixels,
|
|
626
|
+
camera,
|
|
627
|
+
width,
|
|
628
|
+
height,
|
|
629
|
+
source: getCameraFrameCaptureSource(captureOptions)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function captureTensor(nextOptions = {}) {
|
|
633
|
+
const result = capturePixels(nextOptions);
|
|
634
|
+
const tensor = pixelsToPolicyImageTensor(pixels, {
|
|
635
|
+
width,
|
|
636
|
+
height,
|
|
637
|
+
channels: nextOptions.channels,
|
|
638
|
+
layout: nextOptions.layout,
|
|
639
|
+
range: nextOptions.range,
|
|
640
|
+
sourceOrigin: "bottom-left",
|
|
641
|
+
flipX: nextOptions.flipX
|
|
642
|
+
});
|
|
643
|
+
return { ...tensor, camera, source: result.source };
|
|
644
|
+
}
|
|
525
645
|
async function captureAsync(nextOptions = {}) {
|
|
526
646
|
const captureOptions = resolveCaptureOptions(nextOptions);
|
|
527
647
|
runCapturePreRenderHooks(scene);
|
|
@@ -594,6 +714,8 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
594
714
|
height,
|
|
595
715
|
capture,
|
|
596
716
|
captureAsync,
|
|
717
|
+
capturePixels,
|
|
718
|
+
captureTensor,
|
|
597
719
|
captureDataUrl(nextOptions = {}) {
|
|
598
720
|
const type = nextOptions.type ?? options.type ?? "image/png";
|
|
599
721
|
const result = capture(nextOptions);
|
|
@@ -686,6 +808,19 @@ async function captureCameraFrameBlob(renderer, scene, fallbackCamera, options =
|
|
|
686
808
|
session.dispose();
|
|
687
809
|
}
|
|
688
810
|
}
|
|
811
|
+
function captureCameraFrameTensor(renderer, scene, fallbackCamera, options = {}) {
|
|
812
|
+
const session = createCameraFrameCaptureSession(
|
|
813
|
+
renderer,
|
|
814
|
+
scene,
|
|
815
|
+
fallbackCamera,
|
|
816
|
+
options
|
|
817
|
+
);
|
|
818
|
+
try {
|
|
819
|
+
return session.captureTensor(options);
|
|
820
|
+
} finally {
|
|
821
|
+
session.dispose();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
689
824
|
var DEFAULT_BACKGROUND = "#181a1f";
|
|
690
825
|
function ScenarioLighting({
|
|
691
826
|
preset = "studio",
|
|
@@ -1284,6 +1419,12 @@ function clamp01(value) {
|
|
|
1284
1419
|
* @license
|
|
1285
1420
|
* SPDX-License-Identifier: Apache-2.0
|
|
1286
1421
|
*/
|
|
1422
|
+
/**
|
|
1423
|
+
* @license
|
|
1424
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1425
|
+
*
|
|
1426
|
+
* Helpers for turning browser camera captures into policy image tensors.
|
|
1427
|
+
*/
|
|
1287
1428
|
/**
|
|
1288
1429
|
* @license
|
|
1289
1430
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -1291,6 +1432,6 @@ function clamp01(value) {
|
|
|
1291
1432
|
* Offscreen camera-frame capture for R3F/MuJoCo scenes.
|
|
1292
1433
|
*/
|
|
1293
1434
|
|
|
1294
|
-
export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
|
|
1295
|
-
//# sourceMappingURL=chunk-
|
|
1296
|
-
//# sourceMappingURL=chunk-
|
|
1435
|
+
export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createCaptureCamera, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, prepareCaptureCamera, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
|
|
1436
|
+
//# sourceMappingURL=chunk-KHZ5U36J.js.map
|
|
1437
|
+
//# sourceMappingURL=chunk-KHZ5U36J.js.map
|