mujoco-react 8.10.0 → 8.11.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.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { M as MujocoContextValue, a as MujocoCanvasProps, b as MujocoSimAPI, S as SceneConfig, c as MujocoModule, P as PhysicsStepCallback, d as MujocoModel, e as MujocoData, C as ControlGroupInfo, A as ActuatedJointInfo, f as ControlGroupSelector, O as ObservationConfig, g as ObservationResult, I as IkConfig, h as IkContextValue, B as BodyProps, i as IkGizmoProps, D as DragInteractionProps, j as SceneLightsProps, k as ScenarioLightingProps, l as SplatEnvironmentProps, m as PairedSplatEnvironmentConfig, n as SplatFormat, o as SplatCollisionProxyConfig, p as SplatRendererKind, q as SplatCollisionPrimitive, r as ScenarioLightingPreset, V as VisualScenarioConfig, s as SplatEnvironmentMetadataInput, t as SplatEnvironmentMetadata, u as DebugProps, G as GeomInfo, v as ContactListenerProps, T as TrajectoryPlayerProps, w as ActuatorInfo, x as Sites, y as SitePositionResult, z as Sensors, E as SensorHandle, F as SensorInfo, J as Joints, H as JointStateResult, K as Bodies, L as BodyStateResult, N as Actuators, Q as CtrlHandle, R as ContactInfo, U as KeyboardTeleopConfig, W as PolicyConfig, X as ObservationHandle, Y as TrajectoryInput, Z as PlaybackState, _ as TrajectoryFrame } from './types-FFW7ykBu.js';
3
- export { $ as BodyInfo, a0 as ControlJointInfo, a1 as Geoms, a2 as IKSolveFn, a3 as JointInfo, a4 as KeyBinding, a5 as Keyframes, a6 as ModelOptions, a7 as MujocoContact, a8 as MujocoContactArray, a9 as ObservationLayoutItem, aa as ObservationOutput, ab as PhysicsConfig, ac as RayHit, ad as Register, ae as RegisteredRobotMap, af as ResourceSelector, ag as RobotActuators, ah as RobotBodies, ai as RobotGeoms, aj as RobotJoints, ak as RobotKeyframes, al as RobotResource, am as RobotResources, an as RobotSensors, ao as RobotSites, ap as Robots, aq as ScenarioCameraConfig, ar as SceneMarker, as as SceneObject, at as SensorResult, au as SiteInfo, av as SplatAssetConfig, aw as SplatScenarioConfig, ax as StateSnapshot, ay as TrajectoryData, az as XmlPatch, aA as getContact, aB as registerRobotResources } from './types-FFW7ykBu.js';
2
+ import { M as MujocoContextValue, a as MujocoCanvasProps, b as MujocoSimAPI, S as SceneConfig, c as MujocoModule, P as PhysicsStepCallback, d as MujocoModel, e as MujocoData, C as ControlGroupInfo, A as ActuatedJointInfo, f as ControlGroupSelector, O as ObservationConfig, g as ObservationResult, I as IkConfig, h as IkContextValue, B as BodyProps, i as IkGizmoProps, D as DragInteractionProps, j as SceneLightsProps, k as ScenarioLightingProps, l as SplatEnvironmentProps, V as VisualScenarioEffectsProps, m as VisualScenarioConfig, n as SplatRendererKind, o as PairedSplatEnvironmentConfig, p as SplatFormat, q as SplatCollisionProxyConfig, r as SplatCollisionPrimitive, s as ScenarioLightingPreset, t as SplatEnvironmentMetadataInput, u as SplatEnvironmentMetadata, v as SplatSceneInput, w as DebugProps, G as GeomInfo, x as ContactListenerProps, T as TrajectoryPlayerProps, y as ActuatorInfo, z as Sites, E as SitePositionResult, F as Sensors, H as SensorHandle, J as SensorInfo, K as Joints, L as JointStateResult, N as Bodies, Q as BodyStateResult, R as Actuators, U as CtrlHandle, W as ContactInfo, X as KeyboardTeleopConfig, Y as PolicyConfig, Z as PolicyVector, _ as ObservationHandle, $ as TrajectoryInput, a0 as PlaybackState, a1 as TrajectoryFrame } from './types-BmneHLBM.js';
3
+ export { a2 as BodyInfo, a3 as ControlJointInfo, a4 as Geoms, a5 as IKSolveFn, a6 as JointInfo, a7 as KeyBinding, a8 as Keyframes, a9 as ModelOptions, aa as MujocoContact, ab as MujocoContactArray, ac as ObservationLayoutItem, ad as ObservationOutput, ae as PhysicsConfig, af as PolicyActionInput, ag as PolicyInferenceInput, ah as PolicyObservationInput, ai as RayHit, aj as Register, ak as RegisteredRobotMap, al as ResourceSelector, am as RobotActuators, an as RobotBodies, ao as RobotGeoms, ap as RobotJoints, aq as RobotKeyframes, ar as RobotResource, as as RobotResources, at as RobotSensors, au as RobotSites, av as Robots, aw as ScenarioCameraConfig, ax as ScenarioMaterialConfig, ay as SceneMarker, az as SceneObject, aA as SensorResult, aB as SiteInfo, aC as SplatAssetConfig, aD as SplatScenarioConfig, aE as StateSnapshot, aF as TrajectoryData, aG as XmlPatch, aH as getContact, aI as registerRobotResources } from './types-BmneHLBM.js';
4
4
  import * as React$1 from 'react';
