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/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-izZlUweI.js';
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 BodyInfo as a6, type ControlJointInfo as a7, type Geoms as a8, type IKSolveFn as a9, RobotResources as aA, RobotSensors as aB, RobotSites as aC, type Robots as aD, type ScenarioCameraConfig as aE, type ScenarioMaterialConfig as aF, type SceneMarker as aG, type SceneObject as aH, type SensorResult as aI, type SiteInfo as aJ, type SplatAssetConfig as aK, type SplatScenarioConfig as aL, type StateSnapshot as aM, type TrajectoryData as aN, type TrajectoryFrameCallbackInput as aO, type VisualScenarioMaterialFilterInput as aP, type XmlPatch as aQ, getContact as aR, registerRobotResources as aS, type IkGizmoDragInput as aa, type IkSolveInput as ab, type JointInfo as ac, type KeyBinding as ad, type Keyframes as ae, type ModelOptions as af, type MujocoContact as ag, type MujocoContactArray as ah, type ObservationLayoutItem as ai, type ObservationOutput as aj, type PhysicsConfig as ak, type PhysicsStepInput as al, type PolicyActionInput as am, type PolicyInferenceInput as an, type PolicyObservationInput as ao, type RayHit as ap, type Register as aq, type RegisteredRobotMap as ar, type ResetCallbackInput as as, type ResourceSelector as at, RobotActuators as au, RobotBodies as av, RobotGeoms as aw, RobotJoints as ax, RobotKeyframes as ay, type RobotResource 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 };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "9.0.0",
3
+ "version": "9.2.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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, project2DTo3D,
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
- * data.ctrl[0] = config.speed;
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
- * data.ctrl[0] = config.gain * Math.sin(data.time);
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 React from 'react';
10
-
11
- export type FrameCaptureStatus = 'idle' | 'capturing' | 'captured' | 'error';
12
-
13
- export type FrameCaptureTarget =
14
- | HTMLCanvasElement
15
- | HTMLElement
16
- | null
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 type {
86
- FrameCaptureAPI,
87
- FrameCaptureBlobResult,
88
- FrameCaptureOptions,
89
- FrameCaptureResult,
90
- FrameCaptureStatus,
91
- FrameCaptureTarget,
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,