mujoco-react 9.3.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,5 +1,5 @@
1
- import { withContacts, getContact } from './chunk-T3GVZJ4F.js';
2
- export { RobotActuators, RobotBodies, RobotCameras, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerRobotResources, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, withSplatEnvironment } from './chunk-T3GVZJ4F.js';
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';
3
3
  import loadMujoco from '@mujoco/mujoco';
4
4
  import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
5
5
  import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
@@ -1578,11 +1578,24 @@ async function captureCameraFrameBlob(renderer, scene, fallbackCamera, options =
1578
1578
  }
1579
1579
 
1580
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
+ };
1581
1589
  var MountedCameraFrameSequenceReadinessStatus = {
1582
1590
  Ready: "ready",
1583
1591
  Partial: "partial",
1584
1592
  Missing: "missing"
1585
1593
  };
1594
+ var MountedCameraFrameSequenceManifestStatus = {
1595
+ Complete: "complete",
1596
+ Partial: "partial",
1597
+ Missing: "missing"
1598
+ };
1586
1599
  function getResourceName(resource) {
1587
1600
  if (!resource) return null;
1588
1601
  return typeof resource === "string" ? resource : resource.name ?? null;
@@ -1592,6 +1605,9 @@ function createNameSet(resources) {
1592
1605
  (resources ?? []).map((resource) => getResourceName(resource)).filter((name) => Boolean(name))
1593
1606
  );
1594
1607
  }
1608
+ function createResourceNames(resources) {
1609
+ return (resources ?? []).map((resource) => getResourceName(resource)).filter((name) => Boolean(name));
1610
+ }
1595
1611
  function normalizeAliasCandidates(value) {
1596
1612
  if (!value) return [];
1597
1613
  return Array.isArray(value) ? value : [value];
@@ -1599,6 +1615,24 @@ function normalizeAliasCandidates(value) {
1599
1615
  function countMountedSelectors(selector) {
1600
1616
  return Number(Boolean(selector.cameraName)) + Number(Boolean(selector.siteName)) + Number(Boolean(selector.bodyName));
1601
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
+ }
1602
1636
  function getMountedCameraFrameCaptureSource(selector) {
1603
1637
  if (countMountedSelectors(selector) !== 1) return null;
1604
1638
  if (selector.cameraName) {
@@ -1623,10 +1657,119 @@ function getCameraFrameCaptureSourceTarget(source) {
1623
1657
  if (source.kind === "explicit-pose") return "explicit pose";
1624
1658
  return "fallback camera";
1625
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
+ }
1626
1702
  function isSelectorMounted(selector, cameraNames, siteNames, bodyNames) {
1627
1703
  if (countMountedSelectors(selector) !== 1) return false;
1628
1704
  return (selector.cameraName ? cameraNames.has(selector.cameraName) : false) || (selector.siteName ? siteNames.has(selector.siteName) : false) || (selector.bodyName ? bodyNames.has(selector.bodyName) : false);
1629
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
+ }
1630
1773
  function resolveMountedCameraFrameSource(key, options) {
1631
1774
  const cameraNames = createNameSet(options.cameras);
1632
1775
  const siteNames = createNameSet(options.sites);
@@ -1646,6 +1789,14 @@ function resolveMountedCameraFrameSource(key, options) {
1646
1789
  if (!source) continue;
1647
1790
  return { key, selector, source };
1648
1791
  }
1792
+ const [suggestion] = createMountedCameraFrameSourceSuggestions(key, options);
1793
+ if (suggestion) {
1794
+ return {
1795
+ key,
1796
+ selector: suggestion.selector,
1797
+ source: suggestion.source
1798
+ };
1799
+ }
1649
1800
  if (options.allowAliasFallback) {
1650
1801
  for (const selector of aliasCandidates) {
1651
1802
  const source = getMountedCameraFrameCaptureSource(selector);
@@ -1716,6 +1867,77 @@ function createMountedCameraFrameSequenceReadiness(plan) {
1716
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(", ")}.`
1717
1868
  };
1718
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
+ }
1719
1941
  function createMountedCameraFrameSequencePlanFromApi(api, cameraKeys, options = {}) {
1720
1942
  return createMountedCameraFrameSequencePlan(cameraKeys, {
1721
1943
  ...options,
@@ -2948,7 +3170,7 @@ function MujocoSimProvider({
2948
3170
  }
2949
3171
  const frame = {
2950
3172
  frameIndex,
2951
- time: getTime(),
3173
+ time: data.time,
2952
3174
  cameras: cameraFrames
2953
3175
  };
2954
3176
  if (retainFrames) {
@@ -4345,6 +4567,231 @@ function SceneLights({ intensity = 1 }) {
4345
4567
  useSceneLights(intensity);
4346
4568
  return null;
4347
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
+ }
4348
4795
  var JOINT_COLORS = {
4349
4796
  0: 16711680,
4350
4797
  // free - red
@@ -5923,22 +6370,46 @@ function useMountedCameraSequenceRecorder(defaultOptions = {}) {
5923
6370
  const mujoco = useMujoco();
5924
6371
  const [status, setStatus] = useState("idle");
5925
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
+ );
5926
6378
  const reset = useCallback(() => {
5927
6379
  setStatus("idle");
5928
6380
  setError(null);
6381
+ setPlan(null);
6382
+ setReadiness(null);
6383
+ setResult(null);
5929
6384
  }, []);
5930
6385
  const createPlan = useCallback(
5931
6386
  (cameraKeys, options = {}) => {
5932
6387
  if (!mujoco.api) {
5933
6388
  throw new Error("MuJoCo scene is not ready for mounted camera sequence planning.");
5934
6389
  }
5935
- return createMountedCameraFrameSequencePlanFromApi(mujoco.api, cameraKeys, {
5936
- ...defaultOptions,
5937
- ...options
5938
- });
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;
5939
6401
  },
5940
6402
  [defaultOptions, mujoco.api]
5941
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
+ );
5942
6413
  const record = useCallback(
5943
6414
  async (options) => {
5944
6415
  if (!mujoco.api) {
@@ -5946,13 +6417,17 @@ function useMountedCameraSequenceRecorder(defaultOptions = {}) {
5946
6417
  }
5947
6418
  setStatus("capturing");
5948
6419
  setError(null);
6420
+ setResult(null);
5949
6421
  try {
5950
- const result = await recordMountedCameraFrameSequence(mujoco.api, {
6422
+ const nextResult = await recordMountedCameraFrameSequence(mujoco.api, {
5951
6423
  ...defaultOptions,
5952
6424
  ...options
5953
6425
  });
6426
+ setPlan(nextResult.plan);
6427
+ setReadiness(nextResult.readiness);
6428
+ setResult(nextResult);
5954
6429
  setStatus("captured");
5955
- return result;
6430
+ return nextResult;
5956
6431
  } catch (nextError) {
5957
6432
  const error2 = nextError instanceof Error ? nextError : new Error("Unable to record the requested mounted camera sequence.");
5958
6433
  setError(error2);
@@ -5965,8 +6440,12 @@ function useMountedCameraSequenceRecorder(defaultOptions = {}) {
5965
6440
  return {
5966
6441
  status,
5967
6442
  error,
6443
+ plan,
6444
+ readiness,
6445
+ result,
5968
6446
  isRecording: status === "capturing",
5969
6447
  createPlan,
6448
+ checkReadiness,
5970
6449
  record,
5971
6450
  reset
5972
6451
  };
@@ -6322,6 +6801,6 @@ function useCameraAnimation() {
6322
6801
  * useCameraAnimation — composable camera animation hook.
6323
6802
  */
6324
6803
 
6325
- export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MountedCameraFrameSequenceReadinessStatus, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, buildObservation, captureCameraFrame, captureCameraFrameBlob, captureFrame, captureFrameBlob, createCameraFrameCaptureSession, createContiguousControlGroup, createController, createControllerHook, createMountedCameraFrameSequencePlan, createMountedCameraFrameSequencePlanFromApi, createMountedCameraFrameSequenceReadiness, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getCameraFrameCaptureSourceTarget, getControlMap, getMountedCameraFrameCaptureSource, getName, isMountedCameraFrameCaptureSource, loadScene, recordMountedCameraFrameSequence, renderCameraFrameToCanvas, resolveControlGroup, resolveMountedCameraFrameSource, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMountedCameraSequenceRecorder, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
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 };
6326
6805
  //# sourceMappingURL=index.js.map
6327
6806
  //# sourceMappingURL=index.js.map