mujoco-react 9.0.0 → 9.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/README.md +45 -10
- package/dist/index.d.ts +43 -33
- package/dist/index.js +547 -195
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/{types-izZlUweI.d.ts → types-S8ggQY2n.d.ts} +103 -1
- package/package.json +1 -1
- package/src/core/MujocoSimProvider.tsx +146 -2
- package/src/core/createController.tsx +6 -2
- package/src/hooks/useCameraFrameCapture.ts +94 -0
- package/src/hooks/useCameraSequenceRecorder.ts +59 -0
- package/src/hooks/useFrameCapture.ts +8 -42
- package/src/index.ts +26 -9
- package/src/rendering/cameraFrameCapture.ts +184 -0
- package/src/types.ts +136 -0
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 } from './types-
|
|
3
|
+
import { n as SplatEnvironmentProps } from './types-S8ggQY2n.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import '@react-three/fiber';
|
|
6
6
|
import 'three';
|
|
@@ -837,7 +837,13 @@ interface MujocoSimAPI {
|
|
|
837
837
|
addBody(body: SceneObject): Promise<void>;
|
|
838
838
|
removeBody(name: Bodies): Promise<void>;
|
|
839
839
|
recompile(patches?: XmlPatch[]): Promise<void>;
|
|
840
|
+
getCanvas(): HTMLCanvasElement | null;
|
|
840
841
|
getCanvasSnapshot(width?: number, height?: number, mimeType?: string): string;
|
|
842
|
+
captureFrame(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureResult>;
|
|
843
|
+
captureFrameBlob(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureBlobResult>;
|
|
844
|
+
captureCameraFrame(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
|
|
845
|
+
captureCameraFrameBlob(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
|
|
846
|
+
recordCameraSequence(options: CameraFrameSequenceOptions): Promise<CameraFrameSequenceResult>;
|
|
841
847
|
project2DTo3D(x: number, y: number, cameraPos: THREE.Vector3, lookAt: THREE.Vector3): {
|
|
842
848
|
point: THREE.Vector3;
|
|
843
849
|
bodyId: number;
|
|
@@ -849,6 +855,102 @@ interface MujocoSimAPI {
|
|
|
849
855
|
readonly mjModelRef: React__default.RefObject<MujocoModel | null>;
|
|
850
856
|
readonly mjDataRef: React__default.RefObject<MujocoData | null>;
|
|
851
857
|
}
|
|
858
|
+
type FrameCaptureStatus = 'idle' | 'capturing' | 'captured' | 'error';
|
|
859
|
+
type FrameCaptureTarget = HTMLCanvasElement | HTMLElement | null | undefined;
|
|
860
|
+
type FrameCaptureTargetRef = React__default.RefObject<HTMLCanvasElement | HTMLElement | null>;
|
|
861
|
+
interface FrameCaptureOptions {
|
|
862
|
+
target?: FrameCaptureTarget | FrameCaptureTargetRef;
|
|
863
|
+
type?: string;
|
|
864
|
+
quality?: number;
|
|
865
|
+
waitForAnimationFrame?: boolean;
|
|
866
|
+
}
|
|
867
|
+
type MujocoFrameCaptureOptions = Omit<FrameCaptureOptions, 'target'>;
|
|
868
|
+
interface FrameCaptureResult {
|
|
869
|
+
canvas: HTMLCanvasElement;
|
|
870
|
+
dataUrl: string;
|
|
871
|
+
type: string;
|
|
872
|
+
}
|
|
873
|
+
interface FrameCaptureBlobResult {
|
|
874
|
+
canvas: HTMLCanvasElement;
|
|
875
|
+
blob: Blob;
|
|
876
|
+
type: string;
|
|
877
|
+
}
|
|
878
|
+
interface FrameCaptureAPI {
|
|
879
|
+
status: FrameCaptureStatus;
|
|
880
|
+
error: Error | null;
|
|
881
|
+
isCapturing: boolean;
|
|
882
|
+
capture: (options?: FrameCaptureOptions) => Promise<FrameCaptureResult>;
|
|
883
|
+
captureBlob: (options?: FrameCaptureOptions) => Promise<FrameCaptureBlobResult>;
|
|
884
|
+
reset: () => void;
|
|
885
|
+
}
|
|
886
|
+
type CameraFrameCaptureVector3 = THREE.Vector3 | readonly [number, number, number];
|
|
887
|
+
type CameraFrameCaptureQuaternion = THREE.Quaternion | readonly [number, number, number, number];
|
|
888
|
+
interface CameraFrameCaptureOptions {
|
|
889
|
+
camera?: THREE.Camera;
|
|
890
|
+
position?: CameraFrameCaptureVector3;
|
|
891
|
+
lookAt?: CameraFrameCaptureVector3;
|
|
892
|
+
quaternion?: CameraFrameCaptureQuaternion;
|
|
893
|
+
up?: CameraFrameCaptureVector3;
|
|
894
|
+
width?: number;
|
|
895
|
+
height?: number;
|
|
896
|
+
type?: string;
|
|
897
|
+
quality?: number;
|
|
898
|
+
fov?: number;
|
|
899
|
+
near?: number;
|
|
900
|
+
far?: number;
|
|
901
|
+
}
|
|
902
|
+
interface CameraFrameCaptureResult {
|
|
903
|
+
canvas: HTMLCanvasElement;
|
|
904
|
+
camera: THREE.Camera;
|
|
905
|
+
dataUrl: string;
|
|
906
|
+
type: string;
|
|
907
|
+
width: number;
|
|
908
|
+
height: number;
|
|
909
|
+
}
|
|
910
|
+
interface CameraFrameCaptureBlobResult {
|
|
911
|
+
canvas: HTMLCanvasElement;
|
|
912
|
+
camera: THREE.Camera;
|
|
913
|
+
blob: Blob;
|
|
914
|
+
type: string;
|
|
915
|
+
width: number;
|
|
916
|
+
height: number;
|
|
917
|
+
}
|
|
918
|
+
interface CameraFrameCaptureAPI {
|
|
919
|
+
status: FrameCaptureStatus;
|
|
920
|
+
error: Error | null;
|
|
921
|
+
isCapturing: boolean;
|
|
922
|
+
capture: (options?: CameraFrameCaptureOptions) => Promise<CameraFrameCaptureResult>;
|
|
923
|
+
captureBlob: (options?: CameraFrameCaptureOptions) => Promise<CameraFrameCaptureBlobResult>;
|
|
924
|
+
reset: () => void;
|
|
925
|
+
}
|
|
926
|
+
interface CameraFrameSequenceCamera extends CameraFrameCaptureOptions {
|
|
927
|
+
key: string;
|
|
928
|
+
}
|
|
929
|
+
interface CameraFrameSequenceFrame {
|
|
930
|
+
frameIndex: number;
|
|
931
|
+
time: number;
|
|
932
|
+
cameras: Record<string, CameraFrameCaptureResult>;
|
|
933
|
+
}
|
|
934
|
+
interface CameraFrameSequenceOptions {
|
|
935
|
+
cameras: readonly CameraFrameSequenceCamera[];
|
|
936
|
+
frames: number;
|
|
937
|
+
stepsPerFrame?: number;
|
|
938
|
+
reset?: boolean;
|
|
939
|
+
captureInitialFrame?: boolean;
|
|
940
|
+
onFrame?: (frame: CameraFrameSequenceFrame) => void | Promise<void>;
|
|
941
|
+
}
|
|
942
|
+
interface CameraFrameSequenceResult {
|
|
943
|
+
frames: CameraFrameSequenceFrame[];
|
|
944
|
+
cameraKeys: string[];
|
|
945
|
+
frameCount: number;
|
|
946
|
+
}
|
|
947
|
+
interface CameraFrameSequenceRecorderAPI {
|
|
948
|
+
status: FrameCaptureStatus;
|
|
949
|
+
error: Error | null;
|
|
950
|
+
isRecording: boolean;
|
|
951
|
+
record: (options: CameraFrameSequenceOptions) => Promise<CameraFrameSequenceResult>;
|
|
952
|
+
reset: () => void;
|
|
953
|
+
}
|
|
852
954
|
type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
853
955
|
config: SceneConfig;
|
|
854
956
|
/** R3F content rendered while the MuJoCo WASM module is still loading. */
|
|
@@ -907,4 +1009,4 @@ interface JointStateResult {
|
|
|
907
1009
|
velocity: React__default.RefObject<number | Float64Array>;
|
|
908
1010
|
}
|
|
909
1011
|
|
|
910
|
-
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
|
|
1012
|
+
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 SensorResult 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 MujocoFrameCaptureOptions as aA, type ObservationLayoutItem as aB, type ObservationOutput as aC, type PhysicsConfig as aD, type PhysicsStepInput as aE, type PolicyActionInput as aF, type PolicyInferenceInput as aG, type PolicyObservationInput as aH, type RayHit as aI, type Register as aJ, type RegisteredRobotMap as aK, type ResetCallbackInput as aL, type ResourceSelector as aM, RobotActuators as aN, RobotBodies as aO, RobotGeoms as aP, RobotJoints as aQ, RobotKeyframes as aR, type RobotResource as aS, RobotResources as aT, RobotSensors as aU, RobotSites as aV, type Robots as aW, type ScenarioCameraConfig as aX, type ScenarioMaterialConfig as aY, type SceneMarker as aZ, type SceneObject as a_, type CameraFrameCaptureOptions as aa, type CameraFrameCaptureAPI as ab, type CameraFrameSequenceRecorderAPI as ac, type CameraFrameCaptureResult as ad, type CameraFrameCaptureBlobResult as ae, type BodyInfo as af, type CameraFrameCaptureQuaternion as ag, type CameraFrameCaptureVector3 as ah, type CameraFrameSequenceCamera as ai, type CameraFrameSequenceFrame as aj, type CameraFrameSequenceOptions as ak, type CameraFrameSequenceResult as al, type ControlJointInfo as am, type FrameCaptureStatus as an, type FrameCaptureTarget as ao, type FrameCaptureTargetRef as ap, type Geoms as aq, type IKSolveFn as ar, type IkGizmoDragInput as as, type IkSolveInput as at, type JointInfo as au, type KeyBinding as av, type Keyframes as aw, type ModelOptions as ax, type MujocoContact as ay, type MujocoContactArray as az, type MujocoSimAPI as b, type SiteInfo as b0, type SplatAssetConfig as b1, type SplatScenarioConfig as b2, type StateSnapshot as b3, type TrajectoryData as b4, type TrajectoryFrameCallbackInput as b5, type VisualScenarioMaterialFilterInput as b6, type XmlPatch as b7, getContact as b8, registerRobotResources as b9, 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
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
ActuatedJointInfo,
|
|
21
21
|
ActuatorInfo,
|
|
22
22
|
BodyInfo,
|
|
23
|
+
CameraFrameCaptureResult,
|
|
24
|
+
CameraFrameSequenceFrame,
|
|
25
|
+
CameraFrameSequenceOptions,
|
|
26
|
+
CameraFrameSequenceResult,
|
|
23
27
|
ControlGroupInfo,
|
|
24
28
|
ControlGroupSelector,
|
|
25
29
|
ContactInfo,
|
|
@@ -41,6 +45,14 @@ import {
|
|
|
41
45
|
StepCallbackInput,
|
|
42
46
|
XmlPatch,
|
|
43
47
|
} from '../types';
|
|
48
|
+
import {
|
|
49
|
+
captureFrame as captureCanvasFrame,
|
|
50
|
+
captureFrameBlob as captureCanvasFrameBlob,
|
|
51
|
+
} from '../hooks/useFrameCapture';
|
|
52
|
+
import {
|
|
53
|
+
captureCameraFrame,
|
|
54
|
+
captureCameraFrameBlob,
|
|
55
|
+
} from '../rendering/cameraFrameCapture';
|
|
44
56
|
import {
|
|
45
57
|
loadScene,
|
|
46
58
|
createSceneConfigFromFiles,
|
|
@@ -106,6 +118,12 @@ const _rayGeomId = new Int32Array(1);
|
|
|
106
118
|
const _projRaycaster = new THREE.Raycaster();
|
|
107
119
|
const _projNdc = new THREE.Vector2();
|
|
108
120
|
|
|
121
|
+
function waitForNextAnimationFrame() {
|
|
122
|
+
return new Promise<void>((resolve) => {
|
|
123
|
+
requestAnimationFrame(() => resolve());
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
109
127
|
// ---- Internal context types ----
|
|
110
128
|
|
|
111
129
|
export interface MujocoSimContextValue {
|
|
@@ -252,7 +270,7 @@ export function MujocoSimProvider({
|
|
|
252
270
|
interpolate,
|
|
253
271
|
children,
|
|
254
272
|
}: MujocoSimProviderProps) {
|
|
255
|
-
const { gl, camera } = useThree();
|
|
273
|
+
const { gl, camera, scene } = useThree();
|
|
256
274
|
const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading');
|
|
257
275
|
|
|
258
276
|
// --- Refs ---
|
|
@@ -544,6 +562,30 @@ export function MujocoSimProvider({
|
|
|
544
562
|
stepsToRunRef.current = n;
|
|
545
563
|
}, []);
|
|
546
564
|
|
|
565
|
+
const stepImmediately = useCallback((steps = 1) => {
|
|
566
|
+
const model = mjModelRef.current;
|
|
567
|
+
const data = mjDataRef.current;
|
|
568
|
+
if (!model || !data) return false;
|
|
569
|
+
|
|
570
|
+
for (let stepIndex = 0; stepIndex < steps; stepIndex += 1) {
|
|
571
|
+
for (let i = 0; i < model.nv; i += 1) {
|
|
572
|
+
data.qfrc_applied[i] = 0;
|
|
573
|
+
}
|
|
574
|
+
for (const cb of beforeStepCallbacks.current) {
|
|
575
|
+
cb({ model, data });
|
|
576
|
+
}
|
|
577
|
+
mujoco.mj_step(model, data);
|
|
578
|
+
for (const cb of afterStepCallbacks.current) {
|
|
579
|
+
cb({ model, data });
|
|
580
|
+
}
|
|
581
|
+
onStepRef.current?.({ time: data.time, model, data });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
physicsAccumulatorRef.current = 0;
|
|
585
|
+
interpolationStateRef.current.valid = false;
|
|
586
|
+
return true;
|
|
587
|
+
}, [mujoco]);
|
|
588
|
+
|
|
547
589
|
const getTime = useCallback((): number => {
|
|
548
590
|
return mjDataRef.current?.time ?? 0;
|
|
549
591
|
}, []);
|
|
@@ -1019,6 +1061,99 @@ export function MujocoSimProvider({
|
|
|
1019
1061
|
[gl]
|
|
1020
1062
|
);
|
|
1021
1063
|
|
|
1064
|
+
const getCanvas = useCallback((): HTMLCanvasElement | null => {
|
|
1065
|
+
return gl.domElement ?? null;
|
|
1066
|
+
}, [gl]);
|
|
1067
|
+
|
|
1068
|
+
const captureFrameApi = useCallback(
|
|
1069
|
+
(options = {}) => {
|
|
1070
|
+
return captureCanvasFrame({ ...options, target: gl.domElement });
|
|
1071
|
+
},
|
|
1072
|
+
[gl]
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
const captureFrameBlobApi = useCallback(
|
|
1076
|
+
(options = {}) => {
|
|
1077
|
+
return captureCanvasFrameBlob({ ...options, target: gl.domElement });
|
|
1078
|
+
},
|
|
1079
|
+
[gl]
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
const captureCameraFrameApi = useCallback(
|
|
1083
|
+
(options = {}) => {
|
|
1084
|
+
return captureCameraFrame(gl, scene, camera, options);
|
|
1085
|
+
},
|
|
1086
|
+
[camera, gl, scene]
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
const captureCameraFrameBlobApi = useCallback(
|
|
1090
|
+
(options = {}) => {
|
|
1091
|
+
return captureCameraFrameBlob(gl, scene, camera, options);
|
|
1092
|
+
},
|
|
1093
|
+
[camera, gl, scene]
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
const recordCameraSequenceApi = useCallback(
|
|
1097
|
+
async (
|
|
1098
|
+
options: CameraFrameSequenceOptions
|
|
1099
|
+
): Promise<CameraFrameSequenceResult> => {
|
|
1100
|
+
const frameCount = Math.max(0, Math.floor(options.frames));
|
|
1101
|
+
const stepsPerFrame = Math.max(1, Math.floor(options.stepsPerFrame ?? 1));
|
|
1102
|
+
const cameras = options.cameras;
|
|
1103
|
+
const frames: CameraFrameSequenceFrame[] = [];
|
|
1104
|
+
const wasPaused = pausedRef.current;
|
|
1105
|
+
|
|
1106
|
+
if (frameCount === 0 || cameras.length === 0) {
|
|
1107
|
+
return {
|
|
1108
|
+
frames,
|
|
1109
|
+
cameraKeys: cameras.map((sequenceCamera) => sequenceCamera.key),
|
|
1110
|
+
frameCount: 0,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
try {
|
|
1115
|
+
pausedRef.current = true;
|
|
1116
|
+
stepsToRunRef.current = 0;
|
|
1117
|
+
if (options.reset) reset();
|
|
1118
|
+
|
|
1119
|
+
for (let frameIndex = 0; frameIndex < frameCount; frameIndex += 1) {
|
|
1120
|
+
if (frameIndex > 0 || options.captureInitialFrame === false) {
|
|
1121
|
+
stepImmediately(stepsPerFrame);
|
|
1122
|
+
}
|
|
1123
|
+
await waitForNextAnimationFrame();
|
|
1124
|
+
|
|
1125
|
+
const cameraFrames: Record<string, CameraFrameCaptureResult> = {};
|
|
1126
|
+
for (const sequenceCamera of cameras) {
|
|
1127
|
+
const { key, ...captureOptions } = sequenceCamera;
|
|
1128
|
+
cameraFrames[key] = await captureCameraFrame(
|
|
1129
|
+
gl,
|
|
1130
|
+
scene,
|
|
1131
|
+
camera,
|
|
1132
|
+
captureOptions
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const frame = {
|
|
1137
|
+
frameIndex,
|
|
1138
|
+
time: getTime(),
|
|
1139
|
+
cameras: cameraFrames,
|
|
1140
|
+
};
|
|
1141
|
+
frames.push(frame);
|
|
1142
|
+
await options.onFrame?.(frame);
|
|
1143
|
+
}
|
|
1144
|
+
} finally {
|
|
1145
|
+
pausedRef.current = wasPaused;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
return {
|
|
1149
|
+
frames,
|
|
1150
|
+
cameraKeys: cameras.map((sequenceCamera) => sequenceCamera.key),
|
|
1151
|
+
frameCount: frames.length,
|
|
1152
|
+
};
|
|
1153
|
+
},
|
|
1154
|
+
[camera, getTime, gl, reset, scene, stepImmediately]
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1022
1157
|
const project2DTo3D = useCallback(
|
|
1023
1158
|
(x: number, y: number, cameraPos: THREE.Vector3, lookAt: THREE.Vector3): { point: THREE.Vector3; bodyId: number; geomId: number } | null => {
|
|
1024
1159
|
const virtCam = (camera as THREE.PerspectiveCamera).clone();
|
|
@@ -1128,7 +1263,13 @@ export function MujocoSimProvider({
|
|
|
1128
1263
|
addBody: addBodyApi,
|
|
1129
1264
|
removeBody: removeBodyApi,
|
|
1130
1265
|
recompile: recompileApi,
|
|
1266
|
+
getCanvas,
|
|
1131
1267
|
getCanvasSnapshot,
|
|
1268
|
+
captureFrame: captureFrameApi,
|
|
1269
|
+
captureFrameBlob: captureFrameBlobApi,
|
|
1270
|
+
captureCameraFrame: captureCameraFrameApi,
|
|
1271
|
+
captureCameraFrameBlob: captureCameraFrameBlobApi,
|
|
1272
|
+
recordCameraSequence: recordCameraSequenceApi,
|
|
1132
1273
|
project2DTo3D,
|
|
1133
1274
|
setBodyMass,
|
|
1134
1275
|
setGeomFriction,
|
|
@@ -1146,7 +1287,10 @@ export function MujocoSimProvider({
|
|
|
1146
1287
|
getActuatorsApi, getSensors, getModelOption, setGravity, setTimestepApi,
|
|
1147
1288
|
raycast, getKeyframeNames, getKeyframeCount, loadSceneApi,
|
|
1148
1289
|
loadFromFilesApi, addBodyApi, removeBodyApi, recompileApi,
|
|
1149
|
-
getCanvasSnapshot,
|
|
1290
|
+
getCanvas, getCanvasSnapshot, captureFrameApi, captureFrameBlobApi,
|
|
1291
|
+
captureCameraFrameApi, captureCameraFrameBlobApi,
|
|
1292
|
+
recordCameraSequenceApi,
|
|
1293
|
+
project2DTo3D,
|
|
1150
1294
|
setBodyMass, setGeomFriction, setGeomSize,
|
|
1151
1295
|
]
|
|
1152
1296
|
);
|
|
@@ -43,8 +43,10 @@ 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
|
+
* const wheel = useCtrl(RobotActuators.mobile.leftWheel);
|
|
47
|
+
*
|
|
46
48
|
* useBeforePhysicsStep(({ data }) => {
|
|
47
|
-
*
|
|
49
|
+
* wheel.write(config.speed);
|
|
48
50
|
* });
|
|
49
51
|
* return null;
|
|
50
52
|
* },
|
|
@@ -100,9 +102,11 @@ export function createController<TConfig>(
|
|
|
100
102
|
* { name: 'useMyController', defaultConfig: { gain: 1.0 } },
|
|
101
103
|
* function useMyControllerImpl(config) {
|
|
102
104
|
* // config is MyConfig | null — hooks must be called unconditionally
|
|
105
|
+
* const joint = useCtrl(config?.actuator ?? RobotActuators.franka.actuator1);
|
|
106
|
+
*
|
|
103
107
|
* useBeforePhysicsStep(({ data }) => {
|
|
104
108
|
* if (!config) return;
|
|
105
|
-
*
|
|
109
|
+
* joint.write(config.gain * Math.sin(data.time));
|
|
106
110
|
* });
|
|
107
111
|
* if (!config) return null;
|
|
108
112
|
* return { /* value *\/ };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* React state wrapper around MuJoCo/R3F offscreen camera-frame capture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useState } from 'react';
|
|
9
|
+
import { useMujoco } from '../core/MujocoSimProvider';
|
|
10
|
+
import type {
|
|
11
|
+
CameraFrameCaptureAPI,
|
|
12
|
+
CameraFrameCaptureOptions,
|
|
13
|
+
FrameCaptureStatus,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
export function useCameraFrameCapture(
|
|
17
|
+
defaultOptions: CameraFrameCaptureOptions = {}
|
|
18
|
+
): CameraFrameCaptureAPI {
|
|
19
|
+
const mujoco = useMujoco();
|
|
20
|
+
const [status, setStatus] = useState<FrameCaptureStatus>('idle');
|
|
21
|
+
const [error, setError] = useState<Error | null>(null);
|
|
22
|
+
|
|
23
|
+
const reset = useCallback(() => {
|
|
24
|
+
setStatus('idle');
|
|
25
|
+
setError(null);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const capture = useCallback(
|
|
29
|
+
async (options: CameraFrameCaptureOptions = {}) => {
|
|
30
|
+
if (!mujoco.api) {
|
|
31
|
+
throw new Error('MuJoCo scene is not ready for camera frame capture.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setStatus('capturing');
|
|
35
|
+
setError(null);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await mujoco.api.captureCameraFrame({
|
|
39
|
+
...defaultOptions,
|
|
40
|
+
...options,
|
|
41
|
+
});
|
|
42
|
+
setStatus('captured');
|
|
43
|
+
return result;
|
|
44
|
+
} catch (nextError) {
|
|
45
|
+
const error =
|
|
46
|
+
nextError instanceof Error
|
|
47
|
+
? nextError
|
|
48
|
+
: new Error('Unable to capture the requested camera frame.');
|
|
49
|
+
setError(error);
|
|
50
|
+
setStatus('error');
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[defaultOptions, mujoco.api]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const captureBlob = useCallback(
|
|
58
|
+
async (options: CameraFrameCaptureOptions = {}) => {
|
|
59
|
+
if (!mujoco.api) {
|
|
60
|
+
throw new Error('MuJoCo scene is not ready for camera frame capture.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setStatus('capturing');
|
|
64
|
+
setError(null);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const result = await mujoco.api.captureCameraFrameBlob({
|
|
68
|
+
...defaultOptions,
|
|
69
|
+
...options,
|
|
70
|
+
});
|
|
71
|
+
setStatus('captured');
|
|
72
|
+
return result;
|
|
73
|
+
} catch (nextError) {
|
|
74
|
+
const error =
|
|
75
|
+
nextError instanceof Error
|
|
76
|
+
? nextError
|
|
77
|
+
: new Error('Unable to capture the requested camera frame.');
|
|
78
|
+
setError(error);
|
|
79
|
+
setStatus('error');
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[defaultOptions, mujoco.api]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
status,
|
|
88
|
+
error,
|
|
89
|
+
isCapturing: status === 'capturing',
|
|
90
|
+
capture,
|
|
91
|
+
captureBlob,
|
|
92
|
+
reset,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* React state wrapper around fixed-camera simulation sequence recording.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useState } from 'react';
|
|
9
|
+
import { useMujoco } from '../core/MujocoSimProvider';
|
|
10
|
+
import type {
|
|
11
|
+
CameraFrameSequenceOptions,
|
|
12
|
+
CameraFrameSequenceRecorderAPI,
|
|
13
|
+
FrameCaptureStatus,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
export function useCameraSequenceRecorder(): CameraFrameSequenceRecorderAPI {
|
|
17
|
+
const mujoco = useMujoco();
|
|
18
|
+
const [status, setStatus] = useState<FrameCaptureStatus>('idle');
|
|
19
|
+
const [error, setError] = useState<Error | null>(null);
|
|
20
|
+
|
|
21
|
+
const reset = useCallback(() => {
|
|
22
|
+
setStatus('idle');
|
|
23
|
+
setError(null);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const record = useCallback(
|
|
27
|
+
async (options: CameraFrameSequenceOptions) => {
|
|
28
|
+
if (!mujoco.api) {
|
|
29
|
+
throw new Error('MuJoCo scene is not ready for camera sequence recording.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setStatus('capturing');
|
|
33
|
+
setError(null);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = await mujoco.api.recordCameraSequence(options);
|
|
37
|
+
setStatus('captured');
|
|
38
|
+
return result;
|
|
39
|
+
} catch (nextError) {
|
|
40
|
+
const error =
|
|
41
|
+
nextError instanceof Error
|
|
42
|
+
? nextError
|
|
43
|
+
: new Error('Unable to record the requested camera sequence.');
|
|
44
|
+
setError(error);
|
|
45
|
+
setStatus('error');
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[mujoco.api]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
status,
|
|
54
|
+
error,
|
|
55
|
+
isRecording: status === 'capturing',
|
|
56
|
+
record,
|
|
57
|
+
reset,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -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/index.ts
CHANGED
|
@@ -82,15 +82,13 @@ export {
|
|
|
82
82
|
captureFrameBlob,
|
|
83
83
|
useFrameCapture,
|
|
84
84
|
} from './hooks/useFrameCapture';
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
FrameCaptureTargetRef,
|
|
93
|
-
} from './hooks/useFrameCapture';
|
|
85
|
+
export { useCameraFrameCapture } from './hooks/useCameraFrameCapture';
|
|
86
|
+
export { useCameraSequenceRecorder } from './hooks/useCameraSequenceRecorder';
|
|
87
|
+
export {
|
|
88
|
+
captureCameraFrame,
|
|
89
|
+
captureCameraFrameBlob,
|
|
90
|
+
renderCameraFrameToCanvas,
|
|
91
|
+
} from './rendering/cameraFrameCapture';
|
|
94
92
|
export { useCtrlNoise } from './hooks/useCtrlNoise';
|
|
95
93
|
export { useBodyMeshes } from './hooks/useBodyMeshes';
|
|
96
94
|
export { useSelectionHighlight } from './hooks/useSelectionHighlight';
|
|
@@ -189,9 +187,28 @@ export type {
|
|
|
189
187
|
ContactListenerProps,
|
|
190
188
|
// API
|
|
191
189
|
MujocoSimAPI,
|
|
190
|
+
MujocoFrameCaptureOptions,
|
|
191
|
+
CameraFrameCaptureAPI,
|
|
192
|
+
CameraFrameCaptureBlobResult,
|
|
193
|
+
CameraFrameCaptureOptions,
|
|
194
|
+
CameraFrameCaptureQuaternion,
|
|
195
|
+
CameraFrameCaptureResult,
|
|
196
|
+
CameraFrameCaptureVector3,
|
|
197
|
+
CameraFrameSequenceCamera,
|
|
198
|
+
CameraFrameSequenceFrame,
|
|
199
|
+
CameraFrameSequenceOptions,
|
|
200
|
+
CameraFrameSequenceRecorderAPI,
|
|
201
|
+
CameraFrameSequenceResult,
|
|
192
202
|
MujocoCanvasProps,
|
|
193
203
|
MujocoContextValue,
|
|
194
204
|
// Hook return types
|
|
205
|
+
FrameCaptureAPI,
|
|
206
|
+
FrameCaptureBlobResult,
|
|
207
|
+
FrameCaptureOptions,
|
|
208
|
+
FrameCaptureResult,
|
|
209
|
+
FrameCaptureStatus,
|
|
210
|
+
FrameCaptureTarget,
|
|
211
|
+
FrameCaptureTargetRef,
|
|
195
212
|
SitePositionResult,
|
|
196
213
|
SensorResult,
|
|
197
214
|
CtrlHandle,
|