mujoco-react 9.2.0 → 9.4.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 +225 -15
- package/dist/{chunk-33CV6HSV.js → chunk-VDSEPZYQ.js} +303 -14
- package/dist/chunk-VDSEPZYQ.js.map +1 -0
- package/dist/index.d.ts +274 -7
- package/dist/index.js +1172 -131
- 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-BuJ4boaq.d.ts} +160 -5
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +14 -7
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SplatCollisionProxyPreview.tsx +350 -0
- package/src/components/VisualScenario.tsx +287 -11
- package/src/core/MujocoSimProvider.tsx +374 -30
- package/src/core/SceneLoader.ts +13 -0
- package/src/hooks/useMountedCameraSequenceRecorder.ts +155 -0
- package/src/index.ts +80 -0
- package/src/rendering/cameraFrameCapture.ts +195 -26
- package/src/rendering/cameraFrameSource.ts +747 -0
- package/src/spark.tsx +144 -0
- package/src/types.ts +166 -4
- package/src/vite.ts +14 -6
- package/dist/chunk-33CV6HSV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { withContacts, getContact } from './chunk-VDSEPZYQ.js';
|
|
2
|
+
export { RobotActuators, RobotBodies, RobotCameras, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerRobotResources, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-VDSEPZYQ.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,396 @@ 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 MountedCameraFrameSourceSuggestionMatch = {
|
|
1582
|
+
Direct: "direct",
|
|
1583
|
+
Alias: "alias",
|
|
1584
|
+
Normalized: "normalized",
|
|
1585
|
+
Prefix: "prefix",
|
|
1586
|
+
Suffix: "suffix",
|
|
1587
|
+
Contains: "contains"
|
|
1588
|
+
};
|
|
1589
|
+
var MountedCameraFrameSequenceReadinessStatus = {
|
|
1590
|
+
Ready: "ready",
|
|
1591
|
+
Partial: "partial",
|
|
1592
|
+
Missing: "missing"
|
|
1593
|
+
};
|
|
1594
|
+
var MountedCameraFrameSequenceManifestStatus = {
|
|
1595
|
+
Complete: "complete",
|
|
1596
|
+
Partial: "partial",
|
|
1597
|
+
Missing: "missing"
|
|
1598
|
+
};
|
|
1599
|
+
function getResourceName(resource) {
|
|
1600
|
+
if (!resource) return null;
|
|
1601
|
+
return typeof resource === "string" ? resource : resource.name ?? null;
|
|
1602
|
+
}
|
|
1603
|
+
function createNameSet(resources) {
|
|
1604
|
+
return new Set(
|
|
1605
|
+
(resources ?? []).map((resource) => getResourceName(resource)).filter((name) => Boolean(name))
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
function createResourceNames(resources) {
|
|
1609
|
+
return (resources ?? []).map((resource) => getResourceName(resource)).filter((name) => Boolean(name));
|
|
1610
|
+
}
|
|
1611
|
+
function normalizeAliasCandidates(value) {
|
|
1612
|
+
if (!value) return [];
|
|
1613
|
+
return Array.isArray(value) ? value : [value];
|
|
1614
|
+
}
|
|
1615
|
+
function countMountedSelectors(selector) {
|
|
1616
|
+
return Number(Boolean(selector.cameraName)) + Number(Boolean(selector.siteName)) + Number(Boolean(selector.bodyName));
|
|
1617
|
+
}
|
|
1618
|
+
function normalizeCameraSourceName(value) {
|
|
1619
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
1620
|
+
}
|
|
1621
|
+
function createCameraSourceKeyVariants(key) {
|
|
1622
|
+
const candidates = [
|
|
1623
|
+
key,
|
|
1624
|
+
key.startsWith("observation.images.") ? key.slice("observation.images.".length) : "",
|
|
1625
|
+
key.includes(".") ? key.split(".").at(-1) ?? "" : "",
|
|
1626
|
+
key.includes("/") ? key.split("/").at(-1) ?? "" : ""
|
|
1627
|
+
];
|
|
1628
|
+
return candidates.map((candidate) => candidate.trim()).filter((candidate, index, items) => candidate && items.indexOf(candidate) === index);
|
|
1629
|
+
}
|
|
1630
|
+
function getSelectorKey(selector) {
|
|
1631
|
+
if (selector.cameraName) return `camera:${selector.cameraName}`;
|
|
1632
|
+
if (selector.siteName) return `site:${selector.siteName}`;
|
|
1633
|
+
if (selector.bodyName) return `body:${selector.bodyName}`;
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
function getMountedCameraFrameCaptureSource(selector) {
|
|
1637
|
+
if (countMountedSelectors(selector) !== 1) return null;
|
|
1638
|
+
if (selector.cameraName) {
|
|
1639
|
+
return { kind: "mujoco-camera", cameraName: selector.cameraName };
|
|
1640
|
+
}
|
|
1641
|
+
if (selector.siteName) {
|
|
1642
|
+
return { kind: "mujoco-site", siteName: selector.siteName };
|
|
1643
|
+
}
|
|
1644
|
+
if (selector.bodyName) {
|
|
1645
|
+
return { kind: "mujoco-body", bodyName: selector.bodyName };
|
|
1646
|
+
}
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
function isMountedCameraFrameCaptureSource(source) {
|
|
1650
|
+
return source.kind === "mujoco-camera" || source.kind === "mujoco-site" || source.kind === "mujoco-body";
|
|
1651
|
+
}
|
|
1652
|
+
function getCameraFrameCaptureSourceTarget(source) {
|
|
1653
|
+
if (source.kind === "mujoco-camera") return source.cameraName;
|
|
1654
|
+
if (source.kind === "mujoco-site") return source.siteName;
|
|
1655
|
+
if (source.kind === "mujoco-body") return source.bodyName;
|
|
1656
|
+
if (source.kind === "custom-camera") return "custom camera";
|
|
1657
|
+
if (source.kind === "explicit-pose") return "explicit pose";
|
|
1658
|
+
return "fallback camera";
|
|
1659
|
+
}
|
|
1660
|
+
function createMountedCameraFrameSourceSuggestion(key, selector, resourceName, match) {
|
|
1661
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
1662
|
+
if (!source) return null;
|
|
1663
|
+
return {
|
|
1664
|
+
key,
|
|
1665
|
+
selector,
|
|
1666
|
+
source,
|
|
1667
|
+
resourceName,
|
|
1668
|
+
resourceKind: source.kind,
|
|
1669
|
+
match
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
function addMountedCameraFrameSourceSuggestion(suggestions, seen, suggestion) {
|
|
1673
|
+
if (!suggestion) return;
|
|
1674
|
+
const selectorKey = getSelectorKey(suggestion.selector);
|
|
1675
|
+
if (!selectorKey || seen.has(selectorKey)) return;
|
|
1676
|
+
seen.add(selectorKey);
|
|
1677
|
+
suggestions.push(suggestion);
|
|
1678
|
+
}
|
|
1679
|
+
function getCameraFrameResourceMatch(key, resourceName) {
|
|
1680
|
+
if (resourceName === key) return MountedCameraFrameSourceSuggestionMatch.Direct;
|
|
1681
|
+
const normalizedResource = normalizeCameraSourceName(resourceName);
|
|
1682
|
+
if (!normalizedResource) return null;
|
|
1683
|
+
for (const variant of createCameraSourceKeyVariants(key)) {
|
|
1684
|
+
if (resourceName === variant) return MountedCameraFrameSourceSuggestionMatch.Direct;
|
|
1685
|
+
const normalizedKey = normalizeCameraSourceName(variant);
|
|
1686
|
+
if (!normalizedKey) continue;
|
|
1687
|
+
if (normalizedResource === normalizedKey) {
|
|
1688
|
+
return MountedCameraFrameSourceSuggestionMatch.Normalized;
|
|
1689
|
+
}
|
|
1690
|
+
if (normalizedResource.startsWith(`${normalizedKey}_`)) {
|
|
1691
|
+
return MountedCameraFrameSourceSuggestionMatch.Prefix;
|
|
1692
|
+
}
|
|
1693
|
+
if (normalizedResource.endsWith(`_${normalizedKey}`)) {
|
|
1694
|
+
return MountedCameraFrameSourceSuggestionMatch.Suffix;
|
|
1695
|
+
}
|
|
1696
|
+
if (normalizedResource.includes(`_${normalizedKey}_`)) {
|
|
1697
|
+
return MountedCameraFrameSourceSuggestionMatch.Contains;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1702
|
+
function isSelectorMounted(selector, cameraNames, siteNames, bodyNames) {
|
|
1703
|
+
if (countMountedSelectors(selector) !== 1) return false;
|
|
1704
|
+
return (selector.cameraName ? cameraNames.has(selector.cameraName) : false) || (selector.siteName ? siteNames.has(selector.siteName) : false) || (selector.bodyName ? bodyNames.has(selector.bodyName) : false);
|
|
1705
|
+
}
|
|
1706
|
+
function createMountedCameraFrameSourceSuggestions(key, options) {
|
|
1707
|
+
const cameraNames = createNameSet(options.cameras);
|
|
1708
|
+
const siteNames = createNameSet(options.sites);
|
|
1709
|
+
const bodyNames = createNameSet(options.bodies);
|
|
1710
|
+
const suggestions = [];
|
|
1711
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1712
|
+
for (const selector of normalizeAliasCandidates(options.aliases?.[key])) {
|
|
1713
|
+
if (!isSelectorMounted(selector, cameraNames, siteNames, bodyNames)) {
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
1717
|
+
if (!source) continue;
|
|
1718
|
+
addMountedCameraFrameSourceSuggestion(
|
|
1719
|
+
suggestions,
|
|
1720
|
+
seen,
|
|
1721
|
+
createMountedCameraFrameSourceSuggestion(
|
|
1722
|
+
key,
|
|
1723
|
+
selector,
|
|
1724
|
+
getCameraFrameCaptureSourceTarget(source),
|
|
1725
|
+
MountedCameraFrameSourceSuggestionMatch.Alias
|
|
1726
|
+
)
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
for (const cameraName of createResourceNames(options.cameras)) {
|
|
1730
|
+
const match = getCameraFrameResourceMatch(key, cameraName);
|
|
1731
|
+
if (!match) continue;
|
|
1732
|
+
addMountedCameraFrameSourceSuggestion(
|
|
1733
|
+
suggestions,
|
|
1734
|
+
seen,
|
|
1735
|
+
createMountedCameraFrameSourceSuggestion(
|
|
1736
|
+
key,
|
|
1737
|
+
{ cameraName },
|
|
1738
|
+
cameraName,
|
|
1739
|
+
match
|
|
1740
|
+
)
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
for (const siteName of createResourceNames(options.sites)) {
|
|
1744
|
+
const match = getCameraFrameResourceMatch(key, siteName);
|
|
1745
|
+
if (!match) continue;
|
|
1746
|
+
addMountedCameraFrameSourceSuggestion(
|
|
1747
|
+
suggestions,
|
|
1748
|
+
seen,
|
|
1749
|
+
createMountedCameraFrameSourceSuggestion(
|
|
1750
|
+
key,
|
|
1751
|
+
{ siteName },
|
|
1752
|
+
siteName,
|
|
1753
|
+
match
|
|
1754
|
+
)
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
for (const bodyName of createResourceNames(options.bodies)) {
|
|
1758
|
+
const match = getCameraFrameResourceMatch(key, bodyName);
|
|
1759
|
+
if (!match) continue;
|
|
1760
|
+
addMountedCameraFrameSourceSuggestion(
|
|
1761
|
+
suggestions,
|
|
1762
|
+
seen,
|
|
1763
|
+
createMountedCameraFrameSourceSuggestion(
|
|
1764
|
+
key,
|
|
1765
|
+
{ bodyName },
|
|
1766
|
+
bodyName,
|
|
1767
|
+
match
|
|
1768
|
+
)
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
return suggestions;
|
|
1772
|
+
}
|
|
1773
|
+
function resolveMountedCameraFrameSource(key, options) {
|
|
1774
|
+
const cameraNames = createNameSet(options.cameras);
|
|
1775
|
+
const siteNames = createNameSet(options.sites);
|
|
1776
|
+
const bodyNames = createNameSet(options.bodies);
|
|
1777
|
+
const directCandidates = [
|
|
1778
|
+
{ cameraName: key },
|
|
1779
|
+
{ siteName: key },
|
|
1780
|
+
{ bodyName: key }
|
|
1781
|
+
];
|
|
1782
|
+
const aliasCandidates = normalizeAliasCandidates(options.aliases?.[key]);
|
|
1783
|
+
const candidates = [...directCandidates, ...aliasCandidates];
|
|
1784
|
+
for (const selector of candidates) {
|
|
1785
|
+
if (!isSelectorMounted(selector, cameraNames, siteNames, bodyNames)) {
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
1789
|
+
if (!source) continue;
|
|
1790
|
+
return { key, selector, source };
|
|
1791
|
+
}
|
|
1792
|
+
const [suggestion] = createMountedCameraFrameSourceSuggestions(key, options);
|
|
1793
|
+
if (suggestion) {
|
|
1794
|
+
return {
|
|
1795
|
+
key,
|
|
1796
|
+
selector: suggestion.selector,
|
|
1797
|
+
source: suggestion.source
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
if (options.allowAliasFallback) {
|
|
1801
|
+
for (const selector of aliasCandidates) {
|
|
1802
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
1803
|
+
if (!source) continue;
|
|
1804
|
+
return { key, selector, source };
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return null;
|
|
1808
|
+
}
|
|
1809
|
+
function createMountedCameraFrameSequencePlan(cameraKeys, options) {
|
|
1810
|
+
const cameras = [];
|
|
1811
|
+
const resolved = {};
|
|
1812
|
+
const missingKeys = [];
|
|
1813
|
+
for (const key of cameraKeys) {
|
|
1814
|
+
const mountedSource = resolveMountedCameraFrameSource(key, options);
|
|
1815
|
+
if (!mountedSource) {
|
|
1816
|
+
missingKeys.push(key);
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
resolved[key] = mountedSource;
|
|
1820
|
+
cameras.push({
|
|
1821
|
+
key,
|
|
1822
|
+
...options.defaults,
|
|
1823
|
+
...options.cameraOptions?.[key],
|
|
1824
|
+
...mountedSource.selector,
|
|
1825
|
+
source: mountedSource.source
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
if (options.requireAll && missingKeys.length > 0) {
|
|
1829
|
+
throw new Error(
|
|
1830
|
+
`Unable to resolve mounted MuJoCo camera source${missingKeys.length === 1 ? "" : "s"} for ${missingKeys.join(", ")}.`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
return {
|
|
1834
|
+
cameraKeys: [...cameraKeys],
|
|
1835
|
+
cameras,
|
|
1836
|
+
resolved,
|
|
1837
|
+
missingKeys
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
function createMountedCameraFrameSequenceReadiness(plan) {
|
|
1841
|
+
const cameras = {};
|
|
1842
|
+
const resolvedKeys = plan.cameraKeys.filter((key) => Boolean(plan.resolved[key]));
|
|
1843
|
+
for (const key of plan.cameraKeys) {
|
|
1844
|
+
const resolved = plan.resolved[key];
|
|
1845
|
+
cameras[key] = resolved ? {
|
|
1846
|
+
key,
|
|
1847
|
+
ready: true,
|
|
1848
|
+
selector: resolved.selector,
|
|
1849
|
+
source: resolved.source,
|
|
1850
|
+
message: `Camera stream "${key}" resolves to ${resolved.source.kind}:${getCameraFrameCaptureSourceTarget(resolved.source)}.`
|
|
1851
|
+
} : {
|
|
1852
|
+
key,
|
|
1853
|
+
ready: false,
|
|
1854
|
+
message: `Camera stream "${key}" does not resolve to a mounted MuJoCo camera, site, or body.`
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
const missingKeys = [...plan.missingKeys];
|
|
1858
|
+
const ready = missingKeys.length === 0;
|
|
1859
|
+
const status = ready ? MountedCameraFrameSequenceReadinessStatus.Ready : resolvedKeys.length > 0 ? MountedCameraFrameSequenceReadinessStatus.Partial : MountedCameraFrameSequenceReadinessStatus.Missing;
|
|
1860
|
+
return {
|
|
1861
|
+
ready,
|
|
1862
|
+
status,
|
|
1863
|
+
cameraKeys: [...plan.cameraKeys],
|
|
1864
|
+
resolvedKeys,
|
|
1865
|
+
missingKeys,
|
|
1866
|
+
cameras,
|
|
1867
|
+
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(", ")}.`
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function normalizeFrameCount(frameCount) {
|
|
1871
|
+
return Number.isFinite(frameCount) && frameCount !== void 0 ? Math.max(0, Math.floor(frameCount)) : 0;
|
|
1872
|
+
}
|
|
1873
|
+
function createMountedCameraFrameSequenceManifest(result, options = {}) {
|
|
1874
|
+
const cameraKeys = [
|
|
1875
|
+
...options.cameraKeys ?? result.readiness.cameraKeys ?? result.plan.cameraKeys ?? result.cameraKeys
|
|
1876
|
+
];
|
|
1877
|
+
const expectedFrameCount = normalizeFrameCount(
|
|
1878
|
+
options.expectedFrameCount ?? result.frameCount
|
|
1879
|
+
);
|
|
1880
|
+
const recordedFrameCount = normalizeFrameCount(result.frameCount);
|
|
1881
|
+
const streamSummaries = {};
|
|
1882
|
+
const streams = [];
|
|
1883
|
+
let missingFrameCount = 0;
|
|
1884
|
+
let completeStreamCount = 0;
|
|
1885
|
+
let resolvedOrRecordedStreamCount = 0;
|
|
1886
|
+
for (const key of cameraKeys) {
|
|
1887
|
+
const summary = result.cameraSummaries[key];
|
|
1888
|
+
const readiness = result.readiness.cameras[key];
|
|
1889
|
+
const source = summary?.source ?? readiness?.source;
|
|
1890
|
+
const ready = readiness?.ready ?? Boolean(summary);
|
|
1891
|
+
const recorded = normalizeFrameCount(summary?.frameCount);
|
|
1892
|
+
const missing = Math.max(expectedFrameCount - recorded, 0);
|
|
1893
|
+
const complete2 = ready && missing === 0;
|
|
1894
|
+
const status2 = complete2 ? MountedCameraFrameSequenceManifestStatus.Complete : ready || recorded > 0 ? MountedCameraFrameSequenceManifestStatus.Partial : MountedCameraFrameSequenceManifestStatus.Missing;
|
|
1895
|
+
const target = source ? getCameraFrameCaptureSourceTarget(source) : readiness?.message ? void 0 : "missing MuJoCo camera";
|
|
1896
|
+
const message = complete2 ? `Camera stream "${key}" recorded ${recorded} of ${expectedFrameCount} frame${expectedFrameCount === 1 ? "" : "s"}.` : ready || recorded > 0 ? `Camera stream "${key}" recorded ${recorded} of ${expectedFrameCount} frame${expectedFrameCount === 1 ? "" : "s"}.` : readiness?.message ?? `Camera stream "${key}" did not record any frames.`;
|
|
1897
|
+
const stream = {
|
|
1898
|
+
key,
|
|
1899
|
+
ready,
|
|
1900
|
+
complete: complete2,
|
|
1901
|
+
status: status2,
|
|
1902
|
+
source,
|
|
1903
|
+
selector: readiness?.selector,
|
|
1904
|
+
target,
|
|
1905
|
+
width: summary?.width,
|
|
1906
|
+
height: summary?.height,
|
|
1907
|
+
expectedFrameCount,
|
|
1908
|
+
recordedFrameCount: recorded,
|
|
1909
|
+
missingFrameCount: missing,
|
|
1910
|
+
firstFrameIndex: summary?.firstFrameIndex ?? null,
|
|
1911
|
+
lastFrameIndex: summary?.lastFrameIndex ?? null,
|
|
1912
|
+
firstTimestamp: summary?.firstTimestamp ?? null,
|
|
1913
|
+
lastTimestamp: summary?.lastTimestamp ?? null,
|
|
1914
|
+
message
|
|
1915
|
+
};
|
|
1916
|
+
streamSummaries[key] = stream;
|
|
1917
|
+
streams.push(stream);
|
|
1918
|
+
missingFrameCount += missing;
|
|
1919
|
+
if (complete2) completeStreamCount += 1;
|
|
1920
|
+
if (ready || recorded > 0) resolvedOrRecordedStreamCount += 1;
|
|
1921
|
+
}
|
|
1922
|
+
const complete = result.readiness.ready && streams.length === completeStreamCount && missingFrameCount === 0;
|
|
1923
|
+
const status = complete ? MountedCameraFrameSequenceManifestStatus.Complete : resolvedOrRecordedStreamCount > 0 ? MountedCameraFrameSequenceManifestStatus.Partial : MountedCameraFrameSequenceManifestStatus.Missing;
|
|
1924
|
+
return {
|
|
1925
|
+
schema: "mujoco-react/mounted-camera-frame-sequence-manifest@1",
|
|
1926
|
+
ready: result.readiness.ready,
|
|
1927
|
+
complete,
|
|
1928
|
+
status,
|
|
1929
|
+
cameraKeys,
|
|
1930
|
+
resolvedKeys: [...result.readiness.resolvedKeys],
|
|
1931
|
+
missingKeys: [...result.readiness.missingKeys],
|
|
1932
|
+
expectedFrameCount,
|
|
1933
|
+
recordedFrameCount,
|
|
1934
|
+
missingFrameCount,
|
|
1935
|
+
streamSummaries,
|
|
1936
|
+
streams,
|
|
1937
|
+
readiness: result.readiness,
|
|
1938
|
+
message: complete ? `All ${cameraKeys.length} camera stream${cameraKeys.length === 1 ? "" : "s"} recorded ${expectedFrameCount} frame${expectedFrameCount === 1 ? "" : "s"}.` : `Mounted camera sequence coverage is ${status}.`
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
function createMountedCameraFrameSequencePlanFromApi(api, cameraKeys, options = {}) {
|
|
1942
|
+
return createMountedCameraFrameSequencePlan(cameraKeys, {
|
|
1943
|
+
...options,
|
|
1944
|
+
cameras: api.getCameras(),
|
|
1945
|
+
sites: api.getSites(),
|
|
1946
|
+
bodies: api.getBodies()
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
async function recordMountedCameraFrameSequence(api, options) {
|
|
1950
|
+
const { cameraKeys, ...restOptions } = options;
|
|
1951
|
+
const requireAll = restOptions.requireAll ?? restOptions.requireMountedSources ?? true;
|
|
1952
|
+
const plan = createMountedCameraFrameSequencePlanFromApi(
|
|
1953
|
+
api,
|
|
1954
|
+
cameraKeys,
|
|
1955
|
+
{ ...restOptions, requireAll }
|
|
1956
|
+
);
|
|
1957
|
+
const readiness = createMountedCameraFrameSequenceReadiness(plan);
|
|
1958
|
+
const result = await api.recordCameraSequence({
|
|
1959
|
+
...restOptions,
|
|
1960
|
+
cameras: plan.cameras,
|
|
1961
|
+
requireMountedSources: restOptions.requireMountedSources ?? true
|
|
1962
|
+
});
|
|
1963
|
+
return {
|
|
1964
|
+
...result,
|
|
1965
|
+
plan,
|
|
1966
|
+
readiness
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1523
1969
|
var JOINT_TYPE_NAMES2 = ["free", "ball", "slide", "hinge"];
|
|
1524
1970
|
var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
|
|
1525
1971
|
var SENSOR_TYPE_NAMES = {
|
|
@@ -1602,6 +2048,72 @@ function waitForNextAnimationFrame2() {
|
|
|
1602
2048
|
requestAnimationFrame(() => resolve());
|
|
1603
2049
|
});
|
|
1604
2050
|
}
|
|
2051
|
+
function throwIfCameraSequenceAborted(signal) {
|
|
2052
|
+
if (!signal?.aborted) return;
|
|
2053
|
+
if (typeof signal.reason === "object" && signal.reason instanceof Error) {
|
|
2054
|
+
throw signal.reason;
|
|
2055
|
+
}
|
|
2056
|
+
throw new DOMException("Camera sequence recording was aborted.", "AbortError");
|
|
2057
|
+
}
|
|
2058
|
+
function vector3FromArray(values, offset) {
|
|
2059
|
+
return [values[offset], values[offset + 1], values[offset + 2]];
|
|
2060
|
+
}
|
|
2061
|
+
function quaternionFromArray(values, offset) {
|
|
2062
|
+
return [
|
|
2063
|
+
values[offset],
|
|
2064
|
+
values[offset + 1],
|
|
2065
|
+
values[offset + 2],
|
|
2066
|
+
values[offset + 3]
|
|
2067
|
+
];
|
|
2068
|
+
}
|
|
2069
|
+
function quaternionFromXmat(values, offset) {
|
|
2070
|
+
const matrix = new THREE12.Matrix4();
|
|
2071
|
+
matrix.set(
|
|
2072
|
+
values[offset],
|
|
2073
|
+
values[offset + 1],
|
|
2074
|
+
values[offset + 2],
|
|
2075
|
+
0,
|
|
2076
|
+
values[offset + 3],
|
|
2077
|
+
values[offset + 4],
|
|
2078
|
+
values[offset + 5],
|
|
2079
|
+
0,
|
|
2080
|
+
values[offset + 6],
|
|
2081
|
+
values[offset + 7],
|
|
2082
|
+
values[offset + 8],
|
|
2083
|
+
0,
|
|
2084
|
+
0,
|
|
2085
|
+
0,
|
|
2086
|
+
0,
|
|
2087
|
+
1
|
|
2088
|
+
);
|
|
2089
|
+
const quaternion = new THREE12.Quaternion().setFromRotationMatrix(matrix);
|
|
2090
|
+
return [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
|
|
2091
|
+
}
|
|
2092
|
+
function omitResolvedCameraSelectors(options) {
|
|
2093
|
+
const { cameraName, siteName, bodyName, ...rest } = options;
|
|
2094
|
+
return rest;
|
|
2095
|
+
}
|
|
2096
|
+
function countMountedCameraSelectors(options) {
|
|
2097
|
+
return Number(Boolean(options.cameraName)) + Number(Boolean(options.siteName)) + Number(Boolean(options.bodyName));
|
|
2098
|
+
}
|
|
2099
|
+
function assertMatchingMountedCameraSource(key, requested, source) {
|
|
2100
|
+
const selectorCount = countMountedCameraSelectors(requested);
|
|
2101
|
+
if (selectorCount !== 1) {
|
|
2102
|
+
throw new Error(
|
|
2103
|
+
`Camera sequence stream "${key}" must provide exactly one mounted MuJoCo cameraName, siteName, or bodyName selector.`
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
if (!isMountedCameraFrameCaptureSource(source)) {
|
|
2107
|
+
throw new Error(
|
|
2108
|
+
`Camera sequence stream "${key}" resolved to ${source.kind}; use a MuJoCo-mounted camera, site, or body selector for sequence recording.`
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
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)) {
|
|
2112
|
+
throw new Error(
|
|
2113
|
+
`Camera sequence stream "${key}" resolved to ${source.kind}:${getCameraFrameCaptureSourceTarget(source)} instead of the requested mounted selector.`
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
1605
2117
|
var MujocoSimContext = createContext(null);
|
|
1606
2118
|
function useMujocoContext() {
|
|
1607
2119
|
const ctx = useContext(MujocoSimContext);
|
|
@@ -1721,6 +2233,9 @@ function MujocoSimProvider({
|
|
|
1721
2233
|
useEffect(() => {
|
|
1722
2234
|
configRef.current = config;
|
|
1723
2235
|
}, [config]);
|
|
2236
|
+
useEffect(() => {
|
|
2237
|
+
mujocoRef.current = mujoco;
|
|
2238
|
+
}, [mujoco]);
|
|
1724
2239
|
useEffect(() => {
|
|
1725
2240
|
pausedRef.current = paused ?? false;
|
|
1726
2241
|
}, [paused]);
|
|
@@ -1768,6 +2283,7 @@ function MujocoSimProvider({
|
|
|
1768
2283
|
result.mjData.delete();
|
|
1769
2284
|
return;
|
|
1770
2285
|
}
|
|
2286
|
+
mujocoRef.current = mujoco;
|
|
1771
2287
|
mjModelRef.current = result.mjModel;
|
|
1772
2288
|
mjDataRef.current = result.mjData;
|
|
1773
2289
|
physicsAccumulatorRef.current = 0;
|
|
@@ -1833,7 +2349,7 @@ function MujocoSimProvider({
|
|
|
1833
2349
|
if (!interpolateRef.current) {
|
|
1834
2350
|
if (stepsToRunRef.current > 0) {
|
|
1835
2351
|
for (let s = 0; s < stepsToRunRef.current; s++) {
|
|
1836
|
-
|
|
2352
|
+
mujocoRef.current.mj_step(model, data);
|
|
1837
2353
|
}
|
|
1838
2354
|
stepsToRunRef.current = 0;
|
|
1839
2355
|
} else {
|
|
@@ -1842,7 +2358,7 @@ function MujocoSimProvider({
|
|
|
1842
2358
|
const frameTime = clampedDelta * speedRef.current;
|
|
1843
2359
|
while (data.time - startSimTime < frameTime) {
|
|
1844
2360
|
for (let s = 0; s < numSubsteps; s++) {
|
|
1845
|
-
|
|
2361
|
+
mujocoRef.current.mj_step(model, data);
|
|
1846
2362
|
}
|
|
1847
2363
|
}
|
|
1848
2364
|
}
|
|
@@ -1850,7 +2366,7 @@ function MujocoSimProvider({
|
|
|
1850
2366
|
ensureInterpolationBuffers(model);
|
|
1851
2367
|
copyBodyPose(data, interpolationStateRef.current.previousXpos, interpolationStateRef.current.previousXquat);
|
|
1852
2368
|
for (let s = 0; s < stepsToRunRef.current; s++) {
|
|
1853
|
-
|
|
2369
|
+
mujocoRef.current.mj_step(model, data);
|
|
1854
2370
|
}
|
|
1855
2371
|
copyBodyPose(data, interpolationStateRef.current.currentXpos, interpolationStateRef.current.currentXquat);
|
|
1856
2372
|
interpolationStateRef.current.alpha = 1;
|
|
@@ -1865,7 +2381,7 @@ function MujocoSimProvider({
|
|
|
1865
2381
|
while (physicsAccumulatorRef.current >= stepDt) {
|
|
1866
2382
|
copyBodyPose(data, interpolationStateRef.current.previousXpos, interpolationStateRef.current.previousXquat);
|
|
1867
2383
|
for (let s = 0; s < numSubsteps; s++) {
|
|
1868
|
-
|
|
2384
|
+
mujocoRef.current.mj_step(model, data);
|
|
1869
2385
|
}
|
|
1870
2386
|
copyBodyPose(data, interpolationStateRef.current.currentXpos, interpolationStateRef.current.currentXquat);
|
|
1871
2387
|
physicsAccumulatorRef.current -= stepDt;
|
|
@@ -1904,7 +2420,7 @@ function MujocoSimProvider({
|
|
|
1904
2420
|
const model = mjModelRef.current;
|
|
1905
2421
|
const data = mjDataRef.current;
|
|
1906
2422
|
if (!model || !data) return;
|
|
1907
|
-
|
|
2423
|
+
mujocoRef.current.mj_resetData(model, data);
|
|
1908
2424
|
const homeJoints = configRef.current.homeJoints;
|
|
1909
2425
|
if (homeJoints) {
|
|
1910
2426
|
const homeCount = Math.min(homeJoints.length, model.nu);
|
|
@@ -1917,7 +2433,7 @@ function MujocoSimProvider({
|
|
|
1917
2433
|
}
|
|
1918
2434
|
}
|
|
1919
2435
|
configRef.current.onReset?.({ model, data });
|
|
1920
|
-
|
|
2436
|
+
mujocoRef.current.mj_forward(model, data);
|
|
1921
2437
|
for (const cb of resetCallbacks.current) {
|
|
1922
2438
|
cb();
|
|
1923
2439
|
}
|
|
@@ -1935,7 +2451,7 @@ function MujocoSimProvider({
|
|
|
1935
2451
|
const step = useCallback((n = 1) => {
|
|
1936
2452
|
stepsToRunRef.current = n;
|
|
1937
2453
|
}, []);
|
|
1938
|
-
|
|
2454
|
+
useCallback((steps = 1) => {
|
|
1939
2455
|
const model = mjModelRef.current;
|
|
1940
2456
|
const data = mjDataRef.current;
|
|
1941
2457
|
if (!model || !data) return false;
|
|
@@ -1946,7 +2462,7 @@ function MujocoSimProvider({
|
|
|
1946
2462
|
for (const cb of beforeStepCallbacks.current) {
|
|
1947
2463
|
cb({ model, data });
|
|
1948
2464
|
}
|
|
1949
|
-
|
|
2465
|
+
mujocoRef.current.mj_step(model, data);
|
|
1950
2466
|
for (const cb of afterStepCallbacks.current) {
|
|
1951
2467
|
cb({ model, data });
|
|
1952
2468
|
}
|
|
@@ -1984,7 +2500,7 @@ function MujocoSimProvider({
|
|
|
1984
2500
|
data.ctrl.set(snapshot.ctrl);
|
|
1985
2501
|
if (snapshot.act.length > 0) data.act.set(snapshot.act);
|
|
1986
2502
|
data.qfrc_applied.set(snapshot.qfrc_applied);
|
|
1987
|
-
|
|
2503
|
+
mujocoRef.current.mj_forward(model, data);
|
|
1988
2504
|
}, [mujoco]);
|
|
1989
2505
|
const setQpos = useCallback((values) => {
|
|
1990
2506
|
const model = mjModelRef.current;
|
|
@@ -1992,7 +2508,7 @@ function MujocoSimProvider({
|
|
|
1992
2508
|
if (!model || !data) return;
|
|
1993
2509
|
const arr = values instanceof Float64Array ? values : new Float64Array(values);
|
|
1994
2510
|
data.qpos.set(arr.subarray(0, Math.min(arr.length, model.nq)));
|
|
1995
|
-
|
|
2511
|
+
mujocoRef.current.mj_forward(model, data);
|
|
1996
2512
|
}, [mujoco]);
|
|
1997
2513
|
const setQvel = useCallback((values) => {
|
|
1998
2514
|
const data = mjDataRef.current;
|
|
@@ -2227,6 +2743,88 @@ function MujocoSimProvider({
|
|
|
2227
2743
|
}
|
|
2228
2744
|
return result;
|
|
2229
2745
|
}, []);
|
|
2746
|
+
const getCameras = useCallback(() => {
|
|
2747
|
+
const model = mjModelRef.current;
|
|
2748
|
+
if (!model) return [];
|
|
2749
|
+
const ncam = model.ncam ?? 0;
|
|
2750
|
+
const nameAddresses = model.name_camadr;
|
|
2751
|
+
if (!ncam || !nameAddresses) return [];
|
|
2752
|
+
const result = [];
|
|
2753
|
+
for (let i = 0; i < ncam; i += 1) {
|
|
2754
|
+
const posOffset = i * 3;
|
|
2755
|
+
const quatOffset = i * 4;
|
|
2756
|
+
result.push({
|
|
2757
|
+
id: i,
|
|
2758
|
+
name: getName(model, nameAddresses[i]),
|
|
2759
|
+
bodyId: model.cam_bodyid?.[i] ?? -1,
|
|
2760
|
+
fov: model.cam_fovy?.[i] ?? null,
|
|
2761
|
+
position: model.cam_pos ? vector3FromArray(model.cam_pos, posOffset) : null,
|
|
2762
|
+
quaternion: model.cam_quat ? quaternionFromArray(model.cam_quat, quatOffset) : null
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
return result;
|
|
2766
|
+
}, []);
|
|
2767
|
+
const resolveCameraCaptureOptions = useCallback(
|
|
2768
|
+
(options = {}) => {
|
|
2769
|
+
const model = mjModelRef.current;
|
|
2770
|
+
const data = mjDataRef.current;
|
|
2771
|
+
if (!model || !data) {
|
|
2772
|
+
return options;
|
|
2773
|
+
}
|
|
2774
|
+
const baseOptions = omitResolvedCameraSelectors(options);
|
|
2775
|
+
if (options.cameraName) {
|
|
2776
|
+
const cameraId = findCameraByName(model, options.cameraName);
|
|
2777
|
+
if (cameraId < 0) {
|
|
2778
|
+
throw new Error(`MuJoCo camera "${options.cameraName}" was not found.`);
|
|
2779
|
+
}
|
|
2780
|
+
const position = data.cam_xpos ? vector3FromArray(data.cam_xpos, cameraId * 3) : model.cam_pos ? vector3FromArray(model.cam_pos, cameraId * 3) : void 0;
|
|
2781
|
+
const quaternion = data.cam_xmat ? quaternionFromXmat(data.cam_xmat, cameraId * 9) : model.cam_quat ? quaternionFromArray(model.cam_quat, cameraId * 4) : void 0;
|
|
2782
|
+
if (!position || !quaternion) {
|
|
2783
|
+
throw new Error(
|
|
2784
|
+
`MuJoCo camera "${options.cameraName}" does not expose a capture pose.`
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
return {
|
|
2788
|
+
...baseOptions,
|
|
2789
|
+
position,
|
|
2790
|
+
quaternion,
|
|
2791
|
+
fov: options.fov ?? model.cam_fovy?.[cameraId],
|
|
2792
|
+
source: { kind: "mujoco-camera", cameraName: options.cameraName }
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
if (options.siteName) {
|
|
2796
|
+
const siteId = findSiteByName(model, options.siteName);
|
|
2797
|
+
if (siteId < 0) {
|
|
2798
|
+
throw new Error(`MuJoCo site "${options.siteName}" was not found.`);
|
|
2799
|
+
}
|
|
2800
|
+
return {
|
|
2801
|
+
...baseOptions,
|
|
2802
|
+
position: vector3FromArray(data.site_xpos, siteId * 3),
|
|
2803
|
+
quaternion: quaternionFromXmat(data.site_xmat, siteId * 9),
|
|
2804
|
+
source: { kind: "mujoco-site", siteName: options.siteName }
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
if (options.bodyName) {
|
|
2808
|
+
const bodyId = findBodyByName(model, options.bodyName);
|
|
2809
|
+
if (bodyId < 0) {
|
|
2810
|
+
throw new Error(`MuJoCo body "${options.bodyName}" was not found.`);
|
|
2811
|
+
}
|
|
2812
|
+
if (!data.xmat) {
|
|
2813
|
+
throw new Error(
|
|
2814
|
+
`MuJoCo body "${options.bodyName}" does not expose world orientation data.`
|
|
2815
|
+
);
|
|
2816
|
+
}
|
|
2817
|
+
return {
|
|
2818
|
+
...baseOptions,
|
|
2819
|
+
position: vector3FromArray(data.xpos, bodyId * 3),
|
|
2820
|
+
quaternion: quaternionFromXmat(data.xmat, bodyId * 9),
|
|
2821
|
+
source: { kind: "mujoco-body", bodyName: options.bodyName }
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
return options;
|
|
2825
|
+
},
|
|
2826
|
+
[]
|
|
2827
|
+
);
|
|
2230
2828
|
const getModelOption = useCallback(() => {
|
|
2231
2829
|
const model = mjModelRef.current;
|
|
2232
2830
|
if (!model?.opt) return { timestep: 2e-3, gravity: [0, 0, -9.81], integrator: 0 };
|
|
@@ -2303,7 +2901,7 @@ function MujocoSimProvider({
|
|
|
2303
2901
|
const qvelOffset = keyId * model.nv;
|
|
2304
2902
|
for (let i = 0; i < model.nv; i++) data.qvel[i] = model.key_qvel[qvelOffset + i];
|
|
2305
2903
|
}
|
|
2306
|
-
|
|
2904
|
+
mujocoRef.current.mj_forward(model, data);
|
|
2307
2905
|
for (const cb of resetCallbacks.current) {
|
|
2308
2906
|
cb();
|
|
2309
2907
|
}
|
|
@@ -2414,67 +3012,187 @@ function MujocoSimProvider({
|
|
|
2414
3012
|
);
|
|
2415
3013
|
const captureCameraFrameApi = useCallback(
|
|
2416
3014
|
(options = {}) => {
|
|
2417
|
-
return captureCameraFrame(
|
|
3015
|
+
return captureCameraFrame(
|
|
3016
|
+
gl,
|
|
3017
|
+
scene,
|
|
3018
|
+
camera,
|
|
3019
|
+
resolveCameraCaptureOptions(options)
|
|
3020
|
+
);
|
|
2418
3021
|
},
|
|
2419
|
-
[camera, gl, scene]
|
|
3022
|
+
[camera, gl, resolveCameraCaptureOptions, scene]
|
|
2420
3023
|
);
|
|
2421
3024
|
const captureCameraFrameBlobApi = useCallback(
|
|
2422
3025
|
(options = {}) => {
|
|
2423
|
-
return captureCameraFrameBlob(
|
|
3026
|
+
return captureCameraFrameBlob(
|
|
3027
|
+
gl,
|
|
3028
|
+
scene,
|
|
3029
|
+
camera,
|
|
3030
|
+
resolveCameraCaptureOptions(options)
|
|
3031
|
+
);
|
|
2424
3032
|
},
|
|
2425
|
-
[camera, gl, scene]
|
|
3033
|
+
[camera, gl, resolveCameraCaptureOptions, scene]
|
|
2426
3034
|
);
|
|
2427
3035
|
const recordCameraSequenceApi = useCallback(
|
|
2428
3036
|
async (options) => {
|
|
2429
3037
|
const frameCount = Math.max(0, Math.floor(options.frames));
|
|
2430
|
-
const stepsPerFrame = Math.max(
|
|
3038
|
+
const stepsPerFrame = Math.max(0, Math.floor(options.stepsPerFrame ?? 1));
|
|
2431
3039
|
const cameras = options.cameras;
|
|
2432
3040
|
const frames = [];
|
|
3041
|
+
const cameraSummaries = {};
|
|
2433
3042
|
const wasPaused = pausedRef.current;
|
|
3043
|
+
const retainFrames = options.retainFrames ?? true;
|
|
3044
|
+
const requireMountedSources = options.requireMountedSources ?? true;
|
|
3045
|
+
let recordedFrameCount = 0;
|
|
3046
|
+
async function stepCameraSequence(frameIndex, steps) {
|
|
3047
|
+
const model = mjModelRef.current;
|
|
3048
|
+
const data = mjDataRef.current;
|
|
3049
|
+
if (!model || !data) {
|
|
3050
|
+
throw new Error("MuJoCo scene is not ready for camera sequence stepping.");
|
|
3051
|
+
}
|
|
3052
|
+
for (let stepIndex = 0; stepIndex < steps; stepIndex += 1) {
|
|
3053
|
+
for (let i = 0; i < model.nv; i += 1) {
|
|
3054
|
+
data.qfrc_applied[i] = 0;
|
|
3055
|
+
}
|
|
3056
|
+
await options.onBeforeStep?.({
|
|
3057
|
+
frameIndex,
|
|
3058
|
+
stepIndex,
|
|
3059
|
+
time: data.time,
|
|
3060
|
+
model,
|
|
3061
|
+
data
|
|
3062
|
+
});
|
|
3063
|
+
for (const cb of beforeStepCallbacks.current) {
|
|
3064
|
+
cb({ model, data });
|
|
3065
|
+
}
|
|
3066
|
+
mujocoRef.current.mj_step(model, data);
|
|
3067
|
+
for (const cb of afterStepCallbacks.current) {
|
|
3068
|
+
cb({ model, data });
|
|
3069
|
+
}
|
|
3070
|
+
onStepRef.current?.({ time: data.time, model, data });
|
|
3071
|
+
await options.onAfterStep?.({
|
|
3072
|
+
frameIndex,
|
|
3073
|
+
stepIndex,
|
|
3074
|
+
time: data.time,
|
|
3075
|
+
model,
|
|
3076
|
+
data
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
physicsAccumulatorRef.current = 0;
|
|
3080
|
+
interpolationStateRef.current.valid = false;
|
|
3081
|
+
}
|
|
2434
3082
|
if (frameCount === 0 || cameras.length === 0) {
|
|
2435
3083
|
return {
|
|
2436
3084
|
frames,
|
|
2437
3085
|
cameraKeys: cameras.map((sequenceCamera) => sequenceCamera.key),
|
|
3086
|
+
cameraSummaries,
|
|
2438
3087
|
frameCount: 0
|
|
2439
3088
|
};
|
|
2440
3089
|
}
|
|
3090
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
3091
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
3092
|
+
if (mjModelRef.current && mjDataRef.current) break;
|
|
3093
|
+
await waitForNextAnimationFrame2();
|
|
3094
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
3095
|
+
}
|
|
3096
|
+
if (!mjModelRef.current || !mjDataRef.current) {
|
|
3097
|
+
throw new Error("MuJoCo scene is not ready for camera sequence recording.");
|
|
3098
|
+
}
|
|
3099
|
+
const captureSessions = cameras.map((sequenceCamera) => {
|
|
3100
|
+
const { key, ...captureOptions } = sequenceCamera;
|
|
3101
|
+
const initialCaptureOptions = resolveCameraCaptureOptions(captureOptions);
|
|
3102
|
+
const mountedSource = initialCaptureOptions.source;
|
|
3103
|
+
if (requireMountedSources) {
|
|
3104
|
+
assertMatchingMountedCameraSource(
|
|
3105
|
+
key,
|
|
3106
|
+
captureOptions,
|
|
3107
|
+
mountedSource ?? { kind: "fallback-camera" }
|
|
3108
|
+
);
|
|
3109
|
+
}
|
|
3110
|
+
return {
|
|
3111
|
+
key,
|
|
3112
|
+
captureOptions,
|
|
3113
|
+
mountedSource,
|
|
3114
|
+
session: createCameraFrameCaptureSession(
|
|
3115
|
+
gl,
|
|
3116
|
+
scene,
|
|
3117
|
+
camera,
|
|
3118
|
+
initialCaptureOptions
|
|
3119
|
+
)
|
|
3120
|
+
};
|
|
3121
|
+
});
|
|
2441
3122
|
try {
|
|
2442
3123
|
pausedRef.current = true;
|
|
2443
3124
|
stepsToRunRef.current = 0;
|
|
2444
3125
|
if (options.reset) reset();
|
|
2445
3126
|
for (let frameIndex = 0; frameIndex < frameCount; frameIndex += 1) {
|
|
2446
|
-
|
|
2447
|
-
|
|
3127
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
3128
|
+
if (stepsPerFrame > 0 && (frameIndex > 0 || options.captureInitialFrame === false)) {
|
|
3129
|
+
await stepCameraSequence(frameIndex, stepsPerFrame);
|
|
2448
3130
|
}
|
|
2449
3131
|
await waitForNextAnimationFrame2();
|
|
3132
|
+
throwIfCameraSequenceAborted(options.signal);
|
|
3133
|
+
const model = mjModelRef.current;
|
|
3134
|
+
const data = mjDataRef.current;
|
|
3135
|
+
if (!model || !data) {
|
|
3136
|
+
throw new Error("MuJoCo scene is not ready for camera sequence sampling.");
|
|
3137
|
+
}
|
|
3138
|
+
await options.onSample?.({
|
|
3139
|
+
frameIndex,
|
|
3140
|
+
time: data.time,
|
|
3141
|
+
model,
|
|
3142
|
+
data
|
|
3143
|
+
});
|
|
2450
3144
|
const cameraFrames = {};
|
|
2451
|
-
for (const
|
|
2452
|
-
const
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
3145
|
+
for (const { key, captureOptions, mountedSource, session } of captureSessions) {
|
|
3146
|
+
const resolvedCaptureOptions = resolveCameraCaptureOptions(captureOptions);
|
|
3147
|
+
const cameraFrame = session.captureDataUrl({
|
|
3148
|
+
...resolvedCaptureOptions,
|
|
3149
|
+
source: mountedSource ?? resolvedCaptureOptions.source
|
|
3150
|
+
});
|
|
3151
|
+
if (requireMountedSources) {
|
|
3152
|
+
assertMatchingMountedCameraSource(
|
|
3153
|
+
key,
|
|
3154
|
+
captureOptions,
|
|
3155
|
+
cameraFrame.source
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3158
|
+
cameraSummaries[key] = {
|
|
3159
|
+
key,
|
|
3160
|
+
width: cameraFrame.width,
|
|
3161
|
+
height: cameraFrame.height,
|
|
3162
|
+
source: cameraFrame.source,
|
|
3163
|
+
frameCount: (cameraSummaries[key]?.frameCount ?? 0) + 1,
|
|
3164
|
+
firstFrameIndex: cameraSummaries[key]?.firstFrameIndex ?? frameIndex,
|
|
3165
|
+
lastFrameIndex: frameIndex,
|
|
3166
|
+
firstTimestamp: cameraSummaries[key]?.firstTimestamp ?? data.time,
|
|
3167
|
+
lastTimestamp: data.time
|
|
3168
|
+
};
|
|
3169
|
+
cameraFrames[key] = cameraFrame;
|
|
2459
3170
|
}
|
|
2460
3171
|
const frame = {
|
|
2461
3172
|
frameIndex,
|
|
2462
|
-
time:
|
|
3173
|
+
time: data.time,
|
|
2463
3174
|
cameras: cameraFrames
|
|
2464
3175
|
};
|
|
2465
|
-
|
|
3176
|
+
if (retainFrames) {
|
|
3177
|
+
frames.push(frame);
|
|
3178
|
+
}
|
|
3179
|
+
recordedFrameCount += 1;
|
|
2466
3180
|
await options.onFrame?.(frame);
|
|
2467
3181
|
}
|
|
2468
3182
|
} finally {
|
|
3183
|
+
for (const { session } of captureSessions) {
|
|
3184
|
+
session.dispose();
|
|
3185
|
+
}
|
|
2469
3186
|
pausedRef.current = wasPaused;
|
|
2470
3187
|
}
|
|
2471
3188
|
return {
|
|
2472
3189
|
frames,
|
|
2473
3190
|
cameraKeys: cameras.map((sequenceCamera) => sequenceCamera.key),
|
|
2474
|
-
|
|
3191
|
+
cameraSummaries,
|
|
3192
|
+
frameCount: recordedFrameCount
|
|
2475
3193
|
};
|
|
2476
3194
|
},
|
|
2477
|
-
[camera, getTime, gl, reset,
|
|
3195
|
+
[camera, getTime, gl, mujoco, reset, resolveCameraCaptureOptions, scene]
|
|
2478
3196
|
);
|
|
2479
3197
|
const project2DTo3D = useCallback(
|
|
2480
3198
|
(x, y, cameraPos, lookAt) => {
|
|
@@ -2571,6 +3289,7 @@ function MujocoSimProvider({
|
|
|
2571
3289
|
getSites,
|
|
2572
3290
|
getActuators: getActuatorsApi,
|
|
2573
3291
|
getSensors,
|
|
3292
|
+
getCameras,
|
|
2574
3293
|
getModelOption,
|
|
2575
3294
|
setGravity,
|
|
2576
3295
|
setTimestep: setTimestepApi,
|
|
@@ -2629,6 +3348,7 @@ function MujocoSimProvider({
|
|
|
2629
3348
|
getSites,
|
|
2630
3349
|
getActuatorsApi,
|
|
2631
3350
|
getSensors,
|
|
3351
|
+
getCameras,
|
|
2632
3352
|
getModelOption,
|
|
2633
3353
|
setGravity,
|
|
2634
3354
|
setTimestepApi,
|
|
@@ -3847,6 +4567,231 @@ function SceneLights({ intensity = 1 }) {
|
|
|
3847
4567
|
useSceneLights(intensity);
|
|
3848
4568
|
return null;
|
|
3849
4569
|
}
|
|
4570
|
+
function SplatCollisionProxyPreview({
|
|
4571
|
+
collisionProxy,
|
|
4572
|
+
xmlText,
|
|
4573
|
+
fetchXml = fetchSplatCollisionProxyXml,
|
|
4574
|
+
color = "#60a5fa",
|
|
4575
|
+
opacity = 0.12,
|
|
4576
|
+
planeColor = "#94a3b8",
|
|
4577
|
+
planeOpacity = 0.08,
|
|
4578
|
+
children,
|
|
4579
|
+
...groupProps
|
|
4580
|
+
}) {
|
|
4581
|
+
const { geoms } = useSplatCollisionProxyGeoms({
|
|
4582
|
+
collisionProxy,
|
|
4583
|
+
xmlText,
|
|
4584
|
+
fetchXml
|
|
4585
|
+
});
|
|
4586
|
+
if (geoms.length === 0 && !children) return null;
|
|
4587
|
+
return /* @__PURE__ */ jsxs(
|
|
4588
|
+
"group",
|
|
4589
|
+
{
|
|
4590
|
+
...groupProps,
|
|
4591
|
+
userData: {
|
|
4592
|
+
kind: "splat-collision-proxy-preview",
|
|
4593
|
+
...groupProps.userData
|
|
4594
|
+
},
|
|
4595
|
+
children: [
|
|
4596
|
+
geoms.map((geom) => /* @__PURE__ */ jsx(
|
|
4597
|
+
SplatCollisionProxyGeom,
|
|
4598
|
+
{
|
|
4599
|
+
geom,
|
|
4600
|
+
color,
|
|
4601
|
+
opacity,
|
|
4602
|
+
planeColor,
|
|
4603
|
+
planeOpacity
|
|
4604
|
+
},
|
|
4605
|
+
geom.id
|
|
4606
|
+
)),
|
|
4607
|
+
children
|
|
4608
|
+
]
|
|
4609
|
+
}
|
|
4610
|
+
);
|
|
4611
|
+
}
|
|
4612
|
+
function useSplatCollisionProxyGeoms({
|
|
4613
|
+
collisionProxy,
|
|
4614
|
+
xmlText,
|
|
4615
|
+
fetchXml = fetchSplatCollisionProxyXml,
|
|
4616
|
+
enabled = true
|
|
4617
|
+
}) {
|
|
4618
|
+
const [loadedXmlText, setLoadedXmlText] = useState(null);
|
|
4619
|
+
const [status, setStatus] = useState("idle");
|
|
4620
|
+
const [error, setError] = useState(null);
|
|
4621
|
+
const xmlPath = collisionProxy?.xmlPath;
|
|
4622
|
+
useEffect(() => {
|
|
4623
|
+
let cancelled = false;
|
|
4624
|
+
if (!enabled) {
|
|
4625
|
+
setLoadedXmlText(null);
|
|
4626
|
+
setStatus("idle");
|
|
4627
|
+
setError(null);
|
|
4628
|
+
return void 0;
|
|
4629
|
+
}
|
|
4630
|
+
if (xmlText) {
|
|
4631
|
+
setLoadedXmlText(xmlText);
|
|
4632
|
+
setStatus("ready");
|
|
4633
|
+
setError(null);
|
|
4634
|
+
return void 0;
|
|
4635
|
+
}
|
|
4636
|
+
if (!xmlPath || !canFetchSplatCollisionProxyXml(xmlPath)) {
|
|
4637
|
+
setLoadedXmlText(null);
|
|
4638
|
+
setStatus("idle");
|
|
4639
|
+
setError(null);
|
|
4640
|
+
return void 0;
|
|
4641
|
+
}
|
|
4642
|
+
const fetchPath = xmlPath;
|
|
4643
|
+
async function loadProxyXml() {
|
|
4644
|
+
setStatus("loading");
|
|
4645
|
+
setError(null);
|
|
4646
|
+
try {
|
|
4647
|
+
const nextXmlText = await fetchXml(fetchPath);
|
|
4648
|
+
if (!cancelled) {
|
|
4649
|
+
setLoadedXmlText(nextXmlText);
|
|
4650
|
+
setStatus("ready");
|
|
4651
|
+
}
|
|
4652
|
+
} catch (nextError) {
|
|
4653
|
+
if (!cancelled) {
|
|
4654
|
+
setLoadedXmlText(null);
|
|
4655
|
+
setStatus("error");
|
|
4656
|
+
setError(
|
|
4657
|
+
nextError instanceof Error ? nextError : new Error("Unable to load collision proxy XML.")
|
|
4658
|
+
);
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
void loadProxyXml();
|
|
4663
|
+
return () => {
|
|
4664
|
+
cancelled = true;
|
|
4665
|
+
};
|
|
4666
|
+
}, [enabled, fetchXml, xmlPath, xmlText]);
|
|
4667
|
+
const geoms = useMemo(
|
|
4668
|
+
() => loadedXmlText ? parseSplatCollisionProxyGeoms(loadedXmlText) : [],
|
|
4669
|
+
[loadedXmlText]
|
|
4670
|
+
);
|
|
4671
|
+
return useMemo(
|
|
4672
|
+
() => ({
|
|
4673
|
+
geoms,
|
|
4674
|
+
status,
|
|
4675
|
+
error,
|
|
4676
|
+
xmlPath
|
|
4677
|
+
}),
|
|
4678
|
+
[error, geoms, status, xmlPath]
|
|
4679
|
+
);
|
|
4680
|
+
}
|
|
4681
|
+
async function fetchSplatCollisionProxyXml(xmlPath) {
|
|
4682
|
+
const response = await fetch(xmlPath);
|
|
4683
|
+
if (!response.ok) {
|
|
4684
|
+
throw new Error(`Unable to load collision proxy XML (${response.status}).`);
|
|
4685
|
+
}
|
|
4686
|
+
return response.text();
|
|
4687
|
+
}
|
|
4688
|
+
function canFetchSplatCollisionProxyXml(xmlPath) {
|
|
4689
|
+
return xmlPath.startsWith("/") || xmlPath.startsWith("http://") || xmlPath.startsWith("https://");
|
|
4690
|
+
}
|
|
4691
|
+
function parseSplatCollisionProxyGeoms(xmlText) {
|
|
4692
|
+
const parser = typeof DOMParser === "undefined" ? null : new DOMParser();
|
|
4693
|
+
if (!parser) return [];
|
|
4694
|
+
const document2 = parser.parseFromString(xmlText, "application/xml");
|
|
4695
|
+
if (document2.querySelector("parsererror")) return [];
|
|
4696
|
+
const bodyPositions = /* @__PURE__ */ new Map();
|
|
4697
|
+
for (const body of Array.from(document2.querySelectorAll("body"))) {
|
|
4698
|
+
const parentBody = body.parentElement?.closest("body");
|
|
4699
|
+
const parentPosition = parentBody ? bodyPositions.get(parentBody) ?? [0, 0, 0] : [0, 0, 0];
|
|
4700
|
+
bodyPositions.set(
|
|
4701
|
+
body,
|
|
4702
|
+
addProxyVectors(parentPosition, parseProxyVector(body.getAttribute("pos")))
|
|
4703
|
+
);
|
|
4704
|
+
}
|
|
4705
|
+
return Array.from(document2.querySelectorAll("geom")).map((geom, index) => {
|
|
4706
|
+
const type = getCollisionProxyGeomType(geom);
|
|
4707
|
+
if (!type) return null;
|
|
4708
|
+
const parentBody = geom.closest("body");
|
|
4709
|
+
const bodyPosition = parentBody ? bodyPositions.get(parentBody) ?? [0, 0, 0] : [0, 0, 0];
|
|
4710
|
+
const position = addProxyVectors(
|
|
4711
|
+
bodyPosition,
|
|
4712
|
+
parseProxyVector(geom.getAttribute("pos"))
|
|
4713
|
+
);
|
|
4714
|
+
const size = parseNumberList(geom.getAttribute("size"));
|
|
4715
|
+
return {
|
|
4716
|
+
id: geom.getAttribute("name") ?? `${type}-${index}`,
|
|
4717
|
+
type,
|
|
4718
|
+
position,
|
|
4719
|
+
size
|
|
4720
|
+
};
|
|
4721
|
+
}).filter((geom) => Boolean(geom));
|
|
4722
|
+
}
|
|
4723
|
+
function SplatCollisionProxyGeom({
|
|
4724
|
+
geom,
|
|
4725
|
+
color,
|
|
4726
|
+
opacity,
|
|
4727
|
+
planeColor,
|
|
4728
|
+
planeOpacity
|
|
4729
|
+
}) {
|
|
4730
|
+
if (geom.type === "sphere") {
|
|
4731
|
+
return /* @__PURE__ */ jsxs("mesh", { position: geom.position, children: [
|
|
4732
|
+
/* @__PURE__ */ jsx("sphereGeometry", { args: [geom.size[0] ?? 0.1, 16, 8] }),
|
|
4733
|
+
/* @__PURE__ */ jsx(SplatCollisionProxyMaterial, { color, opacity })
|
|
4734
|
+
] });
|
|
4735
|
+
}
|
|
4736
|
+
if (geom.type === "plane") {
|
|
4737
|
+
const width = geom.size[0] && geom.size[0] > 0 ? geom.size[0] * 2 : 4;
|
|
4738
|
+
const height = geom.size[1] && geom.size[1] > 0 ? geom.size[1] * 2 : 4;
|
|
4739
|
+
return /* @__PURE__ */ jsxs("mesh", { position: geom.position, children: [
|
|
4740
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [width, height, 0.02] }),
|
|
4741
|
+
/* @__PURE__ */ jsx(SplatCollisionProxyMaterial, { color: planeColor, opacity: planeOpacity })
|
|
4742
|
+
] });
|
|
4743
|
+
}
|
|
4744
|
+
const size = getCollisionProxyBoxSize(geom);
|
|
4745
|
+
return /* @__PURE__ */ jsxs("mesh", { position: geom.position, children: [
|
|
4746
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: size }),
|
|
4747
|
+
/* @__PURE__ */ jsx(SplatCollisionProxyMaterial, { color, opacity })
|
|
4748
|
+
] });
|
|
4749
|
+
}
|
|
4750
|
+
function SplatCollisionProxyMaterial({
|
|
4751
|
+
color,
|
|
4752
|
+
opacity
|
|
4753
|
+
}) {
|
|
4754
|
+
return /* @__PURE__ */ jsx(
|
|
4755
|
+
"meshBasicMaterial",
|
|
4756
|
+
{
|
|
4757
|
+
color,
|
|
4758
|
+
transparent: true,
|
|
4759
|
+
opacity,
|
|
4760
|
+
wireframe: true
|
|
4761
|
+
}
|
|
4762
|
+
);
|
|
4763
|
+
}
|
|
4764
|
+
function getCollisionProxyGeomType(geom) {
|
|
4765
|
+
const type = geom.getAttribute("type") ?? "sphere";
|
|
4766
|
+
if (type === "box" || type === "plane" || type === "sphere" || type === "capsule" || type === "mesh") {
|
|
4767
|
+
return type;
|
|
4768
|
+
}
|
|
4769
|
+
return null;
|
|
4770
|
+
}
|
|
4771
|
+
function getCollisionProxyBoxSize(geom) {
|
|
4772
|
+
if (geom.type === "capsule") {
|
|
4773
|
+
const radius = geom.size[0] ?? 0.05;
|
|
4774
|
+
const halfLength = geom.size[1] ?? radius;
|
|
4775
|
+
return [radius * 2, radius * 2, Math.max(radius * 2, halfLength * 2)];
|
|
4776
|
+
}
|
|
4777
|
+
if (geom.type === "mesh") return [0.2, 0.2, 0.2];
|
|
4778
|
+
return [
|
|
4779
|
+
(geom.size[0] ?? 0.1) * 2,
|
|
4780
|
+
(geom.size[1] ?? geom.size[0] ?? 0.1) * 2,
|
|
4781
|
+
(geom.size[2] ?? geom.size[0] ?? 0.1) * 2
|
|
4782
|
+
];
|
|
4783
|
+
}
|
|
4784
|
+
function parseProxyVector(value) {
|
|
4785
|
+
const values = parseNumberList(value);
|
|
4786
|
+
return [values[0] ?? 0, values[1] ?? 0, values[2] ?? 0];
|
|
4787
|
+
}
|
|
4788
|
+
function parseNumberList(value) {
|
|
4789
|
+
if (!value) return [];
|
|
4790
|
+
return value.trim().split(/\s+/).map((part) => Number(part)).filter((part) => Number.isFinite(part));
|
|
4791
|
+
}
|
|
4792
|
+
function addProxyVectors(a, b) {
|
|
4793
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
4794
|
+
}
|
|
3850
4795
|
var JOINT_COLORS = {
|
|
3851
4796
|
0: 16711680,
|
|
3852
4797
|
// free - red
|
|
@@ -5421,6 +6366,90 @@ function useCameraSequenceRecorder() {
|
|
|
5421
6366
|
reset
|
|
5422
6367
|
};
|
|
5423
6368
|
}
|
|
6369
|
+
function useMountedCameraSequenceRecorder(defaultOptions = {}) {
|
|
6370
|
+
const mujoco = useMujoco();
|
|
6371
|
+
const [status, setStatus] = useState("idle");
|
|
6372
|
+
const [error, setError] = useState(null);
|
|
6373
|
+
const [plan, setPlan] = useState(null);
|
|
6374
|
+
const [readiness, setReadiness] = useState(null);
|
|
6375
|
+
const [result, setResult] = useState(
|
|
6376
|
+
null
|
|
6377
|
+
);
|
|
6378
|
+
const reset = useCallback(() => {
|
|
6379
|
+
setStatus("idle");
|
|
6380
|
+
setError(null);
|
|
6381
|
+
setPlan(null);
|
|
6382
|
+
setReadiness(null);
|
|
6383
|
+
setResult(null);
|
|
6384
|
+
}, []);
|
|
6385
|
+
const createPlan = useCallback(
|
|
6386
|
+
(cameraKeys, options = {}) => {
|
|
6387
|
+
if (!mujoco.api) {
|
|
6388
|
+
throw new Error("MuJoCo scene is not ready for mounted camera sequence planning.");
|
|
6389
|
+
}
|
|
6390
|
+
const nextPlan = createMountedCameraFrameSequencePlanFromApi(
|
|
6391
|
+
mujoco.api,
|
|
6392
|
+
cameraKeys,
|
|
6393
|
+
{
|
|
6394
|
+
...defaultOptions,
|
|
6395
|
+
...options
|
|
6396
|
+
}
|
|
6397
|
+
);
|
|
6398
|
+
setPlan(nextPlan);
|
|
6399
|
+
setReadiness(null);
|
|
6400
|
+
return nextPlan;
|
|
6401
|
+
},
|
|
6402
|
+
[defaultOptions, mujoco.api]
|
|
6403
|
+
);
|
|
6404
|
+
const checkReadiness = useCallback(
|
|
6405
|
+
(cameraKeys, options = {}) => {
|
|
6406
|
+
const nextPlan = createPlan(cameraKeys, options);
|
|
6407
|
+
const nextReadiness = createMountedCameraFrameSequenceReadiness(nextPlan);
|
|
6408
|
+
setReadiness(nextReadiness);
|
|
6409
|
+
return nextReadiness;
|
|
6410
|
+
},
|
|
6411
|
+
[createPlan]
|
|
6412
|
+
);
|
|
6413
|
+
const record = useCallback(
|
|
6414
|
+
async (options) => {
|
|
6415
|
+
if (!mujoco.api) {
|
|
6416
|
+
throw new Error("MuJoCo scene is not ready for mounted camera sequence recording.");
|
|
6417
|
+
}
|
|
6418
|
+
setStatus("capturing");
|
|
6419
|
+
setError(null);
|
|
6420
|
+
setResult(null);
|
|
6421
|
+
try {
|
|
6422
|
+
const nextResult = await recordMountedCameraFrameSequence(mujoco.api, {
|
|
6423
|
+
...defaultOptions,
|
|
6424
|
+
...options
|
|
6425
|
+
});
|
|
6426
|
+
setPlan(nextResult.plan);
|
|
6427
|
+
setReadiness(nextResult.readiness);
|
|
6428
|
+
setResult(nextResult);
|
|
6429
|
+
setStatus("captured");
|
|
6430
|
+
return nextResult;
|
|
6431
|
+
} catch (nextError) {
|
|
6432
|
+
const error2 = nextError instanceof Error ? nextError : new Error("Unable to record the requested mounted camera sequence.");
|
|
6433
|
+
setError(error2);
|
|
6434
|
+
setStatus("error");
|
|
6435
|
+
throw error2;
|
|
6436
|
+
}
|
|
6437
|
+
},
|
|
6438
|
+
[defaultOptions, mujoco.api]
|
|
6439
|
+
);
|
|
6440
|
+
return {
|
|
6441
|
+
status,
|
|
6442
|
+
error,
|
|
6443
|
+
plan,
|
|
6444
|
+
readiness,
|
|
6445
|
+
result,
|
|
6446
|
+
isRecording: status === "capturing",
|
|
6447
|
+
createPlan,
|
|
6448
|
+
checkReadiness,
|
|
6449
|
+
record,
|
|
6450
|
+
reset
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
5424
6453
|
function useCtrlNoise(config = {}) {
|
|
5425
6454
|
const { mjModelRef } = useMujocoContext();
|
|
5426
6455
|
const configRef = useRef(config);
|
|
@@ -5585,6 +6614,12 @@ function useCameraAnimation() {
|
|
|
5585
6614
|
*
|
|
5586
6615
|
* Offscreen camera-frame capture for R3F/MuJoCo scenes.
|
|
5587
6616
|
*/
|
|
6617
|
+
/**
|
|
6618
|
+
* @license
|
|
6619
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6620
|
+
*
|
|
6621
|
+
* Helpers for resolving dataset camera streams to mounted MuJoCo resources.
|
|
6622
|
+
*/
|
|
5588
6623
|
/**
|
|
5589
6624
|
* @license
|
|
5590
6625
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -5730,6 +6765,12 @@ function useCameraAnimation() {
|
|
|
5730
6765
|
*
|
|
5731
6766
|
* React state wrapper around fixed-camera simulation sequence recording.
|
|
5732
6767
|
*/
|
|
6768
|
+
/**
|
|
6769
|
+
* @license
|
|
6770
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6771
|
+
*
|
|
6772
|
+
* React state wrapper for named MuJoCo camera/site/body sequence recording.
|
|
6773
|
+
*/
|
|
5733
6774
|
/**
|
|
5734
6775
|
* @license
|
|
5735
6776
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -5760,6 +6801,6 @@ function useCameraAnimation() {
|
|
|
5760
6801
|
* useCameraAnimation — composable camera animation hook.
|
|
5761
6802
|
*/
|
|
5762
6803
|
|
|
5763
|
-
export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider,
|
|
6804
|
+
export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MountedCameraFrameSequenceManifestStatus, MountedCameraFrameSequenceReadinessStatus, MountedCameraFrameSourceSuggestionMatch, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SplatCollisionProxyPreview, TendonRenderer, TrajectoryPlayer, buildObservation, canFetchSplatCollisionProxyXml, captureCameraFrame, captureCameraFrameBlob, captureFrame, captureFrameBlob, createCameraFrameCaptureSession, createContiguousControlGroup, createController, createControllerHook, createMountedCameraFrameSequenceManifest, createMountedCameraFrameSequencePlan, createMountedCameraFrameSequencePlanFromApi, createMountedCameraFrameSequenceReadiness, createMountedCameraFrameSourceSuggestions, fetchSplatCollisionProxyXml, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getCameraFrameCaptureSourceTarget, getControlMap, getMountedCameraFrameCaptureSource, getName, isMountedCameraFrameCaptureSource, loadScene, parseSplatCollisionProxyGeoms, 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, useSplatCollisionProxyGeoms, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
|
|
5764
6805
|
//# sourceMappingURL=index.js.map
|
|
5765
6806
|
//# sourceMappingURL=index.js.map
|