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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment } from './chunk-33CV6HSV.js';
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 readRenderTargetToCanvas(renderer, target, width, height) {
1443
- const pixels = new Uint8Array(width * height * 4);
1444
- renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
1445
- const canvas = document.createElement("canvas");
1446
- canvas.width = width;
1447
- canvas.height = height;
1448
- const context = canvas.getContext("2d");
1449
- if (!context) {
1450
- throw new Error("Unable to create a 2D canvas for camera frame capture.");
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
- const imageData = context.createImageData(width, height);
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 renderCameraFrameToCanvas(renderer, scene, fallbackCamera, options = {}) {
1466
- const width = Math.max(1, Math.floor(options.width ?? renderer.domElement.width));
1467
- const height = Math.max(1, Math.floor(options.height ?? renderer.domElement.height));
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 previousTarget = renderer.getRenderTarget();
1474
- const previousXrEnabled = renderer.xr.enabled;
1475
- scene.updateMatrixWorld(true);
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
- renderer.xr.enabled = false;
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
- renderer.setRenderTarget(previousTarget);
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
- mujoco.mj_step(model, data);
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
- mujoco.mj_step(model, data);
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
- mujoco.mj_step(model, data);
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
- mujoco.mj_step(model, data);
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
- mujoco.mj_resetData(model, data);
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
- mujoco.mj_forward(model, data);
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
- const stepImmediately = useCallback((steps = 1) => {
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
- mujoco.mj_step(model, data);
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
- mujoco.mj_forward(model, data);
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
- mujoco.mj_forward(model, data);
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
- mujoco.mj_forward(model, data);
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(gl, scene, camera, options);
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(gl, scene, camera, options);
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(1, Math.floor(options.stepsPerFrame ?? 1));
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
- if (frameIndex > 0 || options.captureInitialFrame === false) {
2447
- stepImmediately(stepsPerFrame);
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 sequenceCamera of cameras) {
2452
- const { key, ...captureOptions } = sequenceCamera;
2453
- cameraFrames[key] = await captureCameraFrame(
2454
- gl,
2455
- scene,
2456
- camera,
2457
- captureOptions
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: getTime(),
3173
+ time: data.time,
2463
3174
  cameras: cameraFrames
2464
3175
  };
2465
- frames.push(frame);
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
- frameCount: frames.length
3191
+ cameraSummaries,
3192
+ frameCount: recordedFrameCount
2475
3193
  };
2476
3194
  },
2477
- [camera, getTime, gl, reset, scene, stepImmediately]
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, RobotActuators, RobotBodies, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, SceneLights, TendonRenderer, TrajectoryPlayer, buildObservation, captureCameraFrame, captureCameraFrameBlob, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getContact, getControlMap, getName, loadScene, registerRobotResources, renderCameraFrameToCanvas, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
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