5
+ import React__default from 'react';
5
6
  import { ThreeElements } from '@react-three/fiber';
6
7
  import * as THREE from 'three';
7
8
 
@@ -300,7 +301,7 @@ declare const useIkController: (config: IkConfig | null) => IkContextValue | nul
300
301
  * runs before the provider's loadScene useEffect). Bodies added/removed after
301
302
  * the initial load trigger a debounced scene reload.
302
303
  */
303
- declare function Body({ name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, children, }: BodyProps): react_jsx_runtime.JSX.Element | null;
304
+ declare function Body({ name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, group, children, }: BodyProps): react_jsx_runtime.JSX.Element | null;
304
305
 
305
306
  /**
306
307
  * IkGizmo — drei PivotControls that tracks a MuJoCo site.
@@ -363,6 +364,8 @@ declare function SceneLights({ intensity }: SceneLightsProps): null;
363
364
  declare function ScenarioLighting({ preset, castShadow, intensity, }: ScenarioLightingProps): react_jsx_runtime.JSX.Element;
364
365
  declare function getScenarioBackground(preset: ScenarioLightingPreset | undefined, fallback?: string): string;
365
366
  declare function getScenarioCameraPosition(basePosition: readonly [number, number, number], scenario?: Pick<VisualScenarioConfig, 'camera'>): [number, number, number];
367
+ declare function VisualScenarioEffects(props: VisualScenarioEffectsProps): null;
368
+ declare function useVisualScenarioEffects({ scenario, enabled, applyBackground, applyFog, applyRenderer, applyMaterials, background, fogNear, fogFar, materialFilter, }: VisualScenarioEffectsProps): void;
366
369
  /**
367
370
  * Renderer-agnostic Gaussian splat environment boundary.
368
371
  *
@@ -370,8 +373,29 @@ declare function getScenarioCameraPosition(basePosition: readonly [number, numbe
370
373
  * Spark/GaussianSplats3D object as `children` once the app chooses a renderer,
371
374
  * and pass MuJoCo/MJCF collision proxy visuals via `collisionProxy`.
372
375
  */
