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/{chunk-QTCAVQS6.js → chunk-FEKBKHEN.js} +56 -5
- package/dist/chunk-FEKBKHEN.js.map +1 -0
- package/dist/index.d.ts +271 -19
- package/dist/index.js +1459 -407
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-BaSMqJHT.d.ts → types-BHBNJubg.d.ts} +133 -2
- package/package.json +1 -1
- package/src/components/SceneRenderer.tsx +11 -4
- package/src/core/GenericIK.ts +12 -1
- package/src/core/MujocoSimProvider.tsx +67 -6
- package/src/core/SceneLoader.ts +8 -2
- package/src/hooks/useContactHistory.ts +155 -0
- package/src/hooks/useControlWriter.ts +176 -0
- package/src/hooks/useNamedObservation.ts +42 -0
- package/src/hooks/usePolicy.ts +133 -10
- package/src/hooks/usePolicyCameraFrames.ts +162 -0
- package/src/hooks/usePose.ts +119 -0
- package/src/hooks/useRemotePolicy.ts +329 -0
- package/src/index.ts +81 -0
- package/src/policyCameraFrames.ts +213 -0
- package/src/policyControls.ts +87 -0
- package/src/policyObservation.ts +172 -0
- package/src/rendering/GeomBuilder.ts +73 -24
- package/src/rendering/cameraFrameCapture.ts +74 -2
- package/src/types.ts +151 -1
- package/dist/chunk-QTCAVQS6.js.map +0 -1
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-
|
|
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-
|
|
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) =>
|
|
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,
|
|
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
|
@@ -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
|
-
|
|
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();
|
package/src/core/GenericIK.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1073
|
-
quaternion: quaternionFromXmat(data.xmat, bodyId * 9),
|
|
1134
|
+
...pose,
|
|
1074
1135
|
source: { kind: 'mujoco-body', bodyName: options.bodyName },
|
|
1075
1136
|
};
|
|
1076
1137
|
}
|
package/src/core/SceneLoader.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|