mujoco-react 8.11.0 → 9.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/README.md +34 -17
- package/dist/{chunk-SEWQULWO.js → chunk-33CV6HSV.js} +3 -3
- package/dist/chunk-33CV6HSV.js.map +1 -0
- package/dist/index.d.ts +13 -41
- package/dist/index.js +185 -152
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-BmneHLBM.d.ts → types-C5gTvR7b.d.ts} +83 -13
- package/package.json +1 -1
- package/src/components/DragInteraction.tsx +1 -1
- package/src/components/IkGizmo.tsx +2 -2
- package/src/components/SceneRenderer.tsx +1 -1
- package/src/components/TrajectoryPlayer.tsx +4 -1
- package/src/components/VisualScenario.tsx +1 -1
- package/src/core/MujocoPhysics.tsx +10 -4
- package/src/core/MujocoSimProvider.tsx +42 -13
- package/src/core/createController.tsx +2 -2
- package/src/hooks/useBodyState.ts +1 -1
- package/src/hooks/useContacts.ts +1 -1
- package/src/hooks/useCtrlNoise.ts +1 -1
- package/src/hooks/useFrameCapture.ts +8 -42
- package/src/hooks/useGamepad.ts +1 -1
- package/src/hooks/useGravityCompensation.ts +1 -1
- package/src/hooks/useIkController.ts +22 -13
- package/src/hooks/useJointState.ts +1 -1
- package/src/hooks/useKeyboardTeleop.ts +1 -1
- package/src/hooks/usePolicy.ts +1 -1
- package/src/hooks/useSensor.ts +1 -1
- package/src/hooks/useTrajectoryPlayer.ts +4 -4
- package/src/hooks/useTrajectoryRecorder.ts +1 -1
- package/src/index.ts +18 -9
- package/src/types.ts +106 -18
- package/dist/chunk-SEWQULWO.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 {
|
|
3
|
+
import { n as SplatEnvironmentProps } from './types-C5gTvR7b.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 { useSplatEnvironment, SplatEnvironment } from './chunk-
|
|
1
|
+
import { useSplatEnvironment, SplatEnvironment } from './chunk-33CV6HSV.js';
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
@@ -327,7 +327,7 @@ interface LoadFromFilesOptions {
|
|
|
327
327
|
homeJoints?: number[];
|
|
328
328
|
xmlPatches?: XmlPatch[];
|
|
329
329
|
sceneObjects?: SceneObject[];
|
|
330
|
-
onReset?: (
|
|
330
|
+
onReset?: (input: ResetCallbackInput) => void;
|
|
331
331
|
}
|
|
332
332
|
interface SceneConfig {
|
|
333
333
|
/** Base URL for fetching model files. The loader fetches `src + sceneFile` and follows dependencies. */
|
|
@@ -347,7 +347,7 @@ interface SceneConfig {
|
|
|
347
347
|
sceneObjects?: SceneObject[];
|
|
348
348
|
homeJoints?: number[];
|
|
349
349
|
xmlPatches?: XmlPatch[];
|
|
350
|
-
onReset?: (
|
|
350
|
+
onReset?: (input: ResetCallbackInput) => void;
|
|
351
351
|
}
|
|
352
352
|
type ResourceSelector<TInfo, TName extends string = string> = TName | readonly TName[] | RegExp | ((info: TInfo) => boolean);
|
|
353
353
|
interface IkConfig {
|
|
@@ -380,7 +380,7 @@ interface IkContextValue {
|
|
|
380
380
|
setIkEnabled: (enabled: boolean) => void;
|
|
381
381
|
moveTarget: (pos: THREE.Vector3, duration?: number) => void;
|
|
382
382
|
syncTargetToSite: () => void;
|
|
383
|
-
solveIK: (
|
|
383
|
+
solveIK: (input: IkSolveInput) => number[] | null;
|
|
384
384
|
getGizmoStats: () => {
|
|
385
385
|
pos: THREE.Vector3;
|
|
386
386
|
rot: THREE.Euler;
|
|
@@ -398,14 +398,38 @@ interface PhysicsConfig {
|
|
|
398
398
|
paused?: boolean;
|
|
399
399
|
speed?: number;
|
|
400
400
|
}
|
|
401
|
-
type IKSolveFn = (
|
|
401
|
+
type IKSolveFn = (input: IkSolveInput) => number[] | null;
|
|
402
|
+
interface IkSolveInput {
|
|
403
|
+
position: THREE.Vector3;
|
|
404
|
+
quaternion: THREE.Quaternion;
|
|
405
|
+
currentQ: number[];
|
|
406
|
+
context?: IKSolveContext;
|
|
407
|
+
}
|
|
402
408
|
interface IKSolveContext {
|
|
403
409
|
model: MujocoModel;
|
|
404
410
|
data: MujocoData;
|
|
405
411
|
siteId: number;
|
|
406
412
|
controlGroup: ControlGroupInfo;
|
|
407
413
|
}
|
|
408
|
-
|
|
414
|
+
interface PhysicsStepInput {
|
|
415
|
+
model: MujocoModel;
|
|
416
|
+
data: MujocoData;
|
|
417
|
+
}
|
|
418
|
+
interface ResetCallbackInput extends PhysicsStepInput {
|
|
419
|
+
}
|
|
420
|
+
interface ReadyCallbackInput {
|
|
421
|
+
api: MujocoSimAPI;
|
|
422
|
+
}
|
|
423
|
+
interface StepCallbackInput {
|
|
424
|
+
time: number;
|
|
425
|
+
model: MujocoModel;
|
|
426
|
+
data: MujocoData;
|
|
427
|
+
}
|
|
428
|
+
interface SelectionCallbackInput {
|
|
429
|
+
bodyId: number;
|
|
430
|
+
name: string;
|
|
431
|
+
}
|
|
432
|
+
type PhysicsStepCallback = (input: PhysicsStepInput) => void;
|
|
409
433
|
interface StateSnapshot {
|
|
410
434
|
time: number;
|
|
411
435
|
qpos: Float64Array;
|
|
@@ -606,7 +630,11 @@ interface IkGizmoProps {
|
|
|
606
630
|
controller: IkContextValue;
|
|
607
631
|
siteName?: string;
|
|
608
632
|
scale?: number;
|
|
609
|
-
onDrag?: (
|
|
633
|
+
onDrag?: (input: IkGizmoDragInput) => void;
|
|
634
|
+
}
|
|
635
|
+
interface IkGizmoDragInput {
|
|
636
|
+
position: THREE.Vector3;
|
|
637
|
+
quaternion: THREE.Quaternion;
|
|
610
638
|
}
|
|
611
639
|
interface DragInteractionProps {
|
|
612
640
|
stiffness?: number;
|
|
@@ -718,7 +746,11 @@ interface VisualScenarioEffectsProps {
|
|
|
718
746
|
background?: THREE.ColorRepresentation;
|
|
719
747
|
fogNear?: number;
|
|
720
748
|
fogFar?: number;
|
|
721
|
-
materialFilter?: (
|
|
749
|
+
materialFilter?: (input: VisualScenarioMaterialFilterInput) => boolean;
|
|
750
|
+
}
|
|
751
|
+
interface VisualScenarioMaterialFilterInput {
|
|
752
|
+
object: THREE.Object3D;
|
|
753
|
+
material: THREE.Material;
|
|
722
754
|
}
|
|
723
755
|
type TrajectoryInput = TrajectoryFrame[] | number[][];
|
|
724
756
|
interface TrajectoryPlayerProps {
|
|
@@ -728,9 +760,16 @@ interface TrajectoryPlayerProps {
|
|
|
728
760
|
loop?: boolean;
|
|
729
761
|
playing?: boolean;
|
|
730
762
|
mode?: 'kinematic' | 'physics';
|
|
731
|
-
onFrame?: (
|
|
763
|
+
onFrame?: (input: TrajectoryFrameCallbackInput) => void;
|
|
732
764
|
onComplete?: () => void;
|
|
733
|
-
onStateChange?: (
|
|
765
|
+
onStateChange?: (input: TrajectoryStateChangeInput) => void;
|
|
766
|
+
}
|
|
767
|
+
interface TrajectoryFrameCallbackInput {
|
|
768
|
+
frameIndex: number;
|
|
769
|
+
frame: TrajectoryFrame | number[] | undefined;
|
|
770
|
+
}
|
|
771
|
+
interface TrajectoryStateChangeInput {
|
|
772
|
+
state: PlaybackState;
|
|
734
773
|
}
|
|
735
774
|
interface ContactListenerProps {
|
|
736
775
|
body: Bodies;
|
|
@@ -798,7 +837,10 @@ interface MujocoSimAPI {
|
|
|
798
837
|
addBody(body: SceneObject): Promise<void>;
|
|
799
838
|
removeBody(name: Bodies): Promise<void>;
|
|
800
839
|
recompile(patches?: XmlPatch[]): Promise<void>;
|
|
840
|
+
getCanvas(): HTMLCanvasElement | null;
|
|
801
841
|
getCanvasSnapshot(width?: number, height?: number, mimeType?: string): string;
|
|
842
|
+
captureFrame(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureResult>;
|
|
843
|
+
captureFrameBlob(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureBlobResult>;
|
|
802
844
|
project2DTo3D(x: number, y: number, cameraPos: THREE.Vector3, lookAt: THREE.Vector3): {
|
|
803
845
|
point: THREE.Vector3;
|
|
804
846
|
bodyId: number;
|
|
@@ -810,14 +852,42 @@ interface MujocoSimAPI {
|
|
|
810
852
|
readonly mjModelRef: React__default.RefObject<MujocoModel | null>;
|
|
811
853
|
readonly mjDataRef: React__default.RefObject<MujocoData | null>;
|
|
812
854
|
}
|
|
855
|
+
type FrameCaptureStatus = 'idle' | 'capturing' | 'captured' | 'error';
|
|
856
|
+
type FrameCaptureTarget = HTMLCanvasElement | HTMLElement | null | undefined;
|
|
857
|
+
type FrameCaptureTargetRef = React__default.RefObject<HTMLCanvasElement | HTMLElement | null>;
|
|
858
|
+
interface FrameCaptureOptions {
|
|
859
|
+
target?: FrameCaptureTarget | FrameCaptureTargetRef;
|
|
860
|
+
type?: string;
|
|
861
|
+
quality?: number;
|
|
862
|
+
waitForAnimationFrame?: boolean;
|
|
863
|
+
}
|
|
864
|
+
type MujocoFrameCaptureOptions = Omit<FrameCaptureOptions, 'target'>;
|
|
865
|
+
interface FrameCaptureResult {
|
|
866
|
+
canvas: HTMLCanvasElement;
|
|
867
|
+
dataUrl: string;
|
|
868
|
+
type: string;
|
|
869
|
+
}
|
|
870
|
+
interface FrameCaptureBlobResult {
|
|
871
|
+
canvas: HTMLCanvasElement;
|
|
872
|
+
blob: Blob;
|
|
873
|
+
type: string;
|
|
874
|
+
}
|
|
875
|
+
interface FrameCaptureAPI {
|
|
876
|
+
status: FrameCaptureStatus;
|
|
877
|
+
error: Error | null;
|
|
878
|
+
isCapturing: boolean;
|
|
879
|
+
capture: (options?: FrameCaptureOptions) => Promise<FrameCaptureResult>;
|
|
880
|
+
captureBlob: (options?: FrameCaptureOptions) => Promise<FrameCaptureBlobResult>;
|
|
881
|
+
reset: () => void;
|
|
882
|
+
}
|
|
813
883
|
type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
814
884
|
config: SceneConfig;
|
|
815
885
|
/** R3F content rendered while the MuJoCo WASM module is still loading. */
|
|
816
886
|
loadingFallback?: ReactNode;
|
|
817
|
-
onReady?: (
|
|
887
|
+
onReady?: (input: ReadyCallbackInput) => void;
|
|
818
888
|
onError?: (error: Error) => void;
|
|
819
|
-
onStep?: (
|
|
820
|
-
onSelection?: (
|
|
889
|
+
onStep?: (input: StepCallbackInput) => void;
|
|
890
|
+
onSelection?: (input: SelectionCallbackInput) => void;
|
|
821
891
|
gravity?: [number, number, number];
|
|
822
892
|
timestep?: number;
|
|
823
893
|
substeps?: number;
|
|
@@ -868,4 +938,4 @@ interface JointStateResult {
|
|
|
868
938
|
velocity: React__default.RefObject<number | Float64Array>;
|
|
869
939
|
}
|
|
870
940
|
|
|
871
|
-
export { type
|
|
941
|
+
export { type PolicyConfig as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type ActuatorInfo as E, type Sites as F, type GeomInfo as G, type SitePositionResult as H, type IkConfig as I, type Sensors as J, type SensorHandle as K, type SensorInfo as L, type MujocoContextValue as M, type Joints as N, type ObservationConfig as O, type PhysicsStepCallback as P, type JointStateResult as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type Bodies as U, type VisualScenarioEffectsProps as V, type BodyStateResult as W, type Actuators as X, type CtrlHandle as Y, type ContactInfo as Z, type KeyboardTeleopConfig as _, type MujocoCanvasProps as a, type PolicyVector as a0, type ObservationHandle as a1, type TrajectoryInput as a2, type TrajectoryStateChangeInput as a3, type PlaybackState as a4, type TrajectoryFrame as a5, type FrameCaptureOptions as a6, type FrameCaptureResult as a7, type FrameCaptureBlobResult as a8, type FrameCaptureAPI as a9, type ResetCallbackInput as aA, type ResourceSelector as aB, RobotActuators as aC, RobotBodies as aD, RobotGeoms as aE, RobotJoints as aF, RobotKeyframes as aG, type RobotResource as aH, RobotResources as aI, RobotSensors as aJ, RobotSites as aK, type Robots as aL, type ScenarioCameraConfig as aM, type ScenarioMaterialConfig as aN, type SceneMarker as aO, type SceneObject as aP, type SensorResult as aQ, type SiteInfo as aR, type SplatAssetConfig as aS, type SplatScenarioConfig as aT, type StateSnapshot as aU, type TrajectoryData as aV, type TrajectoryFrameCallbackInput as aW, type VisualScenarioMaterialFilterInput as aX, type XmlPatch as aY, getContact as aZ, registerRobotResources as a_, type BodyInfo as aa, type ControlJointInfo as ab, type FrameCaptureStatus as ac, type FrameCaptureTarget as ad, type FrameCaptureTargetRef as ae, type Geoms as af, type IKSolveFn as ag, type IkGizmoDragInput as ah, type IkSolveInput as ai, type JointInfo as aj, type KeyBinding as ak, type Keyframes as al, type ModelOptions as am, type MujocoContact as an, type MujocoContactArray as ao, type MujocoFrameCaptureOptions as ap, type ObservationLayoutItem as aq, type ObservationOutput as ar, type PhysicsConfig as as, type PhysicsStepInput as at, type PolicyActionInput as au, type PolicyInferenceInput as av, type PolicyObservationInput as aw, type RayHit as ax, type Register as ay, type RegisteredRobotMap as az, type MujocoSimAPI as b, 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 SplatCollisionPrimitive as t, type ScenarioLightingPreset as u, type SplatEnvironmentMetadataInput as v, type SplatEnvironmentMetadata as w, type SplatSceneInput as x, type DebugProps as y, type ContactListenerProps as z };
|
package/package.json
CHANGED
|
@@ -168,7 +168,7 @@ export function DragInteraction({
|
|
|
168
168
|
}, [gl, camera, scene, controls, mjDataRef]);
|
|
169
169
|
|
|
170
170
|
// Apply spring force each physics frame
|
|
171
|
-
useBeforePhysicsStep((model, data) => {
|
|
171
|
+
useBeforePhysicsStep(({ model, data }) => {
|
|
172
172
|
if (!draggingRef.current || bodyIdRef.current <= 0) return;
|
|
173
173
|
|
|
174
174
|
const bid = bodyIdRef.current;
|
|
@@ -26,7 +26,7 @@ const _scale = new THREE.Vector3(1, 1, 1);
|
|
|
26
26
|
* - `controller` — IkContextValue from `useIkController()`.
|
|
27
27
|
* - `siteName` — MuJoCo site to track. Defaults to the controller's configured site.
|
|
28
28
|
* - `scale` — Gizmo handle scale. Default: 0.18.
|
|
29
|
-
* - `onDrag` — Custom drag callback `(
|
|
29
|
+
* - `onDrag` — Custom drag callback `({ position, quaternion }) => void`.
|
|
30
30
|
* When omitted, dragging enables IK and writes to the IK target.
|
|
31
31
|
* When provided, the consumer handles what happens during drag.
|
|
32
32
|
*/
|
|
@@ -112,7 +112,7 @@ export function IkGizmo({ controller, siteName, scale = 0.18, onDrag }: IkGizmoP
|
|
|
112
112
|
world.decompose(_pos, _quat, _scale);
|
|
113
113
|
if (onDrag) {
|
|
114
114
|
// Custom: consumer handles the drag
|
|
115
|
-
onDrag(_pos.clone(), _quat.clone());
|
|
115
|
+
onDrag({ position: _pos.clone(), quaternion: _quat.clone() });
|
|
116
116
|
} else {
|
|
117
117
|
// Default: write to IK target
|
|
118
118
|
const target = ikTargetRef.current;
|
|
@@ -139,7 +139,7 @@ export function SceneRenderer(props: Omit<ThreeElements['group'], 'ref'>) {
|
|
|
139
139
|
const model = mjModelRef.current;
|
|
140
140
|
if (model && bodyID < model.nbody && onSelectionRef.current) {
|
|
141
141
|
const name = getName(model, model.name_bodyadr[bodyID]);
|
|
142
|
-
onSelectionRef.current(bodyID, name);
|
|
142
|
+
onSelectionRef.current({ bodyId: bodyID, name });
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
}}
|
|
@@ -54,7 +54,10 @@ export function TrajectoryPlayer({
|
|
|
54
54
|
const currentFrame = player.frame;
|
|
55
55
|
if (currentFrame !== lastReportedFrameRef.current && player.playing) {
|
|
56
56
|
lastReportedFrameRef.current = currentFrame;
|
|
57
|
-
onFrameRef.current(
|
|
57
|
+
onFrameRef.current({
|
|
58
|
+
frameIndex: currentFrame,
|
|
59
|
+
frame: trajectory[currentFrame],
|
|
60
|
+
});
|
|
58
61
|
}
|
|
59
62
|
});
|
|
60
63
|
|
|
@@ -490,7 +490,7 @@ function applyScenarioMaterials(
|
|
|
490
490
|
for (const material of normalizeMaterials(object.material)) {
|
|
491
491
|
const mutable = getMutableScenarioMaterial(material);
|
|
492
492
|
if (!mutable) continue;
|
|
493
|
-
if (materialFilter && !materialFilter(object, material)) continue;
|
|
493
|
+
if (materialFilter && !materialFilter({ object, material })) continue;
|
|
494
494
|
|
|
495
495
|
if (!snapshots.has(material)) {
|
|
496
496
|
snapshots.set(material, {
|
|
@@ -6,19 +6,25 @@
|
|
|
6
6
|
import { forwardRef, useEffect } from 'react';
|
|
7
7
|
import { useMujocoWasm } from './MujocoProvider';
|
|
8
8
|
import { MujocoSimProvider } from './MujocoSimProvider';
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
MujocoSimAPI,
|
|
11
|
+
ReadyCallbackInput,
|
|
12
|
+
SceneConfig,
|
|
13
|
+
SelectionCallbackInput,
|
|
14
|
+
StepCallbackInput,
|
|
15
|
+
} from '../types';
|
|
10
16
|
|
|
11
17
|
export interface MujocoPhysicsProps {
|
|
12
18
|
/** Scene/robot configuration. */
|
|
13
19
|
config: SceneConfig;
|
|
14
20
|
/** Fires when model is loaded and API is ready. */
|
|
15
|
-
onReady?: (
|
|
21
|
+
onReady?: (input: ReadyCallbackInput) => void;
|
|
16
22
|
/** Fires on scene load failure. */
|
|
17
23
|
onError?: (error: Error) => void;
|
|
18
24
|
/** Called each physics step. */
|
|
19
|
-
onStep?: (
|
|
25
|
+
onStep?: (input: StepCallbackInput) => void;
|
|
20
26
|
/** Called on body double-click selection. */
|
|
21
|
-
onSelection?: (
|
|
27
|
+
onSelection?: (input: SelectionCallbackInput) => void;
|
|
22
28
|
/** Override model gravity. */
|
|
23
29
|
gravity?: [number, number, number];
|
|
24
30
|
/** Override model.opt.timestep. */
|
|
@@ -31,13 +31,20 @@ import {
|
|
|
31
31
|
MujocoSimAPI,
|
|
32
32
|
PhysicsStepCallback,
|
|
33
33
|
RayHit,
|
|
34
|
+
ReadyCallbackInput,
|
|
34
35
|
SceneConfig,
|
|
35
36
|
SceneObject,
|
|
37
|
+
SelectionCallbackInput,
|
|
36
38
|
SensorInfo,
|
|
37
39
|
SiteInfo,
|
|
38
40
|
StateSnapshot,
|
|
41
|
+
StepCallbackInput,
|
|
39
42
|
XmlPatch,
|
|
40
43
|
} from '../types';
|
|
44
|
+
import {
|
|
45
|
+
captureFrame as captureCanvasFrame,
|
|
46
|
+
captureFrameBlob as captureCanvasFrameBlob,
|
|
47
|
+
} from '../hooks/useFrameCapture';
|
|
41
48
|
import {
|
|
42
49
|
loadScene,
|
|
43
50
|
createSceneConfigFromFiles,
|
|
@@ -117,7 +124,7 @@ export interface MujocoSimContextValue {
|
|
|
117
124
|
interpolateRef: React.RefObject<boolean>;
|
|
118
125
|
interpolationStateRef: React.RefObject<BodyInterpolationState>;
|
|
119
126
|
onSelectionRef: React.RefObject<
|
|
120
|
-
((
|
|
127
|
+
((input: SelectionCallbackInput) => void) | undefined
|
|
121
128
|
>;
|
|
122
129
|
beforeStepCallbacks: React.RefObject<Set<PhysicsStepCallback>>;
|
|
123
130
|
afterStepCallbacks: React.RefObject<Set<PhysicsStepCallback>>;
|
|
@@ -197,7 +204,7 @@ export function useBeforePhysicsStep(callback: PhysicsStepCallback) {
|
|
|
197
204
|
callbackRef.current = callback;
|
|
198
205
|
|
|
199
206
|
useEffect(() => {
|
|
200
|
-
const wrapped: PhysicsStepCallback = (
|
|
207
|
+
const wrapped: PhysicsStepCallback = (input) => callbackRef.current(input);
|
|
201
208
|
beforeStepCallbacks.current.add(wrapped);
|
|
202
209
|
return () => { beforeStepCallbacks.current.delete(wrapped); };
|
|
203
210
|
}, [beforeStepCallbacks]);
|
|
@@ -209,7 +216,7 @@ export function useAfterPhysicsStep(callback: PhysicsStepCallback) {
|
|
|
209
216
|
callbackRef.current = callback;
|
|
210
217
|
|
|
211
218
|
useEffect(() => {
|
|
212
|
-
const wrapped: PhysicsStepCallback = (
|
|
219
|
+
const wrapped: PhysicsStepCallback = (input) => callbackRef.current(input);
|
|
213
220
|
afterStepCallbacks.current.add(wrapped);
|
|
214
221
|
return () => { afterStepCallbacks.current.delete(wrapped); };
|
|
215
222
|
}, [afterStepCallbacks]);
|
|
@@ -219,10 +226,10 @@ interface MujocoSimProviderProps {
|
|
|
219
226
|
mujoco: MujocoModule;
|
|
220
227
|
config: SceneConfig;
|
|
221
228
|
apiRef?: React.ForwardedRef<MujocoSimAPI>;
|
|
222
|
-
onReady?: (
|
|
229
|
+
onReady?: (input: ReadyCallbackInput) => void;
|
|
223
230
|
onError?: (error: Error) => void;
|
|
224
|
-
onStep?: (
|
|
225
|
-
onSelection?: (
|
|
231
|
+
onStep?: (input: StepCallbackInput) => void;
|
|
232
|
+
onSelection?: (input: SelectionCallbackInput) => void;
|
|
226
233
|
// Declarative physics config props
|
|
227
234
|
gravity?: [number, number, number];
|
|
228
235
|
timestep?: number;
|
|
@@ -380,7 +387,7 @@ export function MujocoSimProvider({
|
|
|
380
387
|
useEffect(() => {
|
|
381
388
|
if (status === 'ready') {
|
|
382
389
|
const api = apiRef.current;
|
|
383
|
-
if (onReady) onReady(api);
|
|
390
|
+
if (onReady) onReady({ api });
|
|
384
391
|
// Assign the forwarded ref
|
|
385
392
|
if (externalApiRef) {
|
|
386
393
|
if (typeof externalApiRef === 'function') {
|
|
@@ -409,7 +416,7 @@ export function MujocoSimProvider({
|
|
|
409
416
|
|
|
410
417
|
// Before-step callbacks
|
|
411
418
|
for (const cb of beforeStepCallbacks.current) {
|
|
412
|
-
cb(model, data);
|
|
419
|
+
cb({ model, data });
|
|
413
420
|
}
|
|
414
421
|
|
|
415
422
|
const numSubsteps = substepsRef.current;
|
|
@@ -466,17 +473,17 @@ export function MujocoSimProvider({
|
|
|
466
473
|
interpolationStateRef.current.valid = true;
|
|
467
474
|
|
|
468
475
|
if (!stepped) {
|
|
469
|
-
onStepRef.current?.(data.time);
|
|
476
|
+
onStepRef.current?.({ time: data.time, model, data });
|
|
470
477
|
return;
|
|
471
478
|
}
|
|
472
479
|
}
|
|
473
480
|
|
|
474
481
|
// After-step callbacks
|
|
475
482
|
for (const cb of afterStepCallbacks.current) {
|
|
476
|
-
cb(model, data);
|
|
483
|
+
cb({ model, data });
|
|
477
484
|
}
|
|
478
485
|
|
|
479
|
-
onStepRef.current?.(data.time);
|
|
486
|
+
onStepRef.current?.({ time: data.time, model, data });
|
|
480
487
|
}, -1);
|
|
481
488
|
|
|
482
489
|
function ensureInterpolationBuffers(model: MujocoModel) {
|
|
@@ -515,7 +522,7 @@ export function MujocoSimProvider({
|
|
|
515
522
|
}
|
|
516
523
|
}
|
|
517
524
|
|
|
518
|
-
configRef.current.onReset?.(model, data);
|
|
525
|
+
configRef.current.onReset?.({ model, data });
|
|
519
526
|
mujoco.mj_forward(model, data);
|
|
520
527
|
|
|
521
528
|
// Notify composable plugins (e.g. IkController)
|
|
@@ -1016,6 +1023,24 @@ export function MujocoSimProvider({
|
|
|
1016
1023
|
[gl]
|
|
1017
1024
|
);
|
|
1018
1025
|
|
|
1026
|
+
const getCanvas = useCallback((): HTMLCanvasElement | null => {
|
|
1027
|
+
return gl.domElement ?? null;
|
|
1028
|
+
}, [gl]);
|
|
1029
|
+
|
|
1030
|
+
const captureFrameApi = useCallback(
|
|
1031
|
+
(options = {}) => {
|
|
1032
|
+
return captureCanvasFrame({ ...options, target: gl.domElement });
|
|
1033
|
+
},
|
|
1034
|
+
[gl]
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
const captureFrameBlobApi = useCallback(
|
|
1038
|
+
(options = {}) => {
|
|
1039
|
+
return captureCanvasFrameBlob({ ...options, target: gl.domElement });
|
|
1040
|
+
},
|
|
1041
|
+
[gl]
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1019
1044
|
const project2DTo3D = useCallback(
|
|
1020
1045
|
(x: number, y: number, cameraPos: THREE.Vector3, lookAt: THREE.Vector3): { point: THREE.Vector3; bodyId: number; geomId: number } | null => {
|
|
1021
1046
|
const virtCam = (camera as THREE.PerspectiveCamera).clone();
|
|
@@ -1125,7 +1150,10 @@ export function MujocoSimProvider({
|
|
|
1125
1150
|
addBody: addBodyApi,
|
|
1126
1151
|
removeBody: removeBodyApi,
|
|
1127
1152
|
recompile: recompileApi,
|
|
1153
|
+
getCanvas,
|
|
1128
1154
|
getCanvasSnapshot,
|
|
1155
|
+
captureFrame: captureFrameApi,
|
|
1156
|
+
captureFrameBlob: captureFrameBlobApi,
|
|
1129
1157
|
project2DTo3D,
|
|
1130
1158
|
setBodyMass,
|
|
1131
1159
|
setGeomFriction,
|
|
@@ -1143,7 +1171,8 @@ export function MujocoSimProvider({
|
|
|
1143
1171
|
getActuatorsApi, getSensors, getModelOption, setGravity, setTimestepApi,
|
|
1144
1172
|
raycast, getKeyframeNames, getKeyframeCount, loadSceneApi,
|
|
1145
1173
|
loadFromFilesApi, addBodyApi, removeBodyApi, recompileApi,
|
|
1146
|
-
getCanvasSnapshot,
|
|
1174
|
+
getCanvas, getCanvasSnapshot, captureFrameApi, captureFrameBlobApi,
|
|
1175
|
+
project2DTo3D,
|
|
1147
1176
|
setBodyMass, setGeomFriction, setGeomSize,
|
|
1148
1177
|
]
|
|
1149
1178
|
);
|
|
@@ -43,7 +43,7 @@ export type ControllerComponent<TConfig> = React.FC<{
|
|
|
43
43
|
* const MyController = createController<{ speed: number }>(
|
|
44
44
|
* { name: 'my-controller', defaultConfig: { speed: 1.0 } },
|
|
45
45
|
* function MyControllerImpl({ config }) {
|
|
46
|
-
* useBeforePhysicsStep((
|
|
46
|
+
* useBeforePhysicsStep(({ data }) => {
|
|
47
47
|
* data.ctrl[0] = config.speed;
|
|
48
48
|
* });
|
|
49
49
|
* return null;
|
|
@@ -100,7 +100,7 @@ export function createController<TConfig>(
|
|
|
100
100
|
* { name: 'useMyController', defaultConfig: { gain: 1.0 } },
|
|
101
101
|
* function useMyControllerImpl(config) {
|
|
102
102
|
* // config is MyConfig | null — hooks must be called unconditionally
|
|
103
|
-
* useBeforePhysicsStep((
|
|
103
|
+
* useBeforePhysicsStep(({ data }) => {
|
|
104
104
|
* if (!config) return;
|
|
105
105
|
* data.ctrl[0] = config.gain * Math.sin(data.time);
|
|
106
106
|
* });
|
|
@@ -29,7 +29,7 @@ export function useBodyState(name: Bodies): BodyStateResult {
|
|
|
29
29
|
bodyIdRef.current = findBodyByName(model, name);
|
|
30
30
|
}, [name, status, mjModelRef]);
|
|
31
31
|
|
|
32
|
-
useAfterPhysicsStep((
|
|
32
|
+
useAfterPhysicsStep(({ data }) => {
|
|
33
33
|
const bid = bodyIdRef.current;
|
|
34
34
|
if (bid < 0) return;
|
|
35
35
|
|
package/src/hooks/useContacts.ts
CHANGED
|
@@ -60,7 +60,7 @@ export function useContacts(
|
|
|
60
60
|
bodyResolvedRef.current = true;
|
|
61
61
|
}, [bodyName, status, mjModelRef]);
|
|
62
62
|
|
|
63
|
-
useAfterPhysicsStep((model, data) => {
|
|
63
|
+
useAfterPhysicsStep(({ model, data }) => {
|
|
64
64
|
// Resolve body id lazily once model exists, to avoid missing the first ready frame.
|
|
65
65
|
if (bodyName && !bodyResolvedRef.current) {
|
|
66
66
|
bodyIdRef.current = findBodyByName(model, bodyName);
|
|
@@ -30,7 +30,7 @@ export function useCtrlNoise(config: CtrlNoiseConfig = {}) {
|
|
|
30
30
|
configRef.current = config;
|
|
31
31
|
const noiseRef = useRef<Float64Array | null>(null);
|
|
32
32
|
|
|
33
|
-
useBeforePhysicsStep((
|
|
33
|
+
useBeforePhysicsStep(({ data }) => {
|
|
34
34
|
const cfg = configRef.current;
|
|
35
35
|
if (cfg.enabled === false) return;
|
|
36
36
|
|
|
@@ -6,48 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { useCallback, useState } from 'react';
|
|
9
|
-
import type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
| undefined;
|
|
18
|
-
|
|
19
|
-
export type FrameCaptureTargetRef =
|
|
20
|
-
React.RefObject<HTMLCanvasElement | HTMLElement | null>;
|
|
21
|
-
|
|
22
|
-
export interface FrameCaptureOptions {
|
|
23
|
-
target?: FrameCaptureTarget | FrameCaptureTargetRef;
|
|
24
|
-
type?: string;
|
|
25
|
-
quality?: number;
|
|
26
|
-
waitForAnimationFrame?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FrameCaptureResult {
|
|
30
|
-
canvas: HTMLCanvasElement;
|
|
31
|
-
dataUrl: string;
|
|
32
|
-
type: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface FrameCaptureBlobResult {
|
|
36
|
-
canvas: HTMLCanvasElement;
|
|
37
|
-
blob: Blob;
|
|
38
|
-
type: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface FrameCaptureAPI {
|
|
42
|
-
status: FrameCaptureStatus;
|
|
43
|
-
error: Error | null;
|
|
44
|
-
isCapturing: boolean;
|
|
45
|
-
capture: (options?: FrameCaptureOptions) => Promise<FrameCaptureResult>;
|
|
46
|
-
captureBlob: (
|
|
47
|
-
options?: FrameCaptureOptions
|
|
48
|
-
) => Promise<FrameCaptureBlobResult>;
|
|
49
|
-
reset: () => void;
|
|
50
|
-
}
|
|
9
|
+
import type {
|
|
10
|
+
FrameCaptureAPI,
|
|
11
|
+
FrameCaptureBlobResult,
|
|
12
|
+
FrameCaptureOptions,
|
|
13
|
+
FrameCaptureResult,
|
|
14
|
+
FrameCaptureStatus,
|
|
15
|
+
FrameCaptureTargetRef,
|
|
16
|
+
} from '../types';
|
|
51
17
|
|
|
52
18
|
function isTargetRef(
|
|
53
19
|
target: FrameCaptureOptions['target']
|
package/src/hooks/useGamepad.ts
CHANGED
|
@@ -50,7 +50,7 @@ export function useGamepad(config: GamepadConfig) {
|
|
|
50
50
|
}
|
|
51
51
|
}, [config.axes, config.buttons, status, mjModelRef]);
|
|
52
52
|
|
|
53
|
-
useBeforePhysicsStep((
|
|
53
|
+
useBeforePhysicsStep(({ data }) => {
|
|
54
54
|
const cfg = configRef.current;
|
|
55
55
|
if (cfg.enabled === false) return;
|
|
56
56
|
|
|
@@ -13,7 +13,7 @@ import { useBeforePhysicsStep } from '../core/MujocoSimProvider';
|
|
|
13
13
|
* hook (and DragInteraction) compose correctly — both add to a clean slate.
|
|
14
14
|
*/
|
|
15
15
|
export function useGravityCompensation(enabled = true): void {
|
|
16
|
-
useBeforePhysicsStep((model, data) => {
|
|
16
|
+
useBeforePhysicsStep(({ model, data }) => {
|
|
17
17
|
if (!enabled) return;
|
|
18
18
|
for (let i = 0; i < model.nv; i++) {
|
|
19
19
|
data.qfrc_applied[i] += data.qfrc_bias[i];
|
|
@@ -10,7 +10,7 @@ import { createControllerHook } from '../core/createController';
|
|
|
10
10
|
import { useMujocoContext, useBeforePhysicsStep } from '../core/MujocoSimProvider';
|
|
11
11
|
import { GenericIK } from '../core/GenericIK';
|
|
12
12
|
import { createContiguousControlGroup, findSiteByName, resolveControlGroup } from '../core/SceneLoader';
|
|
13
|
-
import type { ControlGroupInfo, IkConfig, IkContextValue, IKSolveFn, MujocoData } from '../types';
|
|
13
|
+
import type { ControlGroupInfo, IkConfig, IkContextValue, IKSolveFn, IkSolveInput, MujocoData } from '../types';
|
|
14
14
|
|
|
15
15
|
// Preallocated temp for syncGizmoToSite
|
|
16
16
|
const _syncMat4 = new THREE.Matrix4();
|
|
@@ -84,16 +84,16 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
84
84
|
|
|
85
85
|
// IK solve function
|
|
86
86
|
const ikSolveFn = useCallback(
|
|
87
|
-
(
|
|
87
|
+
({ position, quaternion, currentQ, context }: IkSolveInput): number[] | null => {
|
|
88
88
|
if (!config) return null;
|
|
89
|
-
if (config.ikSolveFn) return config.ikSolveFn(
|
|
89
|
+
if (config.ikSolveFn) return config.ikSolveFn({ position, quaternion, currentQ, context });
|
|
90
90
|
const model = mjModelRef.current;
|
|
91
91
|
const data = mjDataRef.current;
|
|
92
92
|
const controlGroup = controlGroupRef.current;
|
|
93
93
|
if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
|
|
94
94
|
return genericIkRef.current.solve(
|
|
95
95
|
model, data, siteIdRef.current, controlGroup.qposAdr,
|
|
96
|
-
|
|
96
|
+
position, quaternion, currentQ,
|
|
97
97
|
{ damping: config.damping, maxIterations: config.maxIterations },
|
|
98
98
|
);
|
|
99
99
|
},
|
|
@@ -128,7 +128,7 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
// IK solve in physics loop
|
|
131
|
-
useBeforePhysicsStep((model, data) => {
|
|
131
|
+
useBeforePhysicsStep(({ model, data }) => {
|
|
132
132
|
if (!config || !ikEnabledRef.current) {
|
|
133
133
|
ikCalculatingRef.current = false;
|
|
134
134
|
return;
|
|
@@ -142,13 +142,22 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
142
142
|
|
|
143
143
|
const currentQ = Array.from(controlGroup.readQpos(data));
|
|
144
144
|
const solution = config.ikSolveFn
|
|
145
|
-
? config.ikSolveFn(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
? config.ikSolveFn({
|
|
146
|
+
position: target.position,
|
|
147
|
+
quaternion: target.quaternion,
|
|
148
|
+
currentQ,
|
|
149
|
+
context: {
|
|
150
|
+
model,
|
|
151
|
+
data,
|
|
152
|
+
siteId: siteIdRef.current,
|
|
153
|
+
controlGroup,
|
|
154
|
+
},
|
|
150
155
|
})
|
|
151
|
-
: ikSolveFnRef.current(
|
|
156
|
+
: ikSolveFnRef.current({
|
|
157
|
+
position: target.position,
|
|
158
|
+
quaternion: target.quaternion,
|
|
159
|
+
currentQ,
|
|
160
|
+
});
|
|
152
161
|
if (solution) {
|
|
153
162
|
controlGroup.writeCtrl(data, solution);
|
|
154
163
|
}
|
|
@@ -192,8 +201,8 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
192
201
|
}, [mjDataRef]);
|
|
193
202
|
|
|
194
203
|
const solveIK = useCallback(
|
|
195
|
-
(
|
|
196
|
-
return ikSolveFnRef.current(
|
|
204
|
+
(input: IkSolveInput): number[] | null => {
|
|
205
|
+
return ikSolveFnRef.current(input);
|
|
197
206
|
},
|
|
198
207
|
[],
|
|
199
208
|
);
|