mujoco-react 10.0.0 → 10.1.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/spark.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _sparkjsdev_spark from '@sparkjsdev/spark';
3
- import { n as SplatEnvironmentProps, q as PairedSplatEnvironmentConfig, S as SceneConfig, t as SplatEnvironmentReadiness, o as VisualScenarioConfig } from './types-BaSMqJHT.js';
3
+ import { n as SplatEnvironmentProps, q as PairedSplatEnvironmentConfig, S as SceneConfig, t as SplatEnvironmentReadiness, o as VisualScenarioConfig } from './types-BHBNJubg.js';
4
4
  import 'react';
5
5
  import '@react-three/fiber';
6
6
  import 'three';
package/dist/spark.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-QTCAVQS6.js';
1
+ import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-FEKBKHEN.js';
2
2
  import { useThree } from '@react-three/fiber';
3
3
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import * as THREE from 'three';
@@ -177,7 +177,15 @@ interface MujocoModel {
177
177
  geom_contype: Int32Array;
178
178
  geom_conaffinity: Int32Array;
179
179
  geom_friction: Float64Array;
180
+ mat_texid: Int32Array;
181
+ mat_texrepeat: Float32Array;
182
+ mat_texuniform: Uint8Array;
180
183
  mat_rgba: Float32Array;
184
+ tex_adr: Int32Array;
185
+ tex_data: Uint8Array;
186
+ tex_height: Int32Array;
187
+ tex_nchannel: Int32Array;
188
+ tex_width: Int32Array;
181
189
  mesh_vertadr: Int32Array;
182
190
  mesh_vertnum: Int32Array;
183
191
  mesh_faceadr: Int32Array;
@@ -313,6 +321,8 @@ interface MujocoModule {
313
321
  }
314
322
  interface SceneObject {
315
323
  name: string;
324
+ /** MuJoCo geom name. Defaults to `${name}_geom` for generated objects. */
325
+ geomName?: string;
316
326
  type: 'box' | 'sphere' | 'cylinder';
317
327
  size: [number, number, number];
318
328
  position: [number, number, number];
@@ -598,16 +608,123 @@ interface PolicyObservationInput {
598
608
  interface PolicyInferenceInput extends PolicyObservationInput {
599
609
  observation: PolicyVector;
600
610
  }
611
+ type PolicyActionChunk = readonly PolicyVector[];
612
+ type PolicyInferenceOutput = PolicyVector | PolicyActionChunk;
613
+ type PolicyInferenceResult = PolicyInferenceOutput | Promise<PolicyInferenceOutput>;
601
614
  interface PolicyActionInput extends PolicyInferenceInput {
602
615
  action: PolicyVector;
603
616
  }
617
+ interface PolicyAPI {
618
+ readonly isRunning: boolean;
619
+ start: () => void;
620
+ stop: () => void;
621
+ clearQueue: () => void;
622
+ reset: () => void;
623
+ readonly inFlight: boolean;
624
+ readonly queuedActions: number;
625
+ readonly lastObservation: PolicyVector | null;
626
+ readonly lastAction: PolicyVector | null;
627
+ readonly lastError: unknown;
628
+ }
604
629
  interface PolicyConfig {
605
630
  frequency: number;
606
631
  enabled?: boolean;
632
+ /** Start async inference while this many queued actions remain. Defaults to 0. */
633
+ prefetchThreshold?: number;
634
+ /**
635
+ * How async action chunks update the queue.
636
+ * - append preserves legacy FIFO behavior.
637
+ * - replace is useful for receding-horizon policies where a fresh chunk should supersede stale queued actions.
638
+ */
639
+ queueStrategy?: 'append' | 'replace';
640
+ /**
641
+ * Clear queued actions and ignore in-flight async results when `stop()` is called.
642
+ * Defaults to false so callers can choose pause/resume behavior explicitly.
643
+ */
644
+ clearQueueOnStop?: boolean;
607
645
  onObservation: (input: PolicyObservationInput) => PolicyVector;
608
646
  /** Run policy inference. Omit to pass observations directly to `onAction` for custom inline controllers. */
609
- infer?: (input: PolicyInferenceInput) => PolicyVector;
647
+ infer?: (input: PolicyInferenceInput) => PolicyInferenceResult;
610
648
  onAction: (input: PolicyActionInput) => void;
649
+ /** Called when async inference rejects. */
650
+ onError?: (error: unknown) => void;
651
+ }
652
+ interface RemotePolicyRequestInput extends PolicyInferenceInput {
653
+ /** True for the first request after hook construction or `reset()`. */
654
+ reset: boolean;
655
+ /** Zero-based request index since construction or `reset()`. */
656
+ requestIndex: number;
657
+ /** Aborts when the request is no longer needed, e.g. after pause/reset. */
658
+ signal: AbortSignal;
659
+ }
660
+ interface RemotePolicyRequestInfo extends RemotePolicyRequestInput {
661
+ body: unknown;
662
+ requestStartedAt: number;
663
+ }
664
+ interface RemotePolicyResponseInfo extends RemotePolicyRequestInfo {
665
+ response: Response;
666
+ responseBody: unknown;
667
+ responseFinishedAt: number;
668
+ requestMs: number;
669
+ }
670
+ type RemotePolicyStatus = 'idle' | 'requesting' | 'ready' | 'error' | 'aborted';
671
+ interface RemotePolicyConfig extends Omit<PolicyConfig, 'infer'> {
672
+ endpoint: string | URL;
673
+ method?: string;
674
+ headers?: HeadersInit;
675
+ credentials?: RequestCredentials;
676
+ /** Additional external cancellation signal for remote inference requests. */
677
+ signal?: AbortSignal;
678
+ /**
679
+ * Abort the active HTTP request when `stop()` or `reset()` is called.
680
+ * Defaults to true so paused policies stop consuming server work.
681
+ */
682
+ abortOnStop?: boolean;
683
+ fetcher?: typeof fetch;
684
+ requestInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'credentials' | 'signal'>;
685
+ buildRequest?: (input: RemotePolicyRequestInput) => unknown | Promise<unknown>;
686
+ readResponse?: (response: Response) => unknown | Promise<unknown>;
687
+ parseResponse?: (responseBody: unknown, info: RemotePolicyResponseInfo) => PolicyInferenceResult;
688
+ onRequest?: (info: RemotePolicyRequestInfo) => void;
689
+ onResponse?: (info: RemotePolicyResponseInfo) => void;
690
+ }
691
+ interface RemotePolicyAPI extends PolicyAPI {
692
+ abort: (reason?: unknown) => void;
693
+ readonly remoteStatus: RemotePolicyStatus;
694
+ readonly requestCount: number;
695
+ readonly responseCount: number;
696
+ readonly lastRequestBody: unknown;
697
+ readonly lastResponseBody: unknown;
698
+ readonly lastHttpStatus: number | null;
699
+ readonly lastRequestMs: number | null;
700
+ }
701
+ interface PolicyCameraFrameStream extends CameraFrameCaptureOptions {
702
+ /** Image key used in policy payloads, e.g. `image`, `front`, or `wrist_cam`. */
703
+ key: string;
704
+ /** Additional payload keys that should receive the same data URL. */
705
+ aliases?: readonly string[];
706
+ }
707
+ interface PolicyCameraFrameCaptureOptions {
708
+ streams: readonly PolicyCameraFrameStream[];
709
+ /**
710
+ * Include `observation.images.${key}` for every captured stream.
711
+ * Defaults to true because LeRobot-style policies usually use these names.
712
+ */
713
+ includeObservationImageAliases?: boolean;
714
+ }
715
+ interface PolicyCameraFrameCaptureResult {
716
+ frames: Record<string, CameraFrameCaptureResult>;
717
+ images: Record<string, string>;
718
+ /** Human-readable source summary for UI/debug telemetry. */
719
+ sourceSummary: string;
720
+ capturedAt: number;
721
+ }
722
+ interface PolicyCameraFrameCaptureAPI {
723
+ status: FrameCaptureStatus;
724
+ error: Error | null;
725
+ isCapturing: boolean;
726
+ capture: (options?: Partial<PolicyCameraFrameCaptureOptions>) => Promise<PolicyCameraFrameCaptureResult>;
727
+ reset: () => void;
611
728
  }
612
729
  type ObservationOutput = 'float32' | 'float64';
613
730
  interface ObservationConfig {
@@ -1026,6 +1143,10 @@ interface CameraFrameCaptureOptions {
1026
1143
  lookAt?: CameraFrameCaptureVector3;
1027
1144
  quaternion?: CameraFrameCaptureQuaternion;
1028
1145
  up?: CameraFrameCaptureVector3;
1146
+ /** Local-space offset applied after resolving a mounted MuJoCo camera/site/body pose. */
1147
+ positionOffset?: CameraFrameCaptureVector3;
1148
+ /** Local-space rotation applied after resolving a mounted MuJoCo camera/site/body pose. Array values use Three.js order: [x, y, z, w]. */
1149
+ quaternionOffset?: CameraFrameCaptureQuaternion;
1029
1150
  width?: number;
1030
1151
  height?: number;
1031
1152
  type?: string;
@@ -1035,6 +1156,16 @@ interface CameraFrameCaptureOptions {
1035
1156
  far?: number;
1036
1157
  /** Provenance for the camera pose used by the capture. Usually set by the MuJoCo provider. */
1037
1158
  source?: CameraFrameCaptureSource;
1159
+ /** Hide rendered Three objects whose MuJoCo geom group is in this list. */
1160
+ hiddenGeomGroups?: readonly number[];
1161
+ /** When provided, only rendered Three objects whose MuJoCo geom group is in this list are visible. */
1162
+ visibleGeomGroups?: readonly number[];
1163
+ /** Hide rendered Three objects whose MuJoCo geom name is in this list. */
1164
+ hiddenGeomNames?: readonly string[];
1165
+ /** Optional clear color for this capture only. Defaults to the renderer's current clear color. */
1166
+ background?: THREE.ColorRepresentation;
1167
+ /** Optional clear alpha for this capture only. Defaults to the renderer's current clear alpha. */
1168
+ backgroundAlpha?: number;
1038
1169
  }
1039
1170
  type CameraFrameCaptureSource = {
1040
1171
  kind: 'mujoco-camera';
@@ -1219,4 +1350,4 @@ interface ArrayJointStateResult {
1219
1350
  velocity: React__default.RefObject<Float64Array>;
1220
1351
  }
1221
1352
 
1222
- export { type ArrayJointStateResult as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type ScenarioLightingPreset as E, type SplatEnvironmentMetadataInput as F, type SplatEnvironmentMetadata as G, type SplatSceneInput as H, type IkConfig as I, type DebugProps as J, type GeomInfo as K, type ContactListenerProps as L, type MujocoContextValue as M, type ActuatorInfo as N, type ObservationConfig as O, type PhysicsStepCallback as P, type Sites as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type SitePositionResult as U, type VisualScenarioEffectsProps as V, type Sensors as W, type SensorHandle as X, type SensorInfo as Y, type Joints as Z, type ScalarJointStateResult as _, type MujocoCanvasProps as a, type Models as a$, type JointStateOptions as a0, type JointStateResult as a1, type Bodies as a2, type BodyStateResult as a3, type Actuators as a4, type CtrlHandle as a5, type ContactInfo as a6, type KeyboardTeleopConfig as a7, type KeyboardIkTargetConfig as a8, type PolicyConfig as a9, type CameraFrameSequenceStepInput as aA, type CameraInfo as aB, type ControlJointInfo as aC, type FrameCaptureStatus as aD, type FrameCaptureTarget as aE, type FrameCaptureTargetRef as aF, type Geoms as aG, type IKSolveFn as aH, type IkGizmoDragInput as aI, type IkSolveInput as aJ, type JointInfo as aK, type JointStateKind as aL, type KeyBinding as aM, type KeyboardIkTargetAction as aN, type KeyboardIkTargetBinding as aO, type Keyframes as aP, ModelActuators as aQ, ModelBodies as aR, ModelCameras as aS, ModelGeoms as aT, ModelJoints as aU, ModelKeyframes as aV, type ModelOptions as aW, type ModelResource as aX, ModelResources as aY, ModelSensors as aZ, ModelSites as a_, type PolicyVector as aa, type ObservationHandle as ab, type TrajectoryInput as ac, type TrajectoryStateChangeInput as ad, type PlaybackState as ae, type TrajectoryFrame as af, type FrameCaptureOptions as ag, type FrameCaptureResult as ah, type FrameCaptureBlobResult as ai, type FrameCaptureAPI as aj, type CameraFrameCaptureOptions as ak, type CameraFrameCaptureAPI as al, type CameraFrameSequenceRecorderAPI as am, type Cameras as an, type CameraFrameSequenceCamera as ao, type CameraFrameCaptureSource as ap, type CameraFrameSequenceOptions as aq, type CameraFrameSequenceResult as ar, type CameraFrameCaptureResult as as, type CameraFrameCaptureBlobResult as at, type BodyInfo as au, type CameraFrameCaptureQuaternion as av, type CameraFrameCaptureVector3 as aw, type CameraFrameSequenceCameraSummary as ax, type CameraFrameSequenceFrame as ay, type CameraFrameSequenceSampleInput as az, type MujocoSimAPI as b, type MujocoContact as b0, type MujocoContactArray as b1, type MujocoFrameCaptureOptions as b2, type ObservationLayoutItem as b3, type ObservationOutput as b4, type PhysicsConfig as b5, type PhysicsStepInput as b6, type PolicyActionInput as b7, type PolicyInferenceInput as b8, type PolicyObservationInput as b9, type RayHit as ba, type Register as bb, type RegisteredModelMap as bc, type ResetCallbackInput as bd, type ResolvedScenarioCameraConfig as be, type ResolvedScenarioMaterialConfig as bf, type ResourceSelector as bg, type ScenarioCameraConfig as bh, type ScenarioMaterialConfig as bi, type SceneMarker as bj, type SceneObject as bk, type SensorResult as bl, type SiteInfo as bm, type SplatAssetConfig as bn, type SplatScenarioConfig as bo, type StateSnapshot as bp, type TrajectoryData as bq, type TrajectoryFrameCallbackInput as br, type VisualScenarioMaterialFilterInput as bs, type XmlPatch as bt, getContact as bu, registerModelResources as bv, type StepCallbackInput as c, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoModel as f, type MujocoData as g, type ControlGroupSelector as h, type ObservationResult as i, type IkContextValue as j, type IkGizmoProps as k, type SceneLightsProps as l, type ScenarioLightingProps as m, type SplatEnvironmentProps as n, type VisualScenarioConfig as o, type SplatRendererKind as p, type PairedSplatEnvironmentConfig as q, type SplatFormat as r, type SplatCollisionProxyConfig as s, type SplatEnvironmentReadiness as t, type SplatCollisionPrimitive as u, SplatEnvironmentReadinessStatus as v, type SplatSceneConfigInput as w, type SplatSceneConfigState as x, type VisualScenarioExecutionContextInput as y, type VisualScenarioExecutionContext as z };
1353
+ export { type ArrayJointStateResult as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type ScenarioLightingPreset as E, type SplatEnvironmentMetadataInput as F, type SplatEnvironmentMetadata as G, type SplatSceneInput as H, type IkConfig as I, type DebugProps as J, type GeomInfo as K, type ContactListenerProps as L, type MujocoContextValue as M, type ActuatorInfo as N, type ObservationConfig as O, type PhysicsStepCallback as P, type Sites as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type SitePositionResult as U, type VisualScenarioEffectsProps as V, type Sensors as W, type SensorHandle as X, type SensorInfo as Y, type Joints as Z, type ScalarJointStateResult as _, type MujocoCanvasProps as a, ModelGeoms as a$, type JointStateOptions as a0, type JointStateResult as a1, type Bodies as a2, type BodyStateResult as a3, type Geoms as a4, type Actuators as a5, type CtrlHandle as a6, type ContactInfo as a7, type KeyboardTeleopConfig as a8, type KeyboardIkTargetConfig as a9, type CameraFrameSequenceRecorderAPI as aA, type CameraFrameCaptureResult as aB, type CameraFrameCaptureBlobResult as aC, type PolicyVector as aD, type BodyInfo as aE, type CameraFrameCaptureQuaternion as aF, type CameraFrameCaptureVector3 as aG, type CameraFrameSequenceCameraSummary as aH, type CameraFrameSequenceFrame as aI, type CameraFrameSequenceSampleInput as aJ, type CameraFrameSequenceStepInput as aK, type CameraInfo as aL, type ControlJointInfo as aM, type FrameCaptureTarget as aN, type FrameCaptureTargetRef as aO, type IKSolveFn as aP, type IkGizmoDragInput as aQ, type IkSolveInput as aR, type JointInfo as aS, type JointStateKind as aT, type KeyBinding as aU, type KeyboardIkTargetAction as aV, type KeyboardIkTargetBinding as aW, type Keyframes as aX, ModelActuators as aY, ModelBodies as aZ, ModelCameras as a_, type PolicyConfig as aa, type PolicyAPI as ab, type RemotePolicyConfig as ac, type RemotePolicyAPI as ad, type ObservationHandle as ae, type ObservationOutput as af, type TrajectoryInput as ag, type TrajectoryStateChangeInput as ah, type PlaybackState as ai, type TrajectoryFrame as aj, type FrameCaptureOptions as ak, type FrameCaptureResult as al, type FrameCaptureBlobResult as am, type FrameCaptureAPI as an, type CameraFrameCaptureOptions as ao, type CameraFrameCaptureAPI as ap, type Cameras as aq, type CameraFrameSequenceCamera as ar, type CameraFrameCaptureSource as as, type CameraFrameSequenceOptions as at, type CameraFrameSequenceResult as au, type PolicyCameraFrameStream as av, type PolicyCameraFrameCaptureOptions as aw, type PolicyCameraFrameCaptureResult as ax, type FrameCaptureStatus as ay, type PolicyCameraFrameCaptureAPI as az, type MujocoSimAPI as b, ModelJoints as b0, ModelKeyframes as b1, type ModelOptions as b2, type ModelResource as b3, ModelResources as b4, ModelSensors as b5, ModelSites as b6, type Models as b7, type MujocoContact as b8, type MujocoContactArray as b9, type SiteInfo as bA, type SplatAssetConfig as bB, type SplatScenarioConfig as bC, type StateSnapshot as bD, type TrajectoryData as bE, type TrajectoryFrameCallbackInput as bF, type VisualScenarioMaterialFilterInput as bG, type XmlPatch as bH, getContact as bI, registerModelResources as bJ, type MujocoFrameCaptureOptions as ba, type ObservationLayoutItem as bb, type PhysicsConfig as bc, type PhysicsStepInput as bd, type PolicyActionChunk as be, type PolicyActionInput as bf, type PolicyInferenceInput as bg, type PolicyInferenceOutput as bh, type PolicyInferenceResult as bi, type PolicyObservationInput as bj, type RayHit as bk, type Register as bl, type RegisteredModelMap as bm, type RemotePolicyRequestInfo as bn, type RemotePolicyRequestInput as bo, type RemotePolicyResponseInfo as bp, type RemotePolicyStatus as bq, type ResetCallbackInput as br, type ResolvedScenarioCameraConfig as bs, type ResolvedScenarioMaterialConfig as bt, type ResourceSelector as bu, type ScenarioCameraConfig as bv, type ScenarioMaterialConfig as bw, type SceneMarker as bx, type SceneObject as by, type SensorResult as bz, type StepCallbackInput as c, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoModel as f, type MujocoData as g, type ControlGroupSelector as h, type ObservationResult as i, type IkContextValue as j, type IkGizmoProps as k, type SceneLightsProps as l, type ScenarioLightingProps as m, type SplatEnvironmentProps as n, type VisualScenarioConfig as o, type SplatRendererKind as p, type PairedSplatEnvironmentConfig as q, type SplatFormat as r, type SplatCollisionProxyConfig as s, type SplatEnvironmentReadiness as t, type SplatCollisionPrimitive as u, SplatEnvironmentReadinessStatus as v, type SplatSceneConfigInput as w, type SplatSceneConfigState as x, type VisualScenarioExecutionContextInput as y, type VisualScenarioExecutionContext as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "10.0.0",
3
+ "version": "10.1.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -5,9 +5,10 @@
5
5
 
6
6
  import { useFrame } from '@react-three/fiber';
7
7
  import type { ThreeElements } from '@react-three/fiber';
8
- import { useEffect, useMemo, useRef } from 'react';
8
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
9
9
  import * as THREE from 'three';
10
10
  import { GeomBuilder } from '../rendering/GeomBuilder';
11
+ import { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY } from '../rendering/cameraFrameCapture';
11
12
  import { MujocoModel } from '../types';
12
13
  import { getName } from '../core/SceneLoader';
13
14
  import { useMujocoContext } from '../core/MujocoSimProvider';
@@ -74,8 +75,7 @@ export function SceneRenderer(props: Omit<ThreeElements['group'], 'ref'>) {
74
75
  bodyRefs.current = refs;
75
76
  }, [status, geomBuilder, mjModelRef]);
76
77
 
77
- // Sync body positions from mjData every frame
78
- useFrame(() => {
78
+ const syncBodiesToData = useCallback(() => {
79
79
  const data = mjDataRef.current;
80
80
  if (!data) return;
81
81
  const bodies = bodyRefs.current;
@@ -121,12 +121,19 @@ export function SceneRenderer(props: Omit<ThreeElements['group'], 'ref'>) {
121
121
  );
122
122
  }
123
123
  }
124
- });
124
+ }, [interpolateRef, interpolationStateRef, mjDataRef]);
125
+
126
+ // Sync body positions from mjData every frame
127
+ useFrame(syncBodiesToData);
125
128
 
126
129
  return (
127
130
  <group
128
131
  {...props}
129
132
  ref={groupRef}
133
+ userData={{
134
+ ...props.userData,
135
+ [CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY]: syncBodiesToData,
136
+ }}
130
137
  onDoubleClick={(e) => {
131
138
  if (typeof props.onDoubleClick === 'function') props.onDoubleClick(e);
132
139
  e.stopPropagation();
@@ -24,6 +24,17 @@ const DEFAULTS: GenericIKOptions = {
24
24
  rotWeight: 0.3,
25
25
  };
26
26
 
27
+ function resolveOptions(opts?: Partial<GenericIKOptions>): GenericIKOptions {
28
+ return {
29
+ maxIterations: opts?.maxIterations ?? DEFAULTS.maxIterations,
30
+ damping: opts?.damping ?? DEFAULTS.damping,
31
+ tolerance: opts?.tolerance ?? DEFAULTS.tolerance,
32
+ epsilon: opts?.epsilon ?? DEFAULTS.epsilon,
33
+ posWeight: opts?.posWeight ?? DEFAULTS.posWeight,
34
+ rotWeight: opts?.rotWeight ?? DEFAULTS.rotWeight,
35
+ };
36
+ }
37
+
27
38
  /**
28
39
  * Generic Damped Least-Squares IK solver.
29
40
  * Uses finite-difference Jacobian via MuJoCo's mj_forward.
@@ -58,7 +69,7 @@ export class GenericIK {
58
69
  currentQ: ArrayLike<number>,
59
70
  opts?: Partial<GenericIKOptions>
60
71
  ): number[] | null {
61
- const o = { ...DEFAULTS, ...opts };
72
+ const o = resolveOptions(opts);
62
73
  const n = qposAdr.length;
63
74
 
64
75
  // Save full qpos so we can restore after solving
@@ -188,6 +188,56 @@ function omitResolvedCameraSelectors(
188
188
  return rest;
189
189
  }
190
190
 
191
+ function vector3FromCaptureValue(value: NonNullable<CameraFrameCaptureOptions['positionOffset']>) {
192
+ return value instanceof THREE.Vector3
193
+ ? value.clone()
194
+ : new THREE.Vector3(value[0], value[1], value[2]);
195
+ }
196
+
197
+ function quaternionFromCaptureValue(value: NonNullable<CameraFrameCaptureOptions['quaternionOffset']>) {
198
+ return value instanceof THREE.Quaternion
199
+ ? value.clone()
200
+ : new THREE.Quaternion(value[0], value[1], value[2], value[3]);
201
+ }
202
+
203
+ function applyMountedCameraPoseOffsets(
204
+ options: CameraFrameCaptureOptions,
205
+ position: [number, number, number],
206
+ quaternion: [number, number, number, number]
207
+ ) {
208
+ const resolvedPosition = new THREE.Vector3(position[0], position[1], position[2]);
209
+ const resolvedQuaternion = new THREE.Quaternion(
210
+ quaternion[0],
211
+ quaternion[1],
212
+ quaternion[2],
213
+ quaternion[3]
214
+ );
215
+
216
+ if (options.positionOffset) {
217
+ resolvedPosition.add(
218
+ vector3FromCaptureValue(options.positionOffset).applyQuaternion(resolvedQuaternion)
219
+ );
220
+ }
221
+
222
+ if (options.quaternionOffset) {
223
+ resolvedQuaternion.multiply(quaternionFromCaptureValue(options.quaternionOffset)).normalize();
224
+ }
225
+
226
+ return {
227
+ position: [
228
+ resolvedPosition.x,
229
+ resolvedPosition.y,
230
+ resolvedPosition.z,
231
+ ] as [number, number, number],
232
+ quaternion: [
233
+ resolvedQuaternion.x,
234
+ resolvedQuaternion.y,
235
+ resolvedQuaternion.z,
236
+ resolvedQuaternion.w,
237
+ ] as [number, number, number, number],
238
+ };
239
+ }
240
+
191
241
  function countMountedCameraSelectors(options: CameraFrameCaptureOptions) {
192
242
  return Number(Boolean(options.cameraName)) +
193
243
  Number(Boolean(options.siteName)) +
@@ -1033,10 +1083,11 @@ export function MujocoSimProvider({
1033
1083
  );
1034
1084
  }
1035
1085
 
1086
+ const pose = applyMountedCameraPoseOffsets(options, position, quaternion);
1087
+
1036
1088
  return {
1037
1089
  ...baseOptions,
1038
- position,
1039
- quaternion,
1090
+ ...pose,
1040
1091
  fov: options.fov ?? model.cam_fovy?.[cameraId],
1041
1092
  source: { kind: 'mujoco-camera', cameraName: options.cameraName },
1042
1093
  };
@@ -1048,10 +1099,15 @@ export function MujocoSimProvider({
1048
1099
  throw new Error(`MuJoCo site "${options.siteName}" was not found.`);
1049
1100
  }
1050
1101
 
1102
+ const pose = applyMountedCameraPoseOffsets(
1103
+ options,
1104
+ vector3FromArray(data.site_xpos, siteId * 3),
1105
+ quaternionFromXmat(data.site_xmat, siteId * 9)
1106
+ );
1107
+
1051
1108
  return {
1052
1109
  ...baseOptions,
1053
- position: vector3FromArray(data.site_xpos, siteId * 3),
1054
- quaternion: quaternionFromXmat(data.site_xmat, siteId * 9),
1110
+ ...pose,
1055
1111
  source: { kind: 'mujoco-site', siteName: options.siteName },
1056
1112
  };
1057
1113
  }
@@ -1067,10 +1123,15 @@ export function MujocoSimProvider({
1067
1123
  );
1068
1124
  }
1069
1125
 
1126
+ const pose = applyMountedCameraPoseOffsets(
1127
+ options,
1128
+ vector3FromArray(data.xpos, bodyId * 3),
1129
+ quaternionFromXmat(data.xmat, bodyId * 9)
1130
+ );
1131
+
1070
1132
  return {
1071
1133
  ...baseOptions,
1072
- position: vector3FromArray(data.xpos, bodyId * 3),
1073
- quaternion: quaternionFromXmat(data.xmat, bodyId * 9),
1134
+ ...pose,
1074
1135
  source: { kind: 'mujoco-body', bodyName: options.bodyName },
1075
1136
  };
1076
1137
  }
@@ -482,8 +482,14 @@ export function createContiguousControlGroup(mjModel: MujocoModel, count: number
482
482
  */
483
483
  function sceneObjectToXml(obj: SceneObject): string {
484
484
  const joint = obj.freejoint ? '<freejoint/>' : '';
485
+ const geomName = obj.geomName ?? `${obj.name}_geom`;
485
486
  const pos = obj.position.map((v) => v.toFixed(3)).join(' ');
486
- const size = obj.size.map((v) => v.toFixed(3)).join(' ');
487
+ const sizeValues = obj.type === 'sphere'
488
+ ? obj.size.slice(0, 1)
489
+ : obj.type === 'cylinder'
490
+ ? obj.size.slice(0, 2)
491
+ : obj.size;
492
+ const size = sizeValues.map((v) => v.toFixed(3)).join(' ');
487
493
  const rgba = obj.rgba.join(' ');
488
494
  const mass = obj.mass ? ` mass="${obj.mass}"` : '';
489
495
  const friction = obj.friction ? ` friction="${obj.friction}"` : '';
@@ -492,7 +498,7 @@ function sceneObjectToXml(obj: SceneObject): string {
492
498
  const condim = obj.condim ? ` condim="${obj.condim}"` : '';
493
499
  const group = obj.group !== undefined ? ` group="${obj.group}"` : '';
494
500
  // Always set contype/conaffinity=1 so objects collide regardless of model defaults
495
- 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>`;
501
+ return `<body name="${obj.name}" pos="${pos}">${joint}<geom name="${geomName}" type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
496
502
  }
497
503
 
498
504
  /** Create virtual directory structure for a file path. */
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Bounded contact history for rollout verification and debugging.
6
+ */
7
+
8
+ import { useCallback, useRef } from 'react';
9
+ import type { RefObject } from 'react';
10
+ import { useAfterPhysicsStep } from '../core/MujocoSimProvider';
11
+ import { getName } from '../core/SceneLoader';
12
+ import { getContact, withContacts } from '../types';
13
+ import type { ContactInfo, MujocoModel } from '../types';
14
+
15
+ export interface ContactHistoryEntry extends ContactInfo {
16
+ time: number;
17
+ body1: number;
18
+ body1Name: string;
19
+ body2: number;
20
+ body2Name: string;
21
+ }
22
+
23
+ export interface ContactHistoryOptions {
24
+ maxLength?: number;
25
+ bodyNames?: readonly string[];
26
+ geomNames?: readonly string[];
27
+ includeWorldBody?: boolean;
28
+ }
29
+
30
+ export interface ContactHistoryHandle {
31
+ entries: RefObject<ContactHistoryEntry[]>;
32
+ clear: () => void;
33
+ countPair: (nameA: string, nameB: string) => number;
34
+ }
35
+
36
+ const geomNameCacheByModel = new WeakMap<MujocoModel, Map<number, string>>();
37
+ const bodyNameCacheByModel = new WeakMap<MujocoModel, Map<number, string>>();
38
+
39
+ function getCachedName(
40
+ cacheByModel: WeakMap<MujocoModel, Map<number, string>>,
41
+ model: MujocoModel,
42
+ id: number,
43
+ address: number
44
+ ) {
45
+ if (id < 0) return '';
46
+ let cache = cacheByModel.get(model);
47
+ if (!cache) {
48
+ cache = new Map();
49
+ cacheByModel.set(model, cache);
50
+ }
51
+ let name = cache.get(id);
52
+ if (name === undefined) {
53
+ name = getName(model, address);
54
+ cache.set(id, name);
55
+ }
56
+ return name;
57
+ }
58
+
59
+ function matchesFilter(
60
+ entry: ContactHistoryEntry,
61
+ bodyNames: readonly string[] | undefined,
62
+ geomNames: readonly string[] | undefined,
63
+ includeWorldBody: boolean
64
+ ) {
65
+ if (!includeWorldBody && (entry.body1 === 0 || entry.body2 === 0)) return false;
66
+ if (
67
+ bodyNames &&
68
+ !bodyNames.includes(entry.body1Name) &&
69
+ !bodyNames.includes(entry.body2Name)
70
+ ) {
71
+ return false;
72
+ }
73
+ if (
74
+ geomNames &&
75
+ !geomNames.includes(entry.geom1Name) &&
76
+ !geomNames.includes(entry.geom2Name)
77
+ ) {
78
+ return false;
79
+ }
80
+ return true;
81
+ }
82
+
83
+ export function useContactHistory(options: ContactHistoryOptions = {}): ContactHistoryHandle {
84
+ const entriesRef = useRef<ContactHistoryEntry[]>([]);
85
+ const optionsRef = useRef(options);
86
+ optionsRef.current = options;
87
+
88
+ const clear = useCallback(() => {
89
+ entriesRef.current = [];
90
+ }, []);
91
+
92
+ const countPair = useCallback((nameA: string, nameB: string) => {
93
+ let count = 0;
94
+ for (const entry of entriesRef.current) {
95
+ const matchesBodies =
96
+ (entry.body1Name === nameA && entry.body2Name === nameB) ||
97
+ (entry.body1Name === nameB && entry.body2Name === nameA);
98
+ const matchesGeoms =
99
+ (entry.geom1Name === nameA && entry.geom2Name === nameB) ||
100
+ (entry.geom1Name === nameB && entry.geom2Name === nameA);
101
+ if (matchesBodies || matchesGeoms) count += 1;
102
+ }
103
+ return count;
104
+ }, []);
105
+
106
+ useAfterPhysicsStep(({ model, data }) => {
107
+ if ((data.ncon ?? 0) <= 0) return;
108
+
109
+ const {
110
+ maxLength = 2000,
111
+ bodyNames,
112
+ geomNames,
113
+ includeWorldBody = false,
114
+ } = optionsRef.current;
115
+ if (maxLength <= 0) return;
116
+
117
+ const nextEntries: ContactHistoryEntry[] = [];
118
+ withContacts(data, (contacts) => {
119
+ for (let index = 0; index < data.ncon; index += 1) {
120
+ const contact = getContact(contacts, index);
121
+ if (!contact) break;
122
+ const body1 = model.geom_bodyid[contact.geom1] ?? -1;
123
+ const body2 = model.geom_bodyid[contact.geom2] ?? -1;
124
+ const entry: ContactHistoryEntry = {
125
+ geom1: contact.geom1,
126
+ geom2: contact.geom2,
127
+ geom1Name: getCachedName(geomNameCacheByModel, model, contact.geom1, model.name_geomadr[contact.geom1]),
128
+ geom2Name: getCachedName(geomNameCacheByModel, model, contact.geom2, model.name_geomadr[contact.geom2]),
129
+ body1,
130
+ body2,
131
+ body1Name: body1 >= 0 ? getCachedName(bodyNameCacheByModel, model, body1, model.name_bodyadr[body1]) : '',
132
+ body2Name: body2 >= 0 ? getCachedName(bodyNameCacheByModel, model, body2, model.name_bodyadr[body2]) : '',
133
+ pos: [contact.pos[0], contact.pos[1], contact.pos[2]],
134
+ depth: contact.dist,
135
+ time: data.time,
136
+ };
137
+ if (matchesFilter(entry, bodyNames, geomNames, includeWorldBody)) {
138
+ nextEntries.push(entry);
139
+ }
140
+ }
141
+ });
142
+
143
+ if (nextEntries.length === 0) return;
144
+ entriesRef.current.push(...nextEntries);
145
+ if (entriesRef.current.length > maxLength) {
146
+ entriesRef.current.splice(0, entriesRef.current.length - maxLength);
147
+ }
148
+ });
149
+
150
+ return {
151
+ entries: entriesRef,
152
+ clear,
153
+ countPair,
154
+ };
155
+ }