373
- declare function SplatEnvironment({ environment, src, format, collisionProxy, collisionProxyMetadata, children, showPlaceholder, ...groupProps }: SplatEnvironmentProps): react_jsx_runtime.JSX.Element;
374
- declare function useSplatEnvironment({ environment, src, format, collisionProxy, }: SplatEnvironmentMetadataInput): SplatEnvironmentMetadata;
376
+ declare function SplatEnvironment({ environment, scenario, renderer, src, format, collisionProxy, collisionProxyMetadata, children, showPlaceholder, ...groupProps }: SplatEnvironmentProps): react_jsx_runtime.JSX.Element;
377
+ declare function useSplatEnvironment({ environment, scenario, renderer, src, format, collisionProxy, }: SplatEnvironmentMetadataInput): SplatEnvironmentMetadata;
378
+ /**
379
+ * Convert a generic visual scenario splat block into a paired visual/physics
380
+ * environment config. Returns undefined until both the splat asset and MJCF
381
+ * collision proxy are present.
382
+ */
383
+ declare function createPairedSplatEnvironment(scenario: Pick<VisualScenarioConfig, 'id' | 'label' | 'environment' | 'splat'>, options?: {
384
+ id?: string;
385
+ label?: string;
386
+ description?: string;
387
+ renderer?: SplatRendererKind;
388
+ }): PairedSplatEnvironmentConfig | undefined;
389
+ /**
390
+ * Compose a MuJoCo scene config with a paired splat collision proxy.
391
+ *
392
+ * This keeps the common hybrid setup declarative:
393
+ * robot XML remains `sceneFile`, the `.spz` remains a visual-only layer, and
394
+ * the paired MJCF collision proxy is added to `environmentFiles`.
395
+ */
396
+ declare function withSplatEnvironment(sceneConfig: SceneConfig, input: SplatSceneInput, options?: {
397
+ renderer?: SplatRendererKind;
398
+ }): SceneConfig;
375
399
  declare function createSplatEnvironmentUserData({ environment, src, format, collisionProxy, }: {
376
400
  environment?: PairedSplatEnvironmentConfig;
377
401
  src?: string;
@@ -587,13 +611,6 @@ declare function useContactEvents(bodyName: Bodies, handlers: {
587
611
  */
588
612
  declare function useKeyboardTeleop(config: KeyboardTeleopConfig): void;
589
613
 
590
- /**
591
- * @license
592
- * SPDX-License-Identifier: Apache-2.0
593
- *
594
- * usePolicy — policy decimation loop hook (spec 10.1)
595
- */
596
-
597
614
  /**
598
615
  * Framework-agnostic policy execution hook.
599
616
  *
@@ -608,7 +625,8 @@ declare function usePolicy(config: PolicyConfig): {
608
625
  readonly isRunning: boolean;
609
626
  start: () => void;
610
627
  stop: () => void;
611
- readonly lastObservation: Float64Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | number[] | null;
628
+ readonly lastObservation: PolicyVector | null;
629
+ readonly lastAction: Float64Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | number[] | null;
612
630
  };
613
631
 
614
632
  /**
@@ -733,6 +751,56 @@ declare function useVideoRecorder(options?: VideoRecorderOptions): {
733
751
  readonly recording: boolean;
734
752
  };
735
753
 
754
+ /**
755
+ * @license
756
+ * SPDX-License-Identifier: Apache-2.0
757
+ *
758
+ * useFrameCapture — still-frame capture for canvas-backed MuJoCo/R3F scenes.
759
+ */
760
+
761
+ type FrameCaptureStatus = 'idle' | 'capturing' | 'captured' | 'error';
762
+ type FrameCaptureTarget = HTMLCanvasElement | HTMLElement | null | undefined;
763
+ type FrameCaptureTargetRef = React__default.RefObject<HTMLCanvasElement | HTMLElement | null>;
764
+ interface FrameCaptureOptions {
765
+ target?: FrameCaptureTarget | FrameCaptureTargetRef;
766
+ type?: string;
767
+ quality?: number;
768
+ waitForAnimationFrame?: boolean;
769
+ }
770
+ interface FrameCaptureResult {
771
+ canvas: HTMLCanvasElement;
772
+ dataUrl: string;
773
+ type: string;
774
+ }
775
+ interface FrameCaptureBlobResult {
776
+ canvas: HTMLCanvasElement;
777
+ blob: Blob;
778
+ type: string;
779
+ }
780
+ interface FrameCaptureAPI {
781
+ status: FrameCaptureStatus;
782
+ error: Error | null;
783
+ isCapturing: boolean;
784
+ capture: (options?: FrameCaptureOptions) => Promise<FrameCaptureResult>;
785
+ captureBlob: (options?: FrameCaptureOptions) => Promise<FrameCaptureBlobResult>;
786
+ reset: () => void;
787
+ }
788
+ /**
789
+ * Capture the current canvas frame as a data URL.
790
+ *
791
+ * For WebGL scenes, create the renderer with `preserveDrawingBuffer: true`
792
+ * when you need deterministic captures after the frame has presented.
793
+ */
794
+ declare function captureFrame(options: FrameCaptureOptions): Promise<FrameCaptureResult>;
795
+ /**
796
+ * Capture the current canvas frame as a Blob.
797
+ */
798
+ declare function captureFrameBlob(options: FrameCaptureOptions): Promise<FrameCaptureBlobResult>;
799
+ /**
800
+ * React state wrapper around `captureFrame` and `captureFrameBlob`.
801
+ */
802
+ declare function useFrameCapture(defaultOptions?: FrameCaptureOptions): FrameCaptureAPI;
803
+
736
804
  /**
737
805
  * @license
738
806
  * SPDX-License-Identifier: Apache-2.0
@@ -828,4 +896,4 @@ interface CameraAnimationAPI {
828
896
  */
829
897
  declare function useCameraAnimation(): CameraAnimationAPI;
830
898
 
831
- export { ActuatedJointInfo, ActuatorInfo, Actuators, Bodies, Body, BodyProps, BodyStateResult, type CameraAnimationAPI, ContactInfo, ContactListener, ContactListenerProps, ContactMarkers, ControlGroupInfo, ControlGroupSelector, type ControllerComponent, type ControllerOptions, CtrlHandle, Debug, DebugProps, DragInteraction, DragInteractionProps, FlexRenderer, GeomInfo, IkConfig, IkContextValue, IkGizmo, IkGizmoProps, InstancedGeomRenderer, JointStateResult, Joints, KeyboardTeleopConfig, MujocoCanvas, MujocoCanvasProps, MujocoContextValue, MujocoData, type MujocoLoader, type MujocoLoaderOptions, MujocoModel, MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoProviderProps, MujocoSimAPI, MujocoSimProvider, type MujocoWasmVariant, ObservationConfig, ObservationHandle, ObservationResult, PairedSplatEnvironmentConfig, PhysicsStepCallback, PlaybackState, PolicyConfig, ScenarioLighting, ScenarioLightingPreset, ScenarioLightingProps, SceneConfig, SceneLights, SceneLightsProps, SensorHandle, SensorInfo, Sensors, SitePositionResult, Sites, SplatCollisionPrimitive, SplatCollisionProxyConfig, SplatEnvironment, SplatEnvironmentMetadata, SplatEnvironmentMetadataInput, SplatEnvironmentProps, SplatFormat, SplatRendererKind, TendonRenderer, TrajectoryFrame, TrajectoryInput, TrajectoryPlayer, TrajectoryPlayerProps, VisualScenarioConfig, buildObservation, createContiguousControlGroup, createController, createControllerHook, createSparkSplatViewerUrl, createSplatEnvironmentUserData, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getControlMap, getName, getScenarioBackground, getScenarioCameraPosition, loadScene, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useSplatEnvironment, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
899
+ export { ActuatedJointInfo, ActuatorInfo, Actuators, Bodies, Body, BodyProps, BodyStateResult, type CameraAnimationAPI, ContactInfo, ContactListener, ContactListenerProps, ContactMarkers, ControlGroupInfo, ControlGroupSelector, type ControllerComponent, type ControllerOptions, CtrlHandle, Debug, DebugProps, DragInteraction, DragInteractionProps, FlexRenderer, type FrameCaptureAPI, type FrameCaptureBlobResult, type FrameCaptureOptions, type FrameCaptureResult, type FrameCaptureStatus, type FrameCaptureTarget, type FrameCaptureTargetRef, GeomInfo, IkConfig, IkContextValue, IkGizmo, IkGizmoProps, InstancedGeomRenderer, JointStateResult, Joints, KeyboardTeleopConfig, MujocoCanvas, MujocoCanvasProps, MujocoContextValue, MujocoData, type MujocoLoader, type MujocoLoaderOptions, MujocoModel, MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoProviderProps, MujocoSimAPI, MujocoSimProvider, type MujocoWasmVariant, ObservationConfig, ObservationHandle, ObservationResult, PairedSplatEnvironmentConfig, PhysicsStepCallback, PlaybackState, PolicyConfig, PolicyVector, ScenarioLighting, ScenarioLightingPreset, ScenarioLightingProps, SceneConfig, SceneLights, SceneLightsProps, SensorHandle, SensorInfo, Sensors, SitePositionResult, Sites, SplatCollisionPrimitive, SplatCollisionProxyConfig, SplatEnvironment, SplatEnvironmentMetadata, SplatEnvironmentMetadataInput, SplatEnvironmentProps, SplatFormat, SplatRendererKind, SplatSceneInput, TendonRenderer, TrajectoryFrame, TrajectoryInput, TrajectoryPlayer, TrajectoryPlayerProps, VisualScenarioConfig, VisualScenarioEffects, VisualScenarioEffectsProps, buildObservation, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getControlMap, getName, getScenarioBackground, getScenarioCameraPosition, loadScene, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useSplatEnvironment, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder, useVisualScenarioEffects, withSplatEnvironment };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { ScenarioLighting, SplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment } from './chunk-KGFRKPLS.js';
1
+ export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment } from './chunk-SEWQULWO.js';
2
2
  import loadMujoco from '@mujoco/mujoco';
3
3
  import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
4
4
  import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
@@ -776,7 +776,8 @@ function sceneObjectToXml(obj) {
776
776
  const solref = obj.solref ? ` solref="${obj.solref}"` : "";
777
777
  const solimp = obj.solimp ? ` solimp="${obj.solimp}"` : "";
778
778
  const condim = obj.condim ? ` condim="${obj.condim}"` : "";
779
- return `<body name="${obj.name}" pos="${pos}">${joint}<geom type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}/></body>`;
779
+ const group = obj.group !== void 0 ? ` group="${obj.group}"` : "";
780
+ return `<body name="${obj.name}" pos="${pos}">${joint}<geom type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
780
781
  }
781
782
  function ensureDir(mujoco, fname) {
782
783
  const dirParts = fname.split("/");
@@ -816,6 +817,20 @@ function normalizeVfsPath(path) {
816
817
  function localFilePath(file) {
817
818
  return normalizeVfsPath(file.webkitRelativePath || file.name);
818
819
  }
820
+ function dirname(path) {
821
+ const normalized = normalizeVfsPath(path);
822
+ const idx = normalized.lastIndexOf("/");
823
+ return idx === -1 ? "" : normalized.slice(0, idx + 1);
824
+ }
825
+ function relativeVfsPath(fromDir, targetPath) {
826
+ const from = normalizeVfsPath(fromDir).split("/").filter(Boolean);
827
+ const target = normalizeVfsPath(targetPath).split("/").filter(Boolean);
828
+ while (from.length && target.length && from[0] === target[0]) {
829
+ from.shift();
830
+ target.shift();
831
+ }
832
+ return [...from.map(() => ".."), ...target].join("/") || ".";
833
+ }
819
834
  function inferSceneFile(files, options) {
820
835
  if (options?.sceneFile) return normalizeVfsPath(options.sceneFile);
821
836
  const paths = files.map(localFilePath);
@@ -834,12 +849,120 @@ function createSceneConfigFromFiles(files, options = {}) {
834
849
  src: "",
835
850
  sceneFile: inferSceneFile(fileArray, options),
836
851
  files: fileArray,
852
+ environmentFiles: options.environmentFiles?.map(normalizeVfsPath),
837
853
  homeJoints: options.homeJoints,
838
854
  xmlPatches: options.xmlPatches,
839
855
  sceneObjects: options.sceneObjects,
840
856
  onReset: options.onReset
841
857
  };
842
858
  }
859
+ var ENVIRONMENT_MERGE_SECTIONS = [
860
+ "asset",
861
+ "worldbody",
862
+ "contact",
863
+ "equality",
864
+ "tendon",
865
+ "sensor",
866
+ "keyframe",
867
+ "custom",
868
+ "extension"
869
+ ];
870
+ function directChild(parent, tagName) {
871
+ const lower = tagName.toLowerCase();
872
+ for (const child of Array.from(parent.children)) {
873
+ if (child.tagName.toLowerCase() === lower) return child;
874
+ }
875
+ return null;
876
+ }
877
+ function ensureTopLevelSection(doc, tagName) {
878
+ const root = doc.documentElement;
879
+ const existing = directChild(root, tagName);
880
+ if (existing) return existing;
881
+ const section = doc.createElement(tagName);
882
+ if (tagName === "asset") {
883
+ const worldbody = directChild(root, "worldbody");
884
+ if (worldbody) root.insertBefore(section, worldbody);
885
+ else root.appendChild(section);
886
+ } else {
887
+ root.appendChild(section);
888
+ }
889
+ return section;
890
+ }
891
+ function readCompilerDirs(doc) {
892
+ const compiler = directChild(doc.documentElement, "compiler");
893
+ const assetDir = compiler?.getAttribute("assetdir") || "";
894
+ return {
895
+ meshDir: compiler?.getAttribute("meshdir") || assetDir,
896
+ textureDir: compiler?.getAttribute("texturedir") || assetDir
897
+ };
898
+ }
899
+ function isExternalPath(path) {
900
+ return /^[a-z]+:\/\//i.test(path) || path.startsWith("package://") || path.startsWith("/");
901
+ }
902
+ function fileReferencePrefix(el, compilerDirs) {
903
+ const tag = el.tagName.toLowerCase();
904
+ if (tag === "mesh") return compilerDirs.meshDir ? compilerDirs.meshDir + "/" : "";
905
+ if (tag === "texture" || tag === "hfield") return compilerDirs.textureDir ? compilerDirs.textureDir + "/" : "";
906
+ return "";
907
+ }
908
+ function rewriteFileReferencesForMerge(node, sourceFile, targetFile, sourceDoc) {
909
+ const sourceDir = dirname(sourceFile);
910
+ const targetDir = dirname(targetFile);
911
+ const compilerDirs = readCompilerDirs(sourceDoc);
912
+ node.querySelectorAll("[file], [filename]").forEach((el) => {
913
+ const attr = el.hasAttribute("file") ? "file" : "filename";
914
+ const value = el.getAttribute(attr);
915
+ if (!value || isExternalPath(value)) return;
916
+ const sourceRelativePath = normalizeVfsPath(fileReferencePrefix(el, compilerDirs) + value);
917
+ const resolvedPath = normalizeVfsPath(sourceDir + sourceRelativePath);
918
+ el.setAttribute(attr, relativeVfsPath(targetDir, resolvedPath));
919
+ });
920
+ }
921
+ function hasParseError(doc) {
922
+ return doc.getElementsByTagName("parsererror").length > 0;
923
+ }
924
+ function composeEnvironmentXml(sceneXml, config, parser, environmentXmlByPath) {
925
+ const environmentFiles = config.environmentFiles?.map(normalizeVfsPath) ?? [];
926
+ if (!environmentFiles.length) return sceneXml;
927
+ const sceneDoc = parser.parseFromString(sceneXml, "text/xml");
928
+ if (hasParseError(sceneDoc)) {
929
+ console.warn(`Could not compose environments: failed to parse ${config.sceneFile}`);
930
+ return sceneXml;
931
+ }
932
+ for (const environmentFile of environmentFiles) {
933
+ const environmentXml = environmentXmlByPath.get(environmentFile);
934
+ if (!environmentXml) {
935
+ console.warn(`Environment XML not found: ${environmentFile}`);
936
+ continue;
937
+ }
938
+ const environmentDoc = parser.parseFromString(environmentXml, "text/xml");
939
+ if (hasParseError(environmentDoc)) {
940
+ console.warn(`Skipping environment XML with parse errors: ${environmentFile}`);
941
+ continue;
942
+ }
943
+ for (const sectionName of ENVIRONMENT_MERGE_SECTIONS) {
944
+ const environmentSection = directChild(environmentDoc.documentElement, sectionName);
945
+ if (!environmentSection?.children.length) continue;
946
+ const targetSection = ensureTopLevelSection(sceneDoc, sectionName);
947
+ for (const child of Array.from(environmentSection.children)) {
948
+ const imported = sceneDoc.importNode(child, true);
949
+ rewriteFileReferencesForMerge(imported, environmentFile, config.sceneFile, environmentDoc);
950
+ targetSection.appendChild(imported);
951
+ }
952
+ }
953
+ }
954
+ return new XMLSerializer().serializeToString(sceneDoc);
955
+ }
956
+ function findTextByConfiguredPath(textByPath, configuredPath) {
957
+ const normalized = normalizeVfsPath(configuredPath);
958
+ const direct = textByPath.get(normalized);
959
+ if (direct) return direct;
960
+ const suffix = "/" + normalized;
961
+ for (const [path, text] of textByPath) {
962
+ if (path.endsWith(suffix) || path === normalized.split("/").pop()) return text;
963
+ }
964
+ return void 0;
965
+ }
843
966
  function applyXmlPatches(text, fname, config) {
844
967
  let result = text;
845
968
  for (const patch of config.xmlPatches ?? []) {
@@ -902,10 +1025,21 @@ async function loadSceneFromFiles(mujoco, config, onProgress) {
902
1025
  if (isModelTextFile(path)) {
903
1026
  const text = applyXmlPatches(await file.text(), path, config);
904
1027
  textByPath.set(path, text);
905
- mujoco.FS.writeFile(`/working/${path}`, text);
906
1028
  } else {
907
1029
  mujoco.FS.writeFile(`/working/${path}`, new Uint8Array(await file.arrayBuffer()));
1030
+ written.add(path);
908
1031
  }
1032
+ }
1033
+ const environmentXmlByPath = /* @__PURE__ */ new Map();
1034
+ for (const environmentFile of config.environmentFiles?.map(normalizeVfsPath) ?? []) {
1035
+ const environmentXml = findTextByConfiguredPath(textByPath, environmentFile);
1036
+ if (environmentXml) environmentXmlByPath.set(environmentFile, environmentXml);
1037
+ }
1038
+ for (const [path, text] of textByPath) {
1039
+ const composedText = path === config.sceneFile ? composeEnvironmentXml(text, config, parser, environmentXmlByPath) : text;
1040
+ textByPath.set(path, composedText);
1041
+ ensureDir(mujoco, path);
1042
+ mujoco.FS.writeFile(`/working/${path}`, composedText);
909
1043
  written.add(path);
910
1044
  }
911
1045
  for (const [path, text] of textByPath) {
@@ -954,6 +1088,17 @@ async function loadScene(mujoco, config, onProgress) {
954
1088
  } catch {
955
1089
  }
956
1090
  const baseUrl = config.src.endsWith("/") ? config.src : config.src + "/";
1091
+ const environmentXmlByPath = /* @__PURE__ */ new Map();
1092
+ const environmentFiles = config.environmentFiles?.map(normalizeVfsPath) ?? [];
1093
+ for (const environmentFile of environmentFiles) {
1094
+ onProgress?.(`Downloading ${environmentFile}...`);
1095
+ const res = await fetch(baseUrl + environmentFile);
1096
+ if (!res.ok) {
1097
+ console.warn(`Failed to fetch environment XML ${environmentFile}: ${res.status} ${res.statusText}`);
1098
+ continue;
1099
+ }
1100
+ environmentXmlByPath.set(environmentFile, applyXmlPatches(await res.text(), environmentFile, config));
1101
+ }
957
1102
  const downloaded = /* @__PURE__ */ new Set();
958
1103
  const xmlQueue = [config.sceneFile];
959
1104
  const assetFiles = [];
@@ -972,7 +1117,8 @@ async function loadScene(mujoco, config, onProgress) {
972
1117
  console.warn(`Failed to fetch ${fname}: ${res.status} ${res.statusText}`);
973
1118
  continue;
974
1119
  }
975
- const text = applyXmlPatches(await res.text(), fname, config);
1120
+ const patchedText = applyXmlPatches(await res.text(), fname, config);
1121
+ const text = fname === config.sceneFile ? composeEnvironmentXml(patchedText, config, parser, environmentXmlByPath) : patchedText;
976
1122
  ensureDir(mujoco, fname);
977
1123
  mujoco.FS.writeFile(`/working/${fname}`, text);
978
1124
  scanDependencies(text, fname, parser, downloaded, xmlQueue);
@@ -2209,6 +2355,7 @@ var MujocoCanvas = forwardRef(
2209
2355
  paused,
2210
2356
  speed,
2211
2357
  interpolate,
2358
+ loadingFallback,
2212
2359
  children,
2213
2360
  ...canvasProps
2214
2361
  }, ref) {
@@ -2218,7 +2365,10 @@ var MujocoCanvas = forwardRef(
2218
2365
  onError(new Error(wasmError ?? "WASM load failed"));
2219
2366
  }
2220
2367
  }, [wasmStatus, wasmError, onError]);
2221
- if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
2368
+ if (wasmStatus === "loading" || !mujoco) {
2369
+ return loadingFallback ? /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: loadingFallback }) : null;
2370
+ }
2371
+ if (wasmStatus === "error") {
2222
2372
  return null;
2223
2373
  }
2224
2374
  return /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: /* @__PURE__ */ jsx(
@@ -2860,6 +3010,7 @@ function Body({
2860
3010
  solref,
2861
3011
  solimp,
2862
3012
  condim,
3013
+ group,
2863
3014
  children
2864
3015
  }) {
2865
3016
  const { bodyRegistryRef, hiddenBodiesRef, requestBodyReload, mjDataRef, mjModelRef, status } = useMujocoContext();
@@ -2879,7 +3030,8 @@ function Body({
2879
3030
  friction,
2880
3031
  solref,
2881
3032
  solimp,
2882
- condim
3033
+ condim,
3034
+ group
2883
3035
  };
2884
3036
  bodyRegistryRef.current.set(name, { definition, hasCustomChildren: hasChildren });
2885
3037
  if (hasChildren) {
@@ -2892,7 +3044,7 @@ function Body({
2892
3044
  requestBodyReload();
2893
3045
  }
2894
3046
  };
2895
- }, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
3047
+ }, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, group, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
2896
3048
  useEffect(() => {
2897
3049
  if (status !== "ready") return;
2898
3050
  const model = mjModelRef.current;
@@ -2904,12 +3056,12 @@ function Body({
2904
3056
  if (!hasChildren) return;
2905
3057
  const data = mjDataRef.current;
2906
3058
  const id = bodyIdRef.current;
2907
- const group = groupRef.current;
2908
- if (!data || id < 0 || !group) return;
3059
+ const group2 = groupRef.current;
3060
+ if (!data || id < 0 || !group2) return;
2909
3061
  const i3 = id * 3;
2910
3062
  const i4 = id * 4;
2911
- group.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
2912
- group.quaternion.set(
3063
+ group2.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
3064
+ group2.quaternion.set(
2913
3065
  data.xquat[i4 + 1],
2914
3066
  data.xquat[i4 + 2],
2915
3067
  data.xquat[i4 + 3],
@@ -4584,22 +4736,25 @@ function useKeyboardTeleop(config) {
4584
4736
  });
4585
4737
  }
4586
4738
  function usePolicy(config) {
4587
- const { mjModelRef } = useMujocoContext();
4588
4739
  const lastActionTimeRef = useRef(0);
4740
+ const lastObservationRef = useRef(null);
4589
4741
  const lastActionRef = useRef(null);
4590
- const isRunningRef = useRef(true);
4742
+ const isRunningRef = useRef(config.enabled ?? true);
4591
4743
  const configRef = useRef(config);
4592
4744
  configRef.current = config;
4745
+ isRunningRef.current = config.enabled ?? isRunningRef.current;
4593
4746
  useBeforePhysicsStep((model, data) => {
4594
4747
  if (!isRunningRef.current) return;
4595
4748
  const cfg = configRef.current;
4596
4749
  model.opt?.timestep ?? 2e-3;
4597
4750
  const interval = 1 / cfg.frequency;
4598
4751
  if (data.time - lastActionTimeRef.current >= interval) {
4599
- const obs = cfg.onObservation(model, data);
4600
- cfg.onAction(obs, model, data);
4752
+ const observation = cfg.onObservation({ model, data });
4753
+ const action = cfg.infer ? cfg.infer({ observation, model, data }) : observation;
4754
+ cfg.onAction({ action, observation, model, data });
4601
4755
  lastActionTimeRef.current = data.time;
4602
- lastActionRef.current = obs;
4756
+ lastObservationRef.current = observation;
4757
+ lastActionRef.current = action;
4603
4758
  }
4604
4759
  });
4605
4760
  return {
@@ -4613,6 +4768,9 @@ function usePolicy(config) {
4613
4768
  isRunningRef.current = false;
4614
4769
  },
4615
4770
  get lastObservation() {
4771
+ return lastObservationRef.current;
4772
+ },
4773
+ get lastAction() {
4616
4774
  return lastActionRef.current;
4617
4775
  }
4618
4776
  };
@@ -4809,6 +4967,114 @@ function useVideoRecorder(options = {}) {
4809
4967
  }
4810
4968
  };
4811
4969
  }
4970
+ function isTargetRef(target) {
4971
+ return Boolean(target && typeof target === "object" && "current" in target);
4972
+ }
4973
+ function resolveCanvasTarget(target) {
4974
+ const resolvedTarget = isTargetRef(target) ? target.current : target;
4975
+ if (!resolvedTarget) {
4976
+ throw new Error("No frame capture target is available.");
4977
+ }
4978
+ if (resolvedTarget instanceof HTMLCanvasElement) {
4979
+ return resolvedTarget;
4980
+ }
4981
+ const canvas = resolvedTarget.querySelector("canvas");
4982
+ if (!canvas) {
4983
+ throw new Error("Frame capture target does not contain a canvas.");
4984
+ }
4985
+ return canvas;
4986
+ }
4987
+ function waitForNextAnimationFrame() {
4988
+ return new Promise((resolve) => {
4989
+ requestAnimationFrame(() => resolve());
4990
+ });
4991
+ }
4992
+ async function captureFrame(options) {
4993
+ const type = options.type ?? "image/png";
4994
+ const canvas = resolveCanvasTarget(options.target);
4995
+ if (options.waitForAnimationFrame ?? true) {
4996
+ await waitForNextAnimationFrame();
4997
+ }
4998
+ return {
4999
+ canvas,
5000
+ dataUrl: canvas.toDataURL(type, options.quality),
5001
+ type
5002
+ };
5003
+ }
5004
+ async function captureFrameBlob(options) {
5005
+ const type = options.type ?? "image/png";
5006
+ const canvas = resolveCanvasTarget(options.target);
5007
+ if (options.waitForAnimationFrame ?? true) {
5008
+ await waitForNextAnimationFrame();
5009
+ }
5010
+ const blob = await new Promise((resolve, reject) => {
5011
+ canvas.toBlob(
5012
+ (nextBlob) => {
5013
+ if (nextBlob) {
5014
+ resolve(nextBlob);
5015
+ } else {
5016
+ reject(new Error("Canvas frame capture did not produce a Blob."));
5017
+ }
5018
+ },
5019
+ type,
5020
+ options.quality
5021
+ );
5022
+ });
5023
+ return { canvas, blob, type };
5024
+ }
5025
+ function useFrameCapture(defaultOptions = {}) {
5026
+ const [status, setStatus] = useState("idle");
5027
+ const [error, setError] = useState(null);
5028
+ const reset = useCallback(() => {
5029
+ setStatus("idle");
5030
+ setError(null);
5031
+ }, []);
5032
+ const capture = useCallback(
5033
+ async (options = {}) => {
5034
+ setStatus("capturing");
5035
+ setError(null);
5036
+ try {
5037
+ const result = await captureFrame({ ...defaultOptions, ...options });
5038
+ setStatus("captured");
5039
+ return result;
5040
+ } catch (nextError) {
5041
+ const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
5042
+ setError(error2);
5043
+ setStatus("error");
5044
+ throw error2;
5045
+ }
5046
+ },
5047
+ [defaultOptions]
5048
+ );
5049
+ const captureBlob = useCallback(
5050
+ async (options = {}) => {
5051
+ setStatus("capturing");
5052
+ setError(null);
5053
+ try {
5054
+ const result = await captureFrameBlob({
5055
+ ...defaultOptions,
5056
+ ...options
5057
+ });
5058
+ setStatus("captured");
5059
+ return result;
5060
+ } catch (nextError) {
5061
+ const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
5062
+ setError(error2);
5063
+ setStatus("error");
5064
+ throw error2;
5065
+ }
5066
+ },
5067
+ [defaultOptions]
5068
+ );
5069
+ return {
5070
+ status,
5071
+ error,
5072
+ isCapturing: status === "capturing",
5073
+ capture,
5074
+ captureBlob,
5075
+ reset
5076
+ };
5077
+ }
4812
5078
  function useCtrlNoise(config = {}) {
4813
5079
  const { mjModelRef } = useMujocoContext();
4814
5080
  const configRef = useRef(config);
@@ -5094,6 +5360,12 @@ function useCameraAnimation() {
5094
5360
  *
5095
5361
  * useVideoRecorder — canvas video recording hook (spec 13.3)
5096
5362
  */
5363
+ /**
5364
+ * @license
5365
+ * SPDX-License-Identifier: Apache-2.0
5366
+ *
5367
+ * useFrameCapture — still-frame capture for canvas-backed MuJoCo/R3F scenes.
5368
+ */
5097
5369
  /**
5098
5370
  * @license
5099
5371
  * SPDX-License-Identifier: Apache-2.0
@@ -5124,6 +5396,6 @@ function useCameraAnimation() {
5124
5396
  * useCameraAnimation — composable camera animation hook.
5125
5397
  */
5126
5398
 
5127
- 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, createContiguousControlGroup, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getContact, getControlMap, getName, loadScene, registerRobotResources, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
5399
+ 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, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getContact, getControlMap, getName, loadScene, registerRobotResources, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
5128
5400
  //# sourceMappingURL=index.js.map
5129
5401
  //# sourceMappingURL=index.js.map