mujoco-react 10.0.1 → 10.2.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-3BMNRSS2.js} +56 -5
- package/dist/chunk-3BMNRSS2.js.map +1 -0
- package/dist/index.d.ts +281 -19
- package/dist/index.js +1451 -258
- 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-CLD5K3JD.d.ts} +163 -2
- package/package.json +1 -1
- package/src/components/SceneRenderer.tsx +11 -4
- package/src/core/MujocoSimProvider.tsx +97 -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 +88 -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/rendering/imageProjection.ts +186 -0
- package/src/types.ts +188 -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-CLD5K3JD.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-3BMNRSS2.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];
|
|
@@ -563,6 +573,35 @@ interface RayHit {
|
|
|
563
573
|
geomId: number;
|
|
564
574
|
distance: number;
|
|
565
575
|
}
|
|
576
|
+
type ImagePointCoordinateSpace = 'normalized' | 'normalized-1000' | 'pixel' | 'ndc';
|
|
577
|
+
interface ImagePointProjectionOptions extends CameraFrameCaptureOptions {
|
|
578
|
+
/** X coordinate in the selected coordinate space. Defaults to normalized 0..1. */
|
|
579
|
+
x: number;
|
|
580
|
+
/** Y coordinate in the selected coordinate space. Defaults to normalized 0..1 with origin at top-left. */
|
|
581
|
+
y: number;
|
|
582
|
+
/**
|
|
583
|
+
* Coordinate convention for x/y:
|
|
584
|
+
* - normalized: 0..1 image coordinates, top-left origin
|
|
585
|
+
* - normalized-1000: 0..1000 detector coordinates, top-left origin
|
|
586
|
+
* - pixel: pixel coordinates, top-left origin
|
|
587
|
+
* - ndc: Three.js normalized device coordinates, -1..1
|
|
588
|
+
*/
|
|
589
|
+
coordinateSpace?: ImagePointCoordinateSpace;
|
|
590
|
+
/** Image width for pixel coordinates. Falls back to `width` or renderer canvas width. */
|
|
591
|
+
imageWidth?: number;
|
|
592
|
+
/** Image height for pixel coordinates. Falls back to `height` or renderer canvas height. */
|
|
593
|
+
imageHeight?: number;
|
|
594
|
+
/** Ignore hits farther than this distance from the camera ray origin. */
|
|
595
|
+
maxDistance?: number;
|
|
596
|
+
}
|
|
597
|
+
interface ImagePointProjectionResult extends RayHit {
|
|
598
|
+
/** NDC coordinates used for raycasting. */
|
|
599
|
+
ndc: [number, number];
|
|
600
|
+
/** Image dimensions used when interpreting pixel coordinates. */
|
|
601
|
+
imageSize: [number, number];
|
|
602
|
+
/** Camera pose provenance, matching camera-frame capture results. */
|
|
603
|
+
source: CameraFrameCaptureSource;
|
|
604
|
+
}
|
|
566
605
|
interface ModelOptions {
|
|
567
606
|
timestep: number;
|
|
568
607
|
gravity: [number, number, number];
|
|
@@ -598,16 +637,123 @@ interface PolicyObservationInput {
|
|
|
598
637
|
interface PolicyInferenceInput extends PolicyObservationInput {
|
|
599
638
|
observation: PolicyVector;
|
|
600
639
|
}
|
|
640
|
+
type PolicyActionChunk = readonly PolicyVector[];
|
|
641
|
+
type PolicyInferenceOutput = PolicyVector | PolicyActionChunk;
|
|
642
|
+
type PolicyInferenceResult = PolicyInferenceOutput | Promise<PolicyInferenceOutput>;
|
|
601
643
|
interface PolicyActionInput extends PolicyInferenceInput {
|
|
602
644
|
action: PolicyVector;
|
|
603
645
|
}
|
|
646
|
+
interface PolicyAPI {
|
|
647
|
+
readonly isRunning: boolean;
|
|
648
|
+
start: () => void;
|
|
649
|
+
stop: () => void;
|
|
650
|
+
clearQueue: () => void;
|
|
651
|
+
reset: () => void;
|
|
652
|
+
readonly inFlight: boolean;
|
|
653
|
+
readonly queuedActions: number;
|
|
654
|
+
readonly lastObservation: PolicyVector | null;
|
|
655
|
+
readonly lastAction: PolicyVector | null;
|
|
656
|
+
readonly lastError: unknown;
|
|
657
|
+
}
|
|
604
658
|
interface PolicyConfig {
|
|
605
659
|
frequency: number;
|
|
606
660
|
enabled?: boolean;
|
|
661
|
+
/** Start async inference while this many queued actions remain. Defaults to 0. */
|
|
662
|
+
prefetchThreshold?: number;
|
|
663
|
+
/**
|
|
664
|
+
* How async action chunks update the queue.
|
|
665
|
+
* - append preserves legacy FIFO behavior.
|
|
666
|
+
* - replace is useful for receding-horizon policies where a fresh chunk should supersede stale queued actions.
|
|
667
|
+
*/
|
|
668
|
+
queueStrategy?: 'append' | 'replace';
|
|
669
|
+
/**
|
|
670
|
+
* Clear queued actions and ignore in-flight async results when `stop()` is called.
|
|
671
|
+
* Defaults to false so callers can choose pause/resume behavior explicitly.
|
|
672
|
+
*/
|
|
673
|
+
clearQueueOnStop?: boolean;
|
|
607
674
|
onObservation: (input: PolicyObservationInput) => PolicyVector;
|
|
608
675
|
/** Run policy inference. Omit to pass observations directly to `onAction` for custom inline controllers. */
|
|
609
|
-
infer?: (input: PolicyInferenceInput) =>
|
|
676
|
+
infer?: (input: PolicyInferenceInput) => PolicyInferenceResult;
|
|
610
677
|
onAction: (input: PolicyActionInput) => void;
|
|
678
|
+
/** Called when async inference rejects. */
|
|
679
|
+
onError?: (error: unknown) => void;
|
|
680
|
+
}
|
|
681
|
+
interface RemotePolicyRequestInput extends PolicyInferenceInput {
|
|
682
|
+
/** True for the first request after hook construction or `reset()`. */
|
|
683
|
+
reset: boolean;
|
|
684
|
+
/** Zero-based request index since construction or `reset()`. */
|
|
685
|
+
requestIndex: number;
|
|
686
|
+
/** Aborts when the request is no longer needed, e.g. after pause/reset. */
|
|
687
|
+
signal: AbortSignal;
|
|
688
|
+
}
|
|
689
|
+
interface RemotePolicyRequestInfo extends RemotePolicyRequestInput {
|
|
690
|
+
body: unknown;
|
|
691
|
+
requestStartedAt: number;
|
|
692
|
+
}
|
|
693
|
+
interface RemotePolicyResponseInfo extends RemotePolicyRequestInfo {
|
|
694
|
+
response: Response;
|
|
695
|
+
responseBody: unknown;
|
|
696
|
+
responseFinishedAt: number;
|
|
697
|
+
requestMs: number;
|
|
698
|
+
}
|
|
699
|
+
type RemotePolicyStatus = 'idle' | 'requesting' | 'ready' | 'error' | 'aborted';
|
|
700
|
+
interface RemotePolicyConfig extends Omit<PolicyConfig, 'infer'> {
|
|
701
|
+
endpoint: string | URL;
|
|
702
|
+
method?: string;
|
|
703
|
+
headers?: HeadersInit;
|
|
704
|
+
credentials?: RequestCredentials;
|
|
705
|
+
/** Additional external cancellation signal for remote inference requests. */
|
|
706
|
+
signal?: AbortSignal;
|
|
707
|
+
/**
|
|
708
|
+
* Abort the active HTTP request when `stop()` or `reset()` is called.
|
|
709
|
+
* Defaults to true so paused policies stop consuming server work.
|
|
710
|
+
*/
|
|
711
|
+
abortOnStop?: boolean;
|
|
712
|
+
fetcher?: typeof fetch;
|
|
713
|
+
requestInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'credentials' | 'signal'>;
|
|
714
|
+
buildRequest?: (input: RemotePolicyRequestInput) => unknown | Promise<unknown>;
|
|
715
|
+
readResponse?: (response: Response) => unknown | Promise<unknown>;
|
|
716
|
+
parseResponse?: (responseBody: unknown, info: RemotePolicyResponseInfo) => PolicyInferenceResult;
|
|
717
|
+
onRequest?: (info: RemotePolicyRequestInfo) => void;
|
|
718
|
+
onResponse?: (info: RemotePolicyResponseInfo) => void;
|
|
719
|
+
}
|
|
720
|
+
interface RemotePolicyAPI extends PolicyAPI {
|
|
721
|
+
abort: (reason?: unknown) => void;
|
|
722
|
+
readonly remoteStatus: RemotePolicyStatus;
|
|
723
|
+
readonly requestCount: number;
|
|
724
|
+
readonly responseCount: number;
|
|
725
|
+
readonly lastRequestBody: unknown;
|
|
726
|
+
readonly lastResponseBody: unknown;
|
|
727
|
+
readonly lastHttpStatus: number | null;
|
|
728
|
+
readonly lastRequestMs: number | null;
|
|
729
|
+
}
|
|
730
|
+
interface PolicyCameraFrameStream extends CameraFrameCaptureOptions {
|
|
731
|
+
/** Image key used in policy payloads, e.g. `image`, `front`, or `wrist_cam`. */
|
|
732
|
+
key: string;
|
|
733
|
+
/** Additional payload keys that should receive the same data URL. */
|
|
734
|
+
aliases?: readonly string[];
|
|
735
|
+
}
|
|
736
|
+
interface PolicyCameraFrameCaptureOptions {
|
|
737
|
+
streams: readonly PolicyCameraFrameStream[];
|
|
738
|
+
/**
|
|
739
|
+
* Include `observation.images.${key}` for every captured stream.
|
|
740
|
+
* Defaults to true because LeRobot-style policies usually use these names.
|
|
741
|
+
*/
|
|
742
|
+
includeObservationImageAliases?: boolean;
|
|
743
|
+
}
|
|
744
|
+
interface PolicyCameraFrameCaptureResult {
|
|
745
|
+
frames: Record<string, CameraFrameCaptureResult>;
|
|
746
|
+
images: Record<string, string>;
|
|
747
|
+
/** Human-readable source summary for UI/debug telemetry. */
|
|
748
|
+
sourceSummary: string;
|
|
749
|
+
capturedAt: number;
|
|
750
|
+
}
|
|
751
|
+
interface PolicyCameraFrameCaptureAPI {
|
|
752
|
+
status: FrameCaptureStatus;
|
|
753
|
+
error: Error | null;
|
|
754
|
+
isCapturing: boolean;
|
|
755
|
+
capture: (options?: Partial<PolicyCameraFrameCaptureOptions>) => Promise<PolicyCameraFrameCaptureResult>;
|
|
756
|
+
reset: () => void;
|
|
611
757
|
}
|
|
612
758
|
type ObservationOutput = 'float32' | 'float64';
|
|
613
759
|
interface ObservationConfig {
|
|
@@ -977,6 +1123,7 @@ interface MujocoSimAPI {
|
|
|
977
1123
|
bodyId: number;
|
|
978
1124
|
geomId: number;
|
|
979
1125
|
} | null;
|
|
1126
|
+
projectImagePointTo3D(options: ImagePointProjectionOptions): ImagePointProjectionResult | null;
|
|
980
1127
|
setBodyMass(name: Bodies, mass: number): void;
|
|
981
1128
|
setGeomFriction(name: Geoms, friction: [number, number, number]): void;
|
|
982
1129
|
setGeomSize(name: Geoms, size: [number, number, number]): void;
|
|
@@ -1026,6 +1173,10 @@ interface CameraFrameCaptureOptions {
|
|
|
1026
1173
|
lookAt?: CameraFrameCaptureVector3;
|
|
1027
1174
|
quaternion?: CameraFrameCaptureQuaternion;
|
|
1028
1175
|
up?: CameraFrameCaptureVector3;
|
|
1176
|
+
/** Local-space offset applied after resolving a mounted MuJoCo camera/site/body pose. */
|
|
1177
|
+
positionOffset?: CameraFrameCaptureVector3;
|
|
1178
|
+
/** Local-space rotation applied after resolving a mounted MuJoCo camera/site/body pose. Array values use Three.js order: [x, y, z, w]. */
|
|
1179
|
+
quaternionOffset?: CameraFrameCaptureQuaternion;
|
|
1029
1180
|
width?: number;
|
|
1030
1181
|
height?: number;
|
|
1031
1182
|
type?: string;
|
|
@@ -1035,6 +1186,16 @@ interface CameraFrameCaptureOptions {
|
|
|
1035
1186
|
far?: number;
|
|
1036
1187
|
/** Provenance for the camera pose used by the capture. Usually set by the MuJoCo provider. */
|
|
1037
1188
|
source?: CameraFrameCaptureSource;
|
|
1189
|
+
/** Hide rendered Three objects whose MuJoCo geom group is in this list. */
|
|
1190
|
+
hiddenGeomGroups?: readonly number[];
|
|
1191
|
+
/** When provided, only rendered Three objects whose MuJoCo geom group is in this list are visible. */
|
|
1192
|
+
visibleGeomGroups?: readonly number[];
|
|
1193
|
+
/** Hide rendered Three objects whose MuJoCo geom name is in this list. */
|
|
1194
|
+
hiddenGeomNames?: readonly string[];
|
|
1195
|
+
/** Optional clear color for this capture only. Defaults to the renderer's current clear color. */
|
|
1196
|
+
background?: THREE.ColorRepresentation;
|
|
1197
|
+
/** Optional clear alpha for this capture only. Defaults to the renderer's current clear alpha. */
|
|
1198
|
+
backgroundAlpha?: number;
|
|
1038
1199
|
}
|
|
1039
1200
|
type CameraFrameCaptureSource = {
|
|
1040
1201
|
kind: 'mujoco-camera';
|
|
@@ -1219,4 +1380,4 @@ interface ArrayJointStateResult {
|
|
|
1219
1380
|
velocity: React__default.RefObject<Float64Array>;
|
|
1220
1381
|
}
|
|
1221
1382
|
|
|
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,
|
|
1383
|
+
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, ModelActuators 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 ImagePointCoordinateSpace as aD, type ImagePointProjectionOptions as aE, type ImagePointProjectionResult as aF, type PolicyVector as aG, type BodyInfo as aH, type CameraFrameCaptureQuaternion as aI, type CameraFrameCaptureVector3 as aJ, type CameraFrameSequenceCameraSummary as aK, type CameraFrameSequenceFrame as aL, type CameraFrameSequenceSampleInput as aM, type CameraFrameSequenceStepInput as aN, type CameraInfo as aO, type ControlJointInfo as aP, type FrameCaptureTarget as aQ, type FrameCaptureTargetRef as aR, type IKSolveFn as aS, type IkGizmoDragInput as aT, type IkSolveInput as aU, type JointInfo as aV, type JointStateKind as aW, type KeyBinding as aX, type KeyboardIkTargetAction as aY, type KeyboardIkTargetBinding as aZ, type Keyframes 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, ModelBodies as b0, ModelCameras as b1, ModelGeoms as b2, ModelJoints as b3, ModelKeyframes as b4, type ModelOptions as b5, type ModelResource as b6, ModelResources as b7, ModelSensors as b8, ModelSites as b9, type SceneMarker as bA, type SceneObject as bB, type SensorResult as bC, type SiteInfo as bD, type SplatAssetConfig as bE, type SplatScenarioConfig as bF, type StateSnapshot as bG, type TrajectoryData as bH, type TrajectoryFrameCallbackInput as bI, type VisualScenarioMaterialFilterInput as bJ, type XmlPatch as bK, getContact as bL, registerModelResources as bM, type Models as ba, type MujocoContact as bb, type MujocoContactArray as bc, type MujocoFrameCaptureOptions as bd, type ObservationLayoutItem as be, type PhysicsConfig as bf, type PhysicsStepInput as bg, type PolicyActionChunk as bh, type PolicyActionInput as bi, type PolicyInferenceInput as bj, type PolicyInferenceOutput as bk, type PolicyInferenceResult as bl, type PolicyObservationInput as bm, type RayHit as bn, type Register as bo, type RegisteredModelMap as bp, type RemotePolicyRequestInfo as bq, type RemotePolicyRequestInput as br, type RemotePolicyResponseInfo as bs, type RemotePolicyStatus as bt, type ResetCallbackInput as bu, type ResolvedScenarioCameraConfig as bv, type ResolvedScenarioMaterialConfig as bw, type ResourceSelector as bx, type ScenarioCameraConfig as by, type ScenarioMaterialConfig 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();
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
ControlGroupSelector,
|
|
32
32
|
ContactInfo,
|
|
33
33
|
GeomInfo,
|
|
34
|
+
ImagePointProjectionOptions,
|
|
35
|
+
ImagePointProjectionResult,
|
|
34
36
|
JointInfo,
|
|
35
37
|
LoadFromFilesOptions,
|
|
36
38
|
LocalMujocoFile,
|
|
@@ -61,6 +63,7 @@ import {
|
|
|
61
63
|
getCameraFrameCaptureSourceTarget,
|
|
62
64
|
isMountedCameraFrameCaptureSource,
|
|
63
65
|
} from '../rendering/cameraFrameSource';
|
|
66
|
+
import { projectImagePointTo3D as projectImagePointTo3DFromScene } from '../rendering/imageProjection';
|
|
64
67
|
import {
|
|
65
68
|
loadScene,
|
|
66
69
|
createSceneConfigFromFiles,
|
|
@@ -188,6 +191,56 @@ function omitResolvedCameraSelectors(
|
|
|
188
191
|
return rest;
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
function vector3FromCaptureValue(value: NonNullable<CameraFrameCaptureOptions['positionOffset']>) {
|
|
195
|
+
return value instanceof THREE.Vector3
|
|
196
|
+
? value.clone()
|
|
197
|
+
: new THREE.Vector3(value[0], value[1], value[2]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function quaternionFromCaptureValue(value: NonNullable<CameraFrameCaptureOptions['quaternionOffset']>) {
|
|
201
|
+
return value instanceof THREE.Quaternion
|
|
202
|
+
? value.clone()
|
|
203
|
+
: new THREE.Quaternion(value[0], value[1], value[2], value[3]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function applyMountedCameraPoseOffsets(
|
|
207
|
+
options: CameraFrameCaptureOptions,
|
|
208
|
+
position: [number, number, number],
|
|
209
|
+
quaternion: [number, number, number, number]
|
|
210
|
+
) {
|
|
211
|
+
const resolvedPosition = new THREE.Vector3(position[0], position[1], position[2]);
|
|
212
|
+
const resolvedQuaternion = new THREE.Quaternion(
|
|
213
|
+
quaternion[0],
|
|
214
|
+
quaternion[1],
|
|
215
|
+
quaternion[2],
|
|
216
|
+
quaternion[3]
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (options.positionOffset) {
|
|
220
|
+
resolvedPosition.add(
|
|
221
|
+
vector3FromCaptureValue(options.positionOffset).applyQuaternion(resolvedQuaternion)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (options.quaternionOffset) {
|
|
226
|
+
resolvedQuaternion.multiply(quaternionFromCaptureValue(options.quaternionOffset)).normalize();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
position: [
|
|
231
|
+
resolvedPosition.x,
|
|
232
|
+
resolvedPosition.y,
|
|
233
|
+
resolvedPosition.z,
|
|
234
|
+
] as [number, number, number],
|
|
235
|
+
quaternion: [
|
|
236
|
+
resolvedQuaternion.x,
|
|
237
|
+
resolvedQuaternion.y,
|
|
238
|
+
resolvedQuaternion.z,
|
|
239
|
+
resolvedQuaternion.w,
|
|
240
|
+
] as [number, number, number, number],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
191
244
|
function countMountedCameraSelectors(options: CameraFrameCaptureOptions) {
|
|
192
245
|
return Number(Boolean(options.cameraName)) +
|
|
193
246
|
Number(Boolean(options.siteName)) +
|
|
@@ -1033,10 +1086,11 @@ export function MujocoSimProvider({
|
|
|
1033
1086
|
);
|
|
1034
1087
|
}
|
|
1035
1088
|
|
|
1089
|
+
const pose = applyMountedCameraPoseOffsets(options, position, quaternion);
|
|
1090
|
+
|
|
1036
1091
|
return {
|
|
1037
1092
|
...baseOptions,
|
|
1038
|
-
|
|
1039
|
-
quaternion,
|
|
1093
|
+
...pose,
|
|
1040
1094
|
fov: options.fov ?? model.cam_fovy?.[cameraId],
|
|
1041
1095
|
source: { kind: 'mujoco-camera', cameraName: options.cameraName },
|
|
1042
1096
|
};
|
|
@@ -1048,10 +1102,15 @@ export function MujocoSimProvider({
|
|
|
1048
1102
|
throw new Error(`MuJoCo site "${options.siteName}" was not found.`);
|
|
1049
1103
|
}
|
|
1050
1104
|
|
|
1105
|
+
const pose = applyMountedCameraPoseOffsets(
|
|
1106
|
+
options,
|
|
1107
|
+
vector3FromArray(data.site_xpos, siteId * 3),
|
|
1108
|
+
quaternionFromXmat(data.site_xmat, siteId * 9)
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1051
1111
|
return {
|
|
1052
1112
|
...baseOptions,
|
|
1053
|
-
|
|
1054
|
-
quaternion: quaternionFromXmat(data.site_xmat, siteId * 9),
|
|
1113
|
+
...pose,
|
|
1055
1114
|
source: { kind: 'mujoco-site', siteName: options.siteName },
|
|
1056
1115
|
};
|
|
1057
1116
|
}
|
|
@@ -1067,10 +1126,15 @@ export function MujocoSimProvider({
|
|
|
1067
1126
|
);
|
|
1068
1127
|
}
|
|
1069
1128
|
|
|
1129
|
+
const pose = applyMountedCameraPoseOffsets(
|
|
1130
|
+
options,
|
|
1131
|
+
vector3FromArray(data.xpos, bodyId * 3),
|
|
1132
|
+
quaternionFromXmat(data.xmat, bodyId * 9)
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1070
1135
|
return {
|
|
1071
1136
|
...baseOptions,
|
|
1072
|
-
|
|
1073
|
-
quaternion: quaternionFromXmat(data.xmat, bodyId * 9),
|
|
1137
|
+
...pose,
|
|
1074
1138
|
source: { kind: 'mujoco-body', bodyName: options.bodyName },
|
|
1075
1139
|
};
|
|
1076
1140
|
}
|
|
@@ -1529,6 +1593,31 @@ export function MujocoSimProvider({
|
|
|
1529
1593
|
[camera, gl]
|
|
1530
1594
|
);
|
|
1531
1595
|
|
|
1596
|
+
const projectImagePointTo3D = useCallback(
|
|
1597
|
+
(options: ImagePointProjectionOptions): ImagePointProjectionResult | null => {
|
|
1598
|
+
const {
|
|
1599
|
+
x,
|
|
1600
|
+
y,
|
|
1601
|
+
coordinateSpace,
|
|
1602
|
+
imageWidth,
|
|
1603
|
+
imageHeight,
|
|
1604
|
+
maxDistance,
|
|
1605
|
+
...captureOptions
|
|
1606
|
+
} = options;
|
|
1607
|
+
const resolvedCaptureOptions = resolveCameraCaptureOptions(captureOptions);
|
|
1608
|
+
return projectImagePointTo3DFromScene(gl, scene, camera, {
|
|
1609
|
+
...resolvedCaptureOptions,
|
|
1610
|
+
x,
|
|
1611
|
+
y,
|
|
1612
|
+
coordinateSpace,
|
|
1613
|
+
imageWidth,
|
|
1614
|
+
imageHeight,
|
|
1615
|
+
maxDistance,
|
|
1616
|
+
});
|
|
1617
|
+
},
|
|
1618
|
+
[camera, gl, resolveCameraCaptureOptions, scene]
|
|
1619
|
+
);
|
|
1620
|
+
|
|
1532
1621
|
// --- Domain randomization ---
|
|
1533
1622
|
|
|
1534
1623
|
const setBodyMass = useCallback((name: string, mass: number): void => {
|
|
@@ -1615,6 +1704,7 @@ export function MujocoSimProvider({
|
|
|
1615
1704
|
captureCameraFrameBlob: captureCameraFrameBlobApi,
|
|
1616
1705
|
recordCameraSequence: recordCameraSequenceApi,
|
|
1617
1706
|
project2DTo3D,
|
|
1707
|
+
projectImagePointTo3D,
|
|
1618
1708
|
setBodyMass,
|
|
1619
1709
|
setGeomFriction,
|
|
1620
1710
|
setGeomSize,
|
|
@@ -1635,6 +1725,7 @@ export function MujocoSimProvider({
|
|
|
1635
1725
|
captureCameraFrameApi, captureCameraFrameBlobApi,
|
|
1636
1726
|
recordCameraSequenceApi,
|
|
1637
1727
|
project2DTo3D,
|
|
1728
|
+
projectImagePointTo3D,
|
|
1638
1729
|
setBodyMass, setGeomFriction, setGeomSize,
|
|
1639
1730
|
]
|
|
1640
1731
|
);
|
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
|
+
}
|