mujoco-react 9.2.0 → 9.3.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 +96 -10
- package/dist/{chunk-33CV6HSV.js → chunk-T3GVZJ4F.js} +222 -8
- package/dist/chunk-T3GVZJ4F.js.map +1 -0
- package/dist/index.d.ts +158 -4
- package/dist/index.js +692 -130
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +24 -2
- package/dist/spark.js +89 -3
- package/dist/spark.js.map +1 -1
- package/dist/{types-S8ggQY2n.d.ts → types-oxbxOkAx.d.ts} +120 -3
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +6 -3
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/VisualScenario.tsx +178 -1
- package/src/core/MujocoSimProvider.tsx +373 -29
- package/src/core/SceneLoader.ts +13 -0
- package/src/hooks/useMountedCameraSequenceRecorder.ts +107 -0
- package/src/index.ts +49 -0
- package/src/rendering/cameraFrameCapture.ts +195 -26
- package/src/rendering/cameraFrameSource.ts +375 -0
- package/src/spark.tsx +144 -0
- package/src/types.ts +122 -2
- package/src/vite.ts +5 -2
- package/dist/chunk-33CV6HSV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { withContacts, getContact } from './chunk-T3GVZJ4F.js';
|
|
2
|
+
export { RobotActuators, RobotBodies, RobotCameras, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerRobotResources, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, withSplatEnvironment } from './chunk-T3GVZJ4F.js';
|
|
2
3
|
import loadMujoco from '@mujoco/mujoco';
|
|
3
4
|
import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
|
|
4
5
|
import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
|
@@ -104,80 +105,6 @@ function MujocoProvider({
|
|
|
104
105
|
}
|
|
105
106
|
);
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
-
// src/types.ts
|
|
109
|
-
var runtimeRobotResources = {};
|
|
110
|
-
var REGISTER_RESOURCE_KEYS = ["actuators", "sensors", "bodies", "joints", "sites", "geoms", "keyframes"];
|
|
111
|
-
function createEmptyRuntimeResources() {
|
|
112
|
-
return {
|
|
113
|
-
actuators: {},
|
|
114
|
-
sensors: {},
|
|
115
|
-
bodies: {},
|
|
116
|
-
joints: {},
|
|
117
|
-
sites: {},
|
|
118
|
-
geoms: {},
|
|
119
|
-
keyframes: {}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
function registerRobotResources(resources) {
|
|
123
|
-
for (const [robot, robotResources] of Object.entries(resources)) {
|
|
124
|
-
const existing = runtimeRobotResources[robot] ?? createEmptyRuntimeResources();
|
|
125
|
-
for (const key of REGISTER_RESOURCE_KEYS) {
|
|
126
|
-
existing[key] = { ...existing[key], ...robotResources[key] ?? {} };
|
|
127
|
-
}
|
|
128
|
-
runtimeRobotResources[robot] = existing;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function createResourceCategory(key) {
|
|
132
|
-
return new Proxy({}, {
|
|
133
|
-
get(_target, robot) {
|
|
134
|
-
if (typeof robot !== "string") return void 0;
|
|
135
|
-
return runtimeRobotResources[robot]?.[key] ?? {};
|
|
136
|
-
},
|
|
137
|
-
ownKeys() {
|
|
138
|
-
return Reflect.ownKeys(runtimeRobotResources);
|
|
139
|
-
},
|
|
140
|
-
getOwnPropertyDescriptor(_target, robot) {
|
|
141
|
-
if (typeof robot !== "string" || !(robot in runtimeRobotResources)) return void 0;
|
|
142
|
-
return { enumerable: true, configurable: true };
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
var RobotResources = new Proxy(runtimeRobotResources, {
|
|
147
|
-
get(target, robot) {
|
|
148
|
-
if (typeof robot !== "string") return void 0;
|
|
149
|
-
return target[robot] ?? createEmptyRuntimeResources();
|
|
150
|
-
},
|
|
151
|
-
ownKeys(target) {
|
|
152
|
-
return Reflect.ownKeys(target);
|
|
153
|
-
},
|
|
154
|
-
getOwnPropertyDescriptor(target, robot) {
|
|
155
|
-
if (typeof robot !== "string" || !(robot in target)) return void 0;
|
|
156
|
-
return { enumerable: true, configurable: true };
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
var RobotActuators = createResourceCategory("actuators");
|
|
160
|
-
var RobotSensors = createResourceCategory("sensors");
|
|
161
|
-
var RobotBodies = createResourceCategory("bodies");
|
|
162
|
-
var RobotJoints = createResourceCategory("joints");
|
|
163
|
-
var RobotSites = createResourceCategory("sites");
|
|
164
|
-
var RobotGeoms = createResourceCategory("geoms");
|
|
165
|
-
var RobotKeyframes = createResourceCategory("keyframes");
|
|
166
|
-
function getContact(contacts, i) {
|
|
167
|
-
try {
|
|
168
|
-
return contacts.get(i);
|
|
169
|
-
} catch {
|
|
170
|
-
return void 0;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
function withContacts(data, read) {
|
|
174
|
-
const contacts = data.contact;
|
|
175
|
-
try {
|
|
176
|
-
return read(contacts);
|
|
177
|
-
} finally {
|
|
178
|
-
contacts.delete?.();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
108
|
var CapsuleGeometry = class extends THREE12.BufferGeometry {
|
|
182
109
|
parameters;
|
|
183
110
|
constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
|
|
@@ -480,6 +407,15 @@ function findSensorByName(mjModel, name) {
|
|
|
480
407
|
}
|
|
481
408
|
return -1;
|
|
482
409
|
}
|
|
410
|
+
function findCameraByName(mjModel, name) {
|
|
411
|
+
const ncam = mjModel.ncam ?? 0;
|
|
412
|
+
const addresses = mjModel.name_camadr;
|
|
413
|
+
if (!addresses) return -1;
|
|
414
|
+
for (let i = 0; i < ncam; i++) {
|
|
415
|
+
if (getName(mjModel, addresses[i]) === name) return i;
|
|
416
|
+
}
|
|
417
|
+
return -1;
|
|
418
|
+
}
|
|
483
419
|
function findTendonByName(mjModel, name) {
|
|
484
420
|
for (let i = 0; i < (mjModel.ntendon ?? 0); i++) {
|
|
485
421
|
if (getName(mjModel, mjModel.name_tendonadr[i]) === name) return i;
|
|
@@ -1439,17 +1375,32 @@ function createCaptureCamera(options, fallbackCamera, width, height) {
|
|
|
1439
1375
|
applyCameraPose(camera, options, fallbackCamera);
|
|
1440
1376
|
return camera;
|
|
1441
1377
|
}
|
|
1442
|
-
function
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1378
|
+
function getCaptureDimensions(renderer, options) {
|
|
1379
|
+
const width = Math.max(
|
|
1380
|
+
1,
|
|
1381
|
+
Math.floor(options.width ?? renderer.domElement.width)
|
|
1382
|
+
);
|
|
1383
|
+
const height = Math.max(
|
|
1384
|
+
1,
|
|
1385
|
+
Math.floor(options.height ?? renderer.domElement.height)
|
|
1386
|
+
);
|
|
1387
|
+
return { width, height };
|
|
1388
|
+
}
|
|
1389
|
+
function prepareCaptureCamera(camera, options, fallbackCamera, width, height) {
|
|
1390
|
+
if (options.camera) {
|
|
1391
|
+
camera.copy(options.camera);
|
|
1392
|
+
}
|
|
1393
|
+
if (camera instanceof THREE12.PerspectiveCamera) {
|
|
1394
|
+
camera.aspect = width / height;
|
|
1395
|
+
camera.fov = options.fov ?? camera.fov;
|
|
1396
|
+
camera.near = options.near ?? camera.near;
|
|
1397
|
+
camera.far = options.far ?? camera.far;
|
|
1398
|
+
camera.updateProjectionMatrix();
|
|
1451
1399
|
}
|
|
1452
|
-
|
|
1400
|
+
applyCameraPose(camera, options, fallbackCamera);
|
|
1401
|
+
}
|
|
1402
|
+
function readRenderTargetToCanvas(renderer, target, canvas, context, pixels, imageData, width, height) {
|
|
1403
|
+
renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
|
|
1453
1404
|
const rowBytes = width * 4;
|
|
1454
1405
|
for (let y = 0; y < height; y += 1) {
|
|
1455
1406
|
const sourceStart = (height - y - 1) * rowBytes;
|
|
@@ -1462,28 +1413,133 @@ function readRenderTargetToCanvas(renderer, target, width, height) {
|
|
|
1462
1413
|
context.putImageData(imageData, 0, 0);
|
|
1463
1414
|
return canvas;
|
|
1464
1415
|
}
|
|
1465
|
-
function
|
|
1466
|
-
|
|
1467
|
-
|
|
1416
|
+
function getCameraFrameCaptureSource(options) {
|
|
1417
|
+
if (options.source) return options.source;
|
|
1418
|
+
if (options.cameraName) {
|
|
1419
|
+
return { kind: "mujoco-camera", cameraName: options.cameraName };
|
|
1420
|
+
}
|
|
1421
|
+
if (options.siteName) {
|
|
1422
|
+
return { kind: "mujoco-site", siteName: options.siteName };
|
|
1423
|
+
}
|
|
1424
|
+
if (options.bodyName) {
|
|
1425
|
+
return { kind: "mujoco-body", bodyName: options.bodyName };
|
|
1426
|
+
}
|
|
1427
|
+
if (options.camera) return { kind: "custom-camera" };
|
|
1428
|
+
if (options.position || options.lookAt || options.quaternion) {
|
|
1429
|
+
return { kind: "explicit-pose" };
|
|
1430
|
+
}
|
|
1431
|
+
return { kind: "fallback-camera" };
|
|
1432
|
+
}
|
|
1433
|
+
function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, options = {}) {
|
|
1434
|
+
const { width, height } = getCaptureDimensions(renderer, options);
|
|
1468
1435
|
const camera = createCaptureCamera(options, fallbackCamera, width, height);
|
|
1469
1436
|
const target = new THREE12.WebGLRenderTarget(width, height, {
|
|
1470
1437
|
format: THREE12.RGBAFormat,
|
|
1471
1438
|
type: THREE12.UnsignedByteType
|
|
1472
1439
|
});
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
|
|
1440
|
+
const canvas = document.createElement("canvas");
|
|
1441
|
+
canvas.width = width;
|
|
1442
|
+
canvas.height = height;
|
|
1443
|
+
const context = canvas.getContext("2d");
|
|
1444
|
+
if (!context) {
|
|
1445
|
+
target.dispose();
|
|
1446
|
+
throw new Error("Unable to create a 2D canvas for camera frame capture.");
|
|
1447
|
+
}
|
|
1448
|
+
const drawContext = context;
|
|
1449
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
1450
|
+
const imageData = drawContext.createImageData(width, height);
|
|
1451
|
+
function capture(nextOptions = {}) {
|
|
1452
|
+
const captureOptions = { ...options, ...nextOptions };
|
|
1453
|
+
const nextDimensions = getCaptureDimensions(renderer, captureOptions);
|
|
1454
|
+
if (nextDimensions.width !== width || nextDimensions.height !== height) {
|
|
1455
|
+
throw new Error(
|
|
1456
|
+
"Camera frame capture sessions require stable width and height."
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
prepareCaptureCamera(
|
|
1460
|
+
camera,
|
|
1461
|
+
captureOptions,
|
|
1462
|
+
fallbackCamera,
|
|
1463
|
+
width,
|
|
1464
|
+
height
|
|
1465
|
+
);
|
|
1466
|
+
const previousTarget = renderer.getRenderTarget();
|
|
1467
|
+
const previousXrEnabled = renderer.xr.enabled;
|
|
1468
|
+
scene.updateMatrixWorld(true);
|
|
1469
|
+
try {
|
|
1470
|
+
renderer.xr.enabled = false;
|
|
1471
|
+
renderer.setRenderTarget(target);
|
|
1472
|
+
renderer.clear();
|
|
1473
|
+
renderer.render(scene, camera);
|
|
1474
|
+
readRenderTargetToCanvas(
|
|
1475
|
+
renderer,
|
|
1476
|
+
target,
|
|
1477
|
+
canvas,
|
|
1478
|
+
drawContext,
|
|
1479
|
+
pixels,
|
|
1480
|
+
imageData,
|
|
1481
|
+
width,
|
|
1482
|
+
height
|
|
1483
|
+
);
|
|
1484
|
+
return {
|
|
1485
|
+
canvas,
|
|
1486
|
+
camera,
|
|
1487
|
+
width,
|
|
1488
|
+
height,
|
|
1489
|
+
source: getCameraFrameCaptureSource(captureOptions)
|
|
1490
|
+
};
|
|
1491
|
+
} finally {
|
|
1492
|
+
renderer.setRenderTarget(previousTarget);
|
|
1493
|
+
renderer.xr.enabled = previousXrEnabled;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return {
|
|
1497
|
+
width,
|
|
1498
|
+
height,
|
|
1499
|
+
capture,
|
|
1500
|
+
captureDataUrl(nextOptions = {}) {
|
|
1501
|
+
const type = nextOptions.type ?? options.type ?? "image/png";
|
|
1502
|
+
const result = capture(nextOptions);
|
|
1503
|
+
return {
|
|
1504
|
+
...result,
|
|
1505
|
+
dataUrl: result.canvas.toDataURL(
|
|
1506
|
+
type,
|
|
1507
|
+
nextOptions.quality ?? options.quality
|
|
1508
|
+
),
|
|
1509
|
+
type
|
|
1510
|
+
};
|
|
1511
|
+
},
|
|
1512
|
+
async captureBlob(nextOptions = {}) {
|
|
1513
|
+
const type = nextOptions.type ?? options.type ?? "image/png";
|
|
1514
|
+
const result = capture(nextOptions);
|
|
1515
|
+
const blob = await new Promise((resolve, reject) => {
|
|
1516
|
+
result.canvas.toBlob(
|
|
1517
|
+
(nextBlob) => {
|
|
1518
|
+
if (nextBlob) resolve(nextBlob);
|
|
1519
|
+
else reject(new Error("Camera frame capture did not produce a Blob."));
|
|
1520
|
+
},
|
|
1521
|
+
type,
|
|
1522
|
+
nextOptions.quality ?? options.quality
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
return { ...result, blob, type };
|
|
1526
|
+
},
|
|
1527
|
+
dispose() {
|
|
1528
|
+
target.dispose();
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
function renderCameraFrameToCanvas(renderer, scene, fallbackCamera, options = {}) {
|
|
1533
|
+
const session = createCameraFrameCaptureSession(
|
|
1534
|
+
renderer,
|
|
1535
|
+
scene,
|
|
1536
|
+
fallbackCamera,
|
|
1537
|
+
options
|
|
1538
|
+
);
|
|
1476
1539
|
try {
|
|
1477
|
-
|
|
1478
|
-
renderer.setRenderTarget(target);
|
|
1479
|
-
renderer.clear();
|
|
1480
|
-
renderer.render(scene, camera);
|
|
1481
|
-
const canvas = readRenderTargetToCanvas(renderer, target, width, height);
|
|
1482
|
-
return { canvas, camera, width, height };
|
|
1540
|
+
return session.capture();
|
|
1483
1541
|
} finally {
|
|
1484
|
-
|
|
1485
|
-
renderer.xr.enabled = previousXrEnabled;
|
|
1486
|
-
target.dispose();
|
|
1542
|
+
session.dispose();
|
|
1487
1543
|
}
|
|
1488
1544
|
}
|
|
1489
1545
|
async function captureCameraFrame(renderer, scene, fallbackCamera, options = {}) {
|
|
@@ -1520,6 +1576,174 @@ async function captureCameraFrameBlob(renderer, scene, fallbackCamera, options =
|
|
|
1520
1576
|
});
|
|
1521
1577
|
return { ...result, blob, type };
|
|
1522
1578
|
}
|
|
1579
|
+
|
|
1580
|
+
// src/rendering/cameraFrameSource.ts
|
|
1581
|
+
var MountedCameraFrameSequenceReadinessStatus = {
|
|
1582
|
+
Ready: "ready",
|
|
1583
|
+
Partial: "partial",
|
|
1584
|
+
Missing: "missing"
|
|
1585
|
+
};
|
|
1586
|
+
function getResourceName(resource) {
|
|
1587
|
+
if (!resource) return null;
|
|
1588
|
+
return typeof resource === "string" ? resource : resource.name ?? null;
|
|
1589
|
+
}
|
|
1590
|
+
function createNameSet(resources) {
|
|
1591
|
+
return new Set(
|
|
1592
|
+
(resources ?? []).map((resource) => getResourceName(resource)).filter((name) => Boolean(name))
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
function normalizeAliasCandidates(value) {
|
|
1596
|
+
if (!value) return [];
|
|
1597
|
+
return Array.isArray(value) ? value : [value];
|
|
1598
|
+
}
|
|
1599
|
+
function countMountedSelectors(selector) {
|
|
1600
|
+
return Number(Boolean(selector.cameraName)) + Number(Boolean(selector.siteName)) + Number(Boolean(selector.bodyName));
|
|
1601
|
+
}
|
|
1602
|
+
function getMountedCameraFrameCaptureSource(selector) {
|
|
1603
|
+
if (countMountedSelectors(selector) !== 1) return null;
|
|
1604
|
+
if (selector.cameraName) {
|
|
1605
|
+
return { kind: "mujoco-camera", cameraName: selector.cameraName };
|
|
1606
|
+
}
|
|
1607
|
+
if (selector.siteName) {
|
|
1608
|
+
return { kind: "mujoco-site", siteName: selector.siteName };
|
|
1609
|
+
}
|
|
1610
|
+
if (selector.bodyName) {
|
|
1611
|
+
return { kind: "mujoco-body", bodyName: selector.bodyName };
|
|
1612
|
+
}
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
function isMountedCameraFrameCaptureSource(source) {
|
|
1616
|
+
return source.kind === "mujoco-camera" || source.kind === "mujoco-site" || source.kind === "mujoco-body";
|
|
1617
|
+
}
|
|
1618
|
+
function getCameraFrameCaptureSourceTarget(source) {
|
|
1619
|
+
if (source.kind === "mujoco-camera") return source.cameraName;
|
|
1620
|
+
if (source.kind === "mujoco-site") return source.siteName;
|
|
1621
|
+
if (source.kind === "mujoco-body") return source.bodyName;
|
|
1622
|
+
if (source.kind === "custom-camera") return "custom camera";
|
|
1623
|
+
if (source.kind === "explicit-pose") return "explicit pose";
|
|
1624
|
+
return "fallback camera";
|
|
1625
|
+
}
|
|
1626
|
+
function isSelectorMounted(selector, cameraNames, siteNames, bodyNames) {
|
|
1627
|
+
if (countMountedSelectors(selector) !== 1) return false;
|
|
1628
|
+
return (selector.cameraName ? cameraNames.has(selector.cameraName) : false) || (selector.siteName ? siteNames.has(selector.siteName) : false) || (selector.bodyName ? bodyNames.has(selector.bodyName) : false);
|
|
1629
|
+
}
|
|
1630
|
+
function resolveMountedCameraFrameSource(key, options) {
|
|
1631
|
+
const cameraNames = createNameSet(options.cameras);
|
|
1632
|
+
const siteNames = createNameSet(options.sites);
|
|
1633
|
+
const bodyNames = createNameSet(options.bodies);
|
|
1634
|
+
const directCandidates = [
|
|
1635
|
+
{ cameraName: key },
|
|
1636
|
+
{ siteName: key },
|
|
1637
|
+
{ bodyName: key }
|
|
1638
|
+
];
|
|
1639
|
+
const aliasCandidates = normalizeAliasCandidates(options.aliases?.[key]);
|
|
1640
|
+
const candidates = [...directCandidates, ...aliasCandidates];
|
|
1641
|
+
for (const selector of candidates) {
|
|
1642
|
+
if (!isSelectorMounted(selector, cameraNames, siteNames, bodyNames)) {
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
1646
|
+
if (!source) continue;
|
|
1647
|
+
return { key, selector, source };
|
|
1648
|
+
}
|
|
1649
|
+
if (options.allowAliasFallback) {
|
|
1650
|
+
for (const selector of aliasCandidates) {
|
|
1651
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
1652
|
+
if (!source) continue;
|
|
1653
|
+
return { key, selector, source };
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return null;
|
|
1657
|
+
}
|
|
1658
|
+
function createMountedCameraFrameSequencePlan(cameraKeys, options) {
|
|
1659
|
+
const cameras = [];
|
|
1660
|
+
const resolved = {};
|
|
1661
|
+
const missingKeys = [];
|
|
1662
|
+
for (const key of cameraKeys) {
|
|
1663
|
+
const mountedSource = resolveMountedCameraFrameSource(key, options);
|
|
1664
|
+
if (!mountedSource) {
|
|
1665
|
+
missingKeys.push(key);
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
resolved[key] = mountedSource;
|
|
1669
|
+
cameras.push({
|
|
1670
|
+
key,
|
|
1671
|
+
...options.defaults,
|
|
1672
|
+
...options.cameraOptions?.[key],
|
|
1673
|
+
...mountedSource.selector,
|
|
1674
|
+
source: mountedSource.source
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
if (options.requireAll && missingKeys.length > 0) {
|
|
1678
|
+
throw new Error(
|
|
1679
|
+
`Unable to resolve mounted MuJoCo camera source${missingKeys.length === 1 ? "" : "s"} for ${missingKeys.join(", ")}.`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
return {
|
|
1683
|
+
cameraKeys: [...cameraKeys],
|
|
1684
|
+
cameras,
|
|
1685
|
+
resolved,
|
|
1686
|
+
missingKeys
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
function createMountedCameraFrameSequenceReadiness(plan) {
|
|
1690
|
+
const cameras = {};
|
|
1691
|
+
const resolvedKeys = plan.cameraKeys.filter((key) => Boolean(plan.resolved[key]));
|
|
1692
|
+
for (const key of plan.cameraKeys) {
|
|
1693
|
+
const resolved = plan.resolved[key];
|
|
1694
|
+
cameras[key] = resolved ? {
|
|
1695
|
+
key,
|
|
1696
|
+
ready: true,
|
|
1697
|
+
selector: resolved.selector,
|
|
1698
|
+
source: resolved.source,
|
|
1699
|
+
message: `Camera stream "${key}" resolves to ${resolved.source.kind}:${getCameraFrameCaptureSourceTarget(resolved.source)}.`
|
|
1700
|
+
} : {
|
|
1701
|
+
key,
|
|
1702
|
+
ready: false,
|
|
1703
|
+
message: `Camera stream "${key}" does not resolve to a mounted MuJoCo camera, site, or body.`
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
const missingKeys = [...plan.missingKeys];
|
|
1707
|
+
const ready = missingKeys.length === 0;
|
|
1708
|
+
const status = ready ? MountedCameraFrameSequenceReadinessStatus.Ready : resolvedKeys.length > 0 ? MountedCameraFrameSequenceReadinessStatus.Partial : MountedCameraFrameSequenceReadinessStatus.Missing;
|
|
1709
|
+
return {
|
|
1710
|
+
ready,
|
|
1711
|
+
status,
|
|
1712
|
+
cameraKeys: [...plan.cameraKeys],
|
|
1713
|
+
resolvedKeys,
|
|
1714
|
+
missingKeys,
|
|
1715
|
+
cameras,
|
|
1716
|
+
message: ready ? `All ${plan.cameraKeys.length} requested camera stream${plan.cameraKeys.length === 1 ? "" : "s"} resolve to mounted MuJoCo sources.` : `Missing mounted MuJoCo source${missingKeys.length === 1 ? "" : "s"} for ${missingKeys.join(", ")}.`
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
function createMountedCameraFrameSequencePlanFromApi(api, cameraKeys, options = {}) {
|
|
1720
|
+
return createMountedCameraFrameSequencePlan(cameraKeys, {
|
|
1721
|
+
...options,
|
|
1722
|
+
cameras: api.getCameras(),
|
|
1723
|
+
sites: api.getSites(),
|
|
1724
|
+
bodies: api.getBodies()
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
async function recordMountedCameraFrameSequence(api, options) {
|
|
1728
|
+
const { cameraKeys, ...restOptions } = options;
|
|
1729
|
+
const requireAll = restOptions.requireAll ?? restOptions.requireMountedSources ?? true;
|
|
1730
|
+
const plan = createMountedCameraFrameSequencePlanFromApi(
|
|
1731
|
+
api,
|
|
1732
|
+
cameraKeys,
|
|
1733
|
+
{ ...restOptions, requireAll }
|
|
1734
|
+
);
|
|
1735
|
+
const readiness = createMountedCameraFrameSequenceReadiness(plan);
|
|
1736
|
+
const result = await api.recordCameraSequence({
|
|
1737
|
+
...restOptions,
|
|
1738
|
+
cameras: plan.cameras,
|
|
1739
|
+
requireMountedSources: restOptions.requireMountedSources ?? true
|
|
1740
|
+
});
|
|
1741
|
+
return {
|
|
1742
|
+
...result,
|
|
1743
|
+
plan,
|
|
1744
|
+
readiness
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1523
1747
|
var JOINT_TYPE_NAMES2 = ["free", "ball", "slide", "hinge"];
|
|
1524
1748
|
var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
|
|
1525
1749
|
var SENSOR_TYPE_NAMES = {
|
|
@@ -1602,6 +1826,72 @@ function waitForNextAnimationFrame2() {
|
|
|
1602
1826
|
requestAnimationFrame(() => resolve());
|
|
1603
1827
|
});
|
|
1604
1828
|
}
|
|
1829
|
+
function throwIfCameraSequenceAborted(signal) {
|
|
1830
|
+
if (!signal?.aborted) return;
|
|
1831
|
+
if (typeof signal.reason === "object" && signal.reason instanceof Error) {
|
|
1832
|
+
throw signal.reason;
|
|
1833
|
+
}
|
|
1834
|
+
throw new DOMException("Camera sequence recording was aborted.", "AbortError");
|
|
1835
|
+
}
|
|
1836
|
+
function vector3FromArray(values, offset) {
|
|
1837
|
+
return [values[offset], values[offset + 1], values[offset + 2]];
|
|
1838
|
+
}
|
|
1839
|
+
function quaternionFromArray(values, offset) {
|
|
1840
|
+
return [
|
|
1841
|
+
values[offset],
|
|
1842
|
+
values[offset + 1],
|
|
1843
|
+
values[offset + 2],
|
|
1844
|
+
values[offset + 3]
|
|
1845
|
+
];
|
|
1846
|
+
}
|
|
1847
|
+
function quaternionFromXmat(values, offset) {
|
|
1848
|
+
const matrix = new THREE12.Matrix4();
|
|
1849
|
+
matrix.set(
|
|
1850
|
+
values[offset],
|
|
1851
|
+
values[offset + 1],
|
|
1852
|
+
values[offset + 2],
|
|
1853
|
+
0,
|
|
1854
|
+
values[offset + 3],
|
|
1855
|
+
values[offset + 4],
|
|
1856
|
+
values[offset + 5],
|
|
1857
|
+
0,
|
|
1858
|
+
values[offset + 6],
|
|
1859
|
+
values[offset + 7],
|
|
1860
|
+
values[offset + 8],
|
|
1861
|
+
0,
|
|
1862
|
+
0,
|
|
1863
|
+
0,
|
|
1864
|
+
0,
|
|
1865
|
+
1
|
|
1866
|
+
);
|
|
1867
|
+
const quaternion = new THREE12.Quaternion().setFromRotationMatrix(matrix);
|
|
1868
|
+
return [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
|
|
1869
|
+
}
|
|
1870
|
+
function omitResolvedCameraSelectors(options) {
|
|
1871
|
+
const { cameraName, siteName, bodyName, ...rest } = options;
|
|
1872
|
+
return rest;
|
|
1873
|
+
}
|
|
1874
|
+
function countMountedCameraSelectors(options) {
|
|
1875
|
+
return Number(Boolean(options.cameraName)) + Number(Boolean(options.siteName)) + Number(Boolean(options.bodyName));
|
|
1876
|
+
}
|
|
1877
|
+
function assertMatchingMountedCameraSource(key, requested, source) {
|
|
1878
|
+
const selectorCount = countMountedCameraSelectors(requested);
|
|
1879
|
+
if (selectorCount !== 1) {
|
|
1880
|
+
throw new Error(
|
|
1881
|
+
`Camera sequence stream "${key}" must provide exactly one mounted MuJoCo cameraName, siteName, or bodyName selector.`
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
if (!isMountedCameraFrameCaptureSource(source)) {
|
|
1885
|
+
throw new Error(
|
|
1886
|
+
`Camera sequence stream "${key}" resolved to ${source.kind}; use a MuJoCo-mounted camera, site, or body selector for sequence recording.`
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
if (requested.cameraName && (source.kind !== "mujoco-camera" || source.cameraName !== requested.cameraName) || requested.siteName && (source.kind !== "mujoco-site" || source.siteName !== requested.siteName) || requested.bodyName && (source.kind !== "mujoco-body" || source.bodyName !== requested.bodyName)) {
|
|
1890
|
+
throw new Error(
|
|
1891
|
+
`Camera sequence stream "${key}" resolved to ${source.kind}:${getCameraFrameCaptureSourceTarget(source)} instead of the requested mounted selector.`
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1605
1895
|
var MujocoSimContext = createContext(null);
|
|
1606
1896
|
function useMujocoContext() {
|
|
1607
1897
|
const ctx = useContext(MujocoSimContext);
|
|
@@ -1721,6 +2011,9 @@ function MujocoSimProvider({
|
|
|
1721
2011
|
useEffect(() => {
|
|
1722
2012
|
configRef.current = config;
|
|
1723
2013
|
}, [config]);
|
|
2014
|
+
useEffect(() => {
|
|
2015
|
+
mujocoRef.current = mujoco;
|
|
2016
|
+
}, [mujoco]);
|
|
1724
2017
|
useEffect(() => {
|
|
1725
2018
|
pausedRef.current = paused ?? false;
|
|
1726
2019
|
}, [paused]);
|
|
@@ -1768,6 +2061,7 @@ function MujocoSimProvider({
|
|
|
1768
2061
|
result.mjData.delete();
|
|
1769
2062
|
return;
|
|
1770
2063
|
}
|
|
2064
|
+
mujocoRef.current = mujoco;
|
|
1771
2065
|
mjModelRef.current = result.mjModel;
|
|
1772
2066
|
mjDataRef.current = result.mjData;
|
|
1773
2067
|
physicsAccumulatorRef.current = 0;
|
|
@@ -1833,7 +2127,7 @@ function MujocoSimProvider({
|
|
|
1833
2127
|
if (!interpolateRef.current) {
|
|
1834
2128
|
if (stepsToRunRef.current > 0) {
|
|
1835
2129
|
for (let s = 0; s < stepsToRunRef.current; s++) {
|
|
1836
|
-
|
|
2130
|
+
mujocoRef.current.mj_step(model, data);
|
|
1837
2131
|
}
|
|
1838
2132
|
stepsToRunRef.current = 0;
|
|
1839
2133
|
} else {
|
|
@@ -1842,7 +2136,7 @@ function MujocoSimProvider({
|
|
|
1842
2136
|
const frameTime = clampedDelta * speedRef.current;
|
|
1843
2137
|
while (data.time - startSimTime < frameTime) {
|
|
1844
2138
|
for (let s = 0; s < numSubsteps; s++) {
|
|
1845
|
-
|
|
2139
|
+
mujocoRef.current.mj_step(model, data);
|
|
1846
2140
|
}
|
|
1847
2141
|
}
|
|
1848
2142
|
}
|
|
@@ -1850,7 +2144,7 @@ function MujocoSimProvider({
|
|
|
1850
2144
|
ensureInterpolationBuffers(model);
|
|
1851
2145
|
copyBodyPose(data, interpolationStateRef.current.previousXpos, interpolationStateRef.current.previousXquat);
|
|
1852
2146
|
for (let s = 0; s < stepsToRunRef.current; s++) {
|
|
1853
|
-
|
|
2147
|
+
mujocoRef.current.mj_step(model, data);
|
|
1854
2148
|
}
|
|
1855
2149
|
copyBodyPose(data, interpolationStateRef.current.currentXpos, interpolationStateRef.current.currentXquat);
|
|
1856
2150
|
interpolationStateRef.current.alpha = 1;
|
|
@@ -1865,7 +2159,7 @@ function MujocoSimProvider({
|
|
|
1865
2159
|
while (physicsAccumulatorRef.current >= stepDt) {
|
|
1866
2160
|
copyBodyPose(data, interpolationStateRef.current.previousXpos, interpolationStateRef.current.previousXquat);
|
|
1867
2161
|
for (let s = 0; s < numSubsteps; s++) {
|
|
1868
|
-
|
|
2162
|
+
mujocoRef.current.mj_step(model, data);
|
|
1869
2163
|
}
|
|
1870
2164
|
copyBodyPose(data, interpolationStateRef.current.currentXpos, interpolationStateRef.current.currentXquat);
|
|
1871
2165
|
physicsAccumulatorRef.current -= stepDt;
|
|
@@ -1904,7 +2198,7 @@ function MujocoSimProvider({
|
|
|
1904
2198
|
const model = mjModelRef.current;
|
|
1905
2199
|
const data = mjDataRef.current;
|
|
1906
2200
|
if (!model || !data) return;
|
|
1907
|
-
|
|
2201
|
+
mujocoRef.current.mj_resetData(model, data);
|
|
1908
2202
|
const homeJoints = configRef.current.homeJoints;
|
|
1909
2203
|
if (homeJoints) {
|
|
1910
2204
|
const homeCount = Math.min(homeJoints.length, model.nu);
|
|
@@ -1917,7 +2211,7 @@ function MujocoSimProvider({
|
|
|
1917
2211
|
}
|
|
1918
2212
|
}
|
|
1919
2213
|
configRef.current.onReset?.({ model, data });
|
|
1920
|
-
|
|
2214
|
+
mujocoRef.current.mj_forward(model, data);
|
|
1921
2215
|
for (const cb of resetCallbacks.current) {
|
|
1922
2216
|
cb();
|
|
1923
2217
|
}
|
|
@@ -1935,7 +2229,7 @@ function MujocoSimProvider({
|
|
|
1935
2229
|
const step = useCallback((n = 1) => {
|
|
1936
2230
|
stepsToRunRef.current = n;
|
|
1937
2231
|
}, []);
|
|
1938
|
-
|
|
2232
|
+
useCallback((steps = 1) => {
|
|
1939
2233
|
const model = mjModelRef.current;
|
|
1940
2234
|
const data = mjDataRef.current;
|
|
1941
2235
|
if (!model || !data) return false;
|
|
@@ -1946,7 +2240,7 @@ function MujocoSimProvider({
|
|
|
1946
2240
|
for (const cb of beforeStepCallbacks.current) {
|
|
1947
2241
|
cb({ model, data });
|
|
1948
2242
|
}
|
|
1949
|
-
|
|
2243
|
+
mujocoRef.current.mj_step(model, data);
|
|
1950
2244
|
for (const cb of afterStepCallbacks.current) {
|
|
1951
2245
|
cb({ model, data });
|
|
1952
2246
|
}
|
|
@@ -1984,7 +2278,7 @@ function MujocoSimProvider({
|
|
|
1984
2278
|
data.ctrl.set(snapshot.ctrl);
|
|
1985
2279
|
if (snapshot.act.length > 0) data.act.set(snapshot.act);
|
|
1986
2280
|
data.qfrc_applied.set(snapshot.qfrc_applied);
|
|
1987
|
-
|
|
2281
|
+
mujocoRef.current.mj_forward(model, data);
|
|
1988
2282
|
}, [mujoco]);
|
|
1989
2283
|
const setQpos = useCallback((values) => {
|
|
1990
2284
|
const model = mjModelRef.current;
|
|
@@ -1992,7 +2286,7 @@ function MujocoSimProvider({
|
|
|
1992
2286
|
if (!model || !data) return;
|
|
1993
2287
|
const arr = values instanceof Float64Array ? values : new Float64Array(values);
|
|
1994
2288
|
data.qpos.set(arr.subarray(0, Math.min(arr.length, model.nq)));
|
|
1995
|
-
|
|
2289
|
+
mujocoRef.current.mj_forward(model, data);
|
|
1996
2290
|
}, [mujoco]);
|
|
1997
2291
|
const setQvel = useCallback((values) => {
|
|
1998
2292
|
const data = mjDataRef.current;
|
|
@@ -2227,6 +2521,88 @@ function MujocoSimProvider({
|
|
|
2227
2521
|
}
|
|
2228
2522
|
return result;
|
|
2229
2523
|
}, []);
|
|
2524
|
+
const getCameras = useCallback(() => {
|
|
2525
|
+
const model = mjModelRef.current;
|
|
2526
|
+
if (!model) return [];
|
|
2527
|
+
const ncam = model.ncam ?? 0;
|
|
2528
|
+
const nameAddresses = model.name_camadr;
|
|
2529
|
+
if (!ncam || !nameAddresses) return [];
|
|
2530
|
+
const result = [];
|
|
2531
|
+
for (let i = 0; i < ncam; i += 1) {
|
|
2532
|
+
const posOffset = i * 3;
|
|
2533
|
+
const quatOffset = i * 4;
|
|
2534
|
+
result.push({
|
|
2535
|
+
id: i,
|
|
2536
|
+
name: getName(model, nameAddresses[i]),
|
|
2537
|
+
bodyId: model.cam_bodyid?.[i] ?? -1,
|
|
2538
|
+
fov: model.cam_fovy?.[i] ?? null,
|
|
2539
|
+
position: model.cam_pos ? vector3FromArray(model.cam_pos, posOffset) : null,
|
|
2540
|
+
quaternion: model.cam_quat ? quaternionFromArray(model.cam_quat, quatOffset) : null
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
return result;
|
|
2544
|
+
}, []);
|
|
2545
|
+
const resolveCameraCaptureOptions = useCallback(
|
|
2546
|
+
(options = {}) => {
|
|
2547
|
+
const model = mjModelRef.current;
|
|
2548
|
+
const data = mjDataRef.current;
|
|
2549
|
+
if (!model || !data) {
|
|
2550
|
+
return options;
|
|
2551
|
+
}
|
|
2552
|
+
const baseOptions = omitResolvedCameraSelectors(options);
|
|
2553
|
+
if (options.cameraName) {
|
|
2554
|
+
const cameraId = findCameraByName(model, options.cameraName);
|
|
2555
|
+
if (cameraId < 0) {
|
|
2556
|
+
throw new Error(`MuJoCo camera "${options.cameraName}" was not found.`);
|
|
2557
|
+
}
|
|
2558
|
+
const position = data.cam_xpos ? vector3FromArray(data.cam_xpos, cameraId * 3) : model.cam_pos ? vector3FromArray(model.cam_pos, cameraId * 3) : void 0;
|
|
2559
|
+
const quaternion = data.cam_xmat ? quaternionFromXmat(data.cam_xmat, cameraId * 9) : model.cam_quat ? quaternionFromArray(model.cam_quat, cameraId * 4) : void 0;
|
|
2560
|
+
if (!position || !quaternion) {
|
|
2561
|
+
throw new Error(
|
|
2562
|
+
`MuJoCo camera "${options.cameraName}" does not expose a capture pose.`
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2565
|
+
return {
|
|
2566
|
+
...baseOptions,
|
|
2567
|
+
position,
|
|
2568
|
+
quaternion,
|
|
2569
|
+
fov: options.fov ?? model.cam_fovy?.[cameraId],
|
|
2570
|
+
source: { kind: "mujoco-camera", cameraName: options.cameraName }
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
if (options.siteName) {
|
|
2574
|
+
const siteId = findSiteByName(model, options.siteName);
|
|
2575
|
+
if (siteId < 0) {
|
|
2576
|
+
throw new Error(`MuJoCo site "${options.siteName}" was not found.`);
|
|
2577
|
+
}
|
|
2578
|
+
return {
|
|
2579
|
+
...baseOptions,
|
|
2580
|
+
position: vector3FromArray(data.site_xpos, siteId * 3),
|
|
2581
|
+
quaternion: quaternionFromXmat(data.site_xmat, siteId * 9),
|
|
2582
|
+
source: { kind: "mujoco-site", siteName: options.siteName }
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
if (options.bodyName) {
|
|
2586
|
+
const bodyId = findBodyByName(model, options.bodyName);
|
|
2587
|
+
if (bodyId < 0) {
|
|
2588
|
+
throw new Error(`MuJoCo body "${options.bodyName}" was not found.`);
|
|
2589
|
+
}
|
|
2590
|
+
if (!data.xmat) {
|
|
2591
|
+
throw new Error(
|
|
2592
|
+
`MuJoCo body "${options.bodyName}" does not expose world orientation data.`
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
return {
|
|
2596
|
+
...baseOptions,
|
|
2597
|
+
position: vector3FromArray(data.xpos, bodyId * 3),
|
|
2598
|
+
quaternion: quaternionFromXmat(data.xmat, bodyId * 9),
|
|
2599
|
+
source: { kind: "mujoco-body", bodyName: options.bodyName }
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
return options;
|
|
2603
|
+
},
|
|
2604
|
+
[]
|
|
2605
|
+
);
|
|
2230
2606
|
const getModelOption = useCallback(() => {
|
|
2231
2607
|
const model = mjModelRef.current;
|
|
2232
2608
|
if (!model?.opt) return { timestep: 2e-3, gravity: [0, 0, -9.81], integrator: 0 };
|
|
@@ -2303,7 +2679,7 @@ function MujocoSimProvider({
|
|
|
2303
2679
|
const qvelOffset = keyId * model.nv;
|
|
2304
2680
|
for (let i = 0; i < model.nv; i++) data.qvel[i] = model.key_qvel[qvelOffset + i];
|
|
2305
2681
|
}
|
|
2306
|
-
|
|
2682
|
+
mujocoRef.current.mj_forward(model, data);
|
|
2307
2683
|
for (const cb of resetCallbacks.current) {
|
|
2308
2684
|
cb();
|
|
2309
2685
|
}
|
|
@@ -2414,67 +2790,187 @@ function MujocoSimProvider({
|
|
|
2414
2790
|
);
|
|
2415
2791
|
const captureCameraFrameApi = useCallback(
|
|
2416
2792
|
(options = {}) => {
|
|
2417
|
-
return captureCameraFrame(
|
|
2793
|
+
return captureCameraFrame(
|
|
2794
|
+
gl,
|
|
2795
|
+
scene,
|
|
2796
|
+
camera,
|
|
2797
|
+
resolveCameraCaptureOptions(options)
|
|
2798
|
+
);
|
|
2418
2799
|
},
|
|
2419
|
-
[camera, gl, scene]
|
|
2800
|
+
[camera, gl, resolveCameraCaptureOptions, scene]
|
|
2420
2801
|
);
|
|
2421
2802
|
const captureCameraFrameBlobApi = useCallback(
|
|
2422
2803
|
(options = {}) => {
|
|
2423
|
-
return captureCameraFrameBlob(
|
|
2804
|
+
return captureCameraFrameBlob(
|
|
2805
|
+
gl,
|
|
2806
|
+
scene,
|
|
2807
|
+
camera,
|
|
2808
|
+
resolveCameraCaptureOptions(options)
|
|
2809
|
+
);
|
|
2424
2810
|
},
|
|
2425
|
-
[camera, gl, scene]
|
|
2811
|
+
[camera, gl, resolveCameraCaptureOptions, scene]
|
|
2426
2812
|
);
|
|
2427
2813
|
const recordCameraSequenceApi = useCallback(
|
|
2428
2814
|
async (options) => {
|
|
2429
2815
|
const frameCount = Math.max(0, Math.floor(options.frames));
|
|
2430
|
-
const stepsPerFrame = Math.max(
|
|
2816
|
+
const stepsPerFrame = Math.max(0, Math.floor(options.stepsPerFrame ?? 1));
|
|
2431
2817
|
const cameras = options.cameras;
|
|
2432
2818
|
const frames = [];
|
|
2819
|
+
const cameraSummaries = {};
|
|
2433
2820
|
const wasPaused = pausedRef.current;
|
|
2821
|
+
const retainFrames = options.retainFrames ?? true;
|
|
2822
|
+
const requireMountedSources = options.requireMountedSources ?? true;
|
|
2823
|
+
let recordedFrameCount = 0;
|
|
2824
|
+
async function stepCameraSequence(frameIndex, steps) {
|
|
2825
|
+
const model = mjModelRef.current;
|
|
2826
|
+
const data = mjDataRef.current;
|
|
2827
|
+
if (!model || !data) {
|
|
2828
|
+
throw new Error("MuJoCo scene is not ready for camera sequence stepping.");
|
|
2829
|
+
}
|
|
2830
|
+
for (let stepIndex = 0; stepIndex < steps; stepIndex += 1) {
|
|
2831
|
+
for (let i = 0; i < model.nv; i += 1) {
|
|
2832
|
+
data.qfrc_applied[i] = 0;
|
|
2833
|
+
}
|
|
2834
|
+
await options.onBeforeStep?.({
|
|
2835
|
+
frameIndex,
|
|
2836
|
+
stepIndex,
|
|
2837
|
+
time: data.time,
|
|
2838
|
+
model,
|
|
2839
|
+
data
|
|
2840
|
+
});
|
|
2841
|
+
for (const cb of beforeStepCallbacks.current) {
|
|
2842
|
+
cb({ model, data });
|
|
2843
|
+
}
|
|
2844
|
+
mujocoRef.current.mj_step(model, data);
|
|
2845
|
+
for (const cb of afterStepCallbacks.current) {
|
|
2846
|
+
cb({ model, data });
|
|
2847
|
+
}
|
|
2848
|
+
onStepRef.current?.({ time: data.time, model, data });
|
|
2849
|
+
await options.onAfterStep?.({
|
|
2850
|
+
frameIndex,
|
|
2851
|
+
stepIndex,
|
|
2852
|
+
time: data.time,
|
|
2853
|
+
model,
|
|
2854
|
+
data
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
physicsAccumulatorRef.current = 0;
|
|
2858
|
+
interpolationStateRef.current.valid = false;
|
|
2859
|
+
}
|
|
2434
2860
|
if (frameCount === 0 || cameras.length === 0) {
|
|
2435
2861
|
return {
|
|
2436
2862
|
frames,
|
|
2437
2863
|
cameraKeys: cameras.map((sequenceCamera) => sequenceCamera.key),
|
|
2864
|
+
cameraSummaries,
|
|
2438
2865
|
frameCount: 0
|
|
2439
2866
|
};
|
|
2440
2867
|
}
|
|
2868
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
2869
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
2870
|
+
if (mjModelRef.current && mjDataRef.current) break;
|
|
2871
|
+
await waitForNextAnimationFrame2();
|
|
2872
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
2873
|
+
}
|
|
2874
|
+
if (!mjModelRef.current || !mjDataRef.current) {
|
|
2875
|
+
throw new Error("MuJoCo scene is not ready for camera sequence recording.");
|
|
2876
|
+
}
|
|
2877
|
+
const captureSessions = cameras.map((sequenceCamera) => {
|
|
2878
|
+
const { key, ...captureOptions } = sequenceCamera;
|
|
2879
|
+
const initialCaptureOptions = resolveCameraCaptureOptions(captureOptions);
|
|
2880
|
+
const mountedSource = initialCaptureOptions.source;
|
|
2881
|
+
if (requireMountedSources) {
|
|
2882
|
+
assertMatchingMountedCameraSource(
|
|
2883
|
+
key,
|
|
2884
|
+
captureOptions,
|
|
2885
|
+
mountedSource ?? { kind: "fallback-camera" }
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
return {
|
|
2889
|
+
key,
|
|
2890
|
+
captureOptions,
|
|
2891
|
+
mountedSource,
|
|
2892
|
+
session: createCameraFrameCaptureSession(
|
|
2893
|
+
gl,
|
|
2894
|
+
scene,
|
|
2895
|
+
camera,
|
|
2896
|
+
initialCaptureOptions
|
|
2897
|
+
)
|
|
2898
|
+
};
|
|
2899
|
+
});
|
|
2441
2900
|
try {
|
|
2442
2901
|
pausedRef.current = true;
|
|
2443
2902
|
stepsToRunRef.current = 0;
|
|
2444
2903
|
if (options.reset) reset();
|
|
2445
2904
|
for (let frameIndex = 0; frameIndex < frameCount; frameIndex += 1) {
|
|
2446
|
-
|
|
2447
|
-
|
|
2905
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
2906
|
+
if (stepsPerFrame > 0 && (frameIndex > 0 || options.captureInitialFrame === false)) {
|
|
2907
|
+
await stepCameraSequence(frameIndex, stepsPerFrame);
|
|
2448
2908
|
}
|
|
2449
2909
|
await waitForNextAnimationFrame2();
|
|
2910
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
2911
|
+
const model = mjModelRef.current;
|
|
2912
|
+
const data = mjDataRef.current;
|
|
2913
|
+
if (!model || !data) {
|
|
2914
|
+
throw new Error("MuJoCo scene is not ready for camera sequence sampling.");
|
|
2915
|
+
}
|
|
2916
|
+
await options.onSample?.({
|
|
2917
|
+
frameIndex,
|
|
2918
|
+
time: data.time,
|
|
2919
|
+
model,
|
|
2920
|
+
data
|
|
2921
|
+
});
|
|
2450
2922
|
const cameraFrames = {};
|
|
2451
|
-
for (const
|
|
2452
|
-
const
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2923
|
+
for (const { key, captureOptions, mountedSource, session } of captureSessions) {
|
|
2924
|
+
const resolvedCaptureOptions = resolveCameraCaptureOptions(captureOptions);
|
|
2925
|
+
const cameraFrame = session.captureDataUrl({
|
|
2926
|
+
...resolvedCaptureOptions,
|
|
2927
|
+
source: mountedSource ?? resolvedCaptureOptions.source
|
|
2928
|
+
});
|
|
2929
|
+
if (requireMountedSources) {
|
|
2930
|
+
assertMatchingMountedCameraSource(
|
|
2931
|
+
key,
|
|
2932
|
+
captureOptions,
|
|
2933
|
+
cameraFrame.source
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
cameraSummaries[key] = {
|
|
2937
|
+
key,
|
|
2938
|
+
width: cameraFrame.width,
|
|
2939
|
+
height: cameraFrame.height,
|
|
2940
|
+
source: cameraFrame.source,
|
|
2941
|
+
frameCount: (cameraSummaries[key]?.frameCount ?? 0) + 1,
|
|
2942
|
+
firstFrameIndex: cameraSummaries[key]?.firstFrameIndex ?? frameIndex,
|
|
2943
|
+
lastFrameIndex: frameIndex,
|
|
2944
|
+
firstTimestamp: cameraSummaries[key]?.firstTimestamp ?? data.time,
|
|
2945
|
+
lastTimestamp: data.time
|
|
2946
|
+
};
|
|
2947
|
+
cameraFrames[key] = cameraFrame;
|
|
2459
2948
|
}
|
|
2460
2949
|
const frame = {
|
|
2461
2950
|
frameIndex,
|
|
2462
2951
|
time: getTime(),
|
|
2463
2952
|
cameras: cameraFrames
|
|
2464
2953
|
};
|
|
2465
|
-
|
|
2954
|
+
if (retainFrames) {
|
|
2955
|
+
frames.push(frame);
|
|
2956
|
+
}
|
|
2957
|
+
recordedFrameCount += 1;
|
|
2466
2958
|
await options.onFrame?.(frame);
|
|
2467
2959
|
}
|
|
2468
2960
|
} finally {
|
|
2961
|
+
for (const { session } of captureSessions) {
|
|
2962
|
+
session.dispose();
|
|
2963
|
+
}
|
|
2469
2964
|
pausedRef.current = wasPaused;
|
|
2470
2965
|
}
|
|
2471
2966
|
return {
|
|
2472
2967
|
frames,
|
|
2473
2968
|
cameraKeys: cameras.map((sequenceCamera) => sequenceCamera.key),
|
|
2474
|
-
|
|
2969
|
+
cameraSummaries,
|
|
2970
|
+
frameCount: recordedFrameCount
|
|
2475
2971
|
};
|
|
2476
2972
|
},
|
|
2477
|
-
[camera, getTime, gl, reset,
|
|
2973
|
+
[camera, getTime, gl, mujoco, reset, resolveCameraCaptureOptions, scene]
|
|
2478
2974
|
);
|
|
2479
2975
|
const project2DTo3D = useCallback(
|
|
2480
2976
|
(x, y, cameraPos, lookAt) => {
|
|
@@ -2571,6 +3067,7 @@ function MujocoSimProvider({
|
|
|
2571
3067
|
getSites,
|
|
2572
3068
|
getActuators: getActuatorsApi,
|
|
2573
3069
|
getSensors,
|
|
3070
|
+
getCameras,
|
|
2574
3071
|
getModelOption,
|
|
2575
3072
|
setGravity,
|
|
2576
3073
|
setTimestep: setTimestepApi,
|
|
@@ -2629,6 +3126,7 @@ function MujocoSimProvider({
|
|
|
2629
3126
|
getSites,
|
|
2630
3127
|
getActuatorsApi,
|
|
2631
3128
|
getSensors,
|
|
3129
|
+
getCameras,
|
|
2632
3130
|
getModelOption,
|
|
2633
3131
|
setGravity,
|
|
2634
3132
|
setTimestepApi,
|
|
@@ -5421,6 +5919,58 @@ function useCameraSequenceRecorder() {
|
|
|
5421
5919
|
reset
|
|
5422
5920
|
};
|
|
5423
5921
|
}
|
|
5922
|
+
function useMountedCameraSequenceRecorder(defaultOptions = {}) {
|
|
5923
|
+
const mujoco = useMujoco();
|
|
5924
|
+
const [status, setStatus] = useState("idle");
|
|
5925
|
+
const [error, setError] = useState(null);
|
|
5926
|
+
const reset = useCallback(() => {
|
|
5927
|
+
setStatus("idle");
|
|
5928
|
+
setError(null);
|
|
5929
|
+
}, []);
|
|
5930
|
+
const createPlan = useCallback(
|
|
5931
|
+
(cameraKeys, options = {}) => {
|
|
5932
|
+
if (!mujoco.api) {
|
|
5933
|
+
throw new Error("MuJoCo scene is not ready for mounted camera sequence planning.");
|
|
5934
|
+
}
|
|
5935
|
+
return createMountedCameraFrameSequencePlanFromApi(mujoco.api, cameraKeys, {
|
|
5936
|
+
...defaultOptions,
|
|
5937
|
+
...options
|
|
5938
|
+
});
|
|
5939
|
+
},
|
|
5940
|
+
[defaultOptions, mujoco.api]
|
|
5941
|
+
);
|
|
5942
|
+
const record = useCallback(
|
|
5943
|
+
async (options) => {
|
|
5944
|
+
if (!mujoco.api) {
|
|
5945
|
+
throw new Error("MuJoCo scene is not ready for mounted camera sequence recording.");
|
|
5946
|
+
}
|
|
5947
|
+
setStatus("capturing");
|
|
5948
|
+
setError(null);
|
|
5949
|
+
try {
|
|
5950
|
+
const result = await recordMountedCameraFrameSequence(mujoco.api, {
|
|
5951
|
+
...defaultOptions,
|
|
5952
|
+
...options
|
|
5953
|
+
});
|
|
5954
|
+
setStatus("captured");
|
|
5955
|
+
return result;
|
|
5956
|
+
} catch (nextError) {
|
|
5957
|
+
const error2 = nextError instanceof Error ? nextError : new Error("Unable to record the requested mounted camera sequence.");
|
|
5958
|
+
setError(error2);
|
|
5959
|
+
setStatus("error");
|
|
5960
|
+
throw error2;
|
|
5961
|
+
}
|
|
5962
|
+
},
|
|
5963
|
+
[defaultOptions, mujoco.api]
|
|
5964
|
+
);
|
|
5965
|
+
return {
|
|
5966
|
+
status,
|
|
5967
|
+
error,
|
|
5968
|
+
isRecording: status === "capturing",
|
|
5969
|
+
createPlan,
|
|
5970
|
+
record,
|
|
5971
|
+
reset
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5424
5974
|
function useCtrlNoise(config = {}) {
|
|
5425
5975
|
const { mjModelRef } = useMujocoContext();
|
|
5426
5976
|
const configRef = useRef(config);
|
|
@@ -5585,6 +6135,12 @@ function useCameraAnimation() {
|
|
|
5585
6135
|
*
|
|
5586
6136
|
* Offscreen camera-frame capture for R3F/MuJoCo scenes.
|
|
5587
6137
|
*/
|
|
6138
|
+
/**
|
|
6139
|
+
* @license
|
|
6140
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6141
|
+
*
|
|
6142
|
+
* Helpers for resolving dataset camera streams to mounted MuJoCo resources.
|
|
6143
|
+
*/
|
|
5588
6144
|
/**
|
|
5589
6145
|
* @license
|
|
5590
6146
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -5730,6 +6286,12 @@ function useCameraAnimation() {
|
|
|
5730
6286
|
*
|
|
5731
6287
|
* React state wrapper around fixed-camera simulation sequence recording.
|
|
5732
6288
|
*/
|
|
6289
|
+
/**
|
|
6290
|
+
* @license
|
|
6291
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6292
|
+
*
|
|
6293
|
+
* React state wrapper for named MuJoCo camera/site/body sequence recording.
|
|
6294
|
+
*/
|
|
5733
6295
|
/**
|
|
5734
6296
|
* @license
|
|
5735
6297
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -5760,6 +6322,6 @@ function useCameraAnimation() {
|
|
|
5760
6322
|
* useCameraAnimation — composable camera animation hook.
|
|
5761
6323
|
*/
|
|
5762
6324
|
|
|
5763
|
-
export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider,
|
|
6325
|
+
export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MountedCameraFrameSequenceReadinessStatus, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, buildObservation, captureCameraFrame, captureCameraFrameBlob, captureFrame, captureFrameBlob, createCameraFrameCaptureSession, createContiguousControlGroup, createController, createControllerHook, createMountedCameraFrameSequencePlan, createMountedCameraFrameSequencePlanFromApi, createMountedCameraFrameSequenceReadiness, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getCameraFrameCaptureSourceTarget, getControlMap, getMountedCameraFrameCaptureSource, getName, isMountedCameraFrameCaptureSource, loadScene, recordMountedCameraFrameSequence, renderCameraFrameToCanvas, resolveControlGroup, resolveMountedCameraFrameSource, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMountedCameraSequenceRecorder, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
|
|
5764
6326
|
//# sourceMappingURL=index.js.map
|
|
5765
6327
|
//# sourceMappingURL=index.js.map
|