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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment } from './chunk-SEWQULWO.js';
1
+ export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment } from './chunk-33CV6HSV.js';
2
2
  import loadMujoco from '@mujoco/mujoco';
3
3
  import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
4
4
  import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
@@ -1285,7 +1285,7 @@ function SceneRenderer(props) {
1285
1285
  const model = mjModelRef.current;
1286
1286
  if (model && bodyID < model.nbody && onSelectionRef.current) {
1287
1287
  const name = getName(model, model.name_bodyadr[bodyID]);
1288
- onSelectionRef.current(bodyID, name);
1288
+ onSelectionRef.current({ bodyId: bodyID, name });
1289
1289
  }
1290
1290
  }
1291
1291
  }
@@ -1294,6 +1294,114 @@ function SceneRenderer(props) {
1294
1294
  }
1295
1295
  var _previousQuat = new THREE11.Quaternion();
1296
1296
  var _currentQuat = new THREE11.Quaternion();
1297
+ function isTargetRef(target) {
1298
+ return Boolean(target && typeof target === "object" && "current" in target);
1299
+ }
1300
+ function resolveCanvasTarget(target) {
1301
+ const resolvedTarget = isTargetRef(target) ? target.current : target;
1302
+ if (!resolvedTarget) {
1303
+ throw new Error("No frame capture target is available.");
1304
+ }
1305
+ if (resolvedTarget instanceof HTMLCanvasElement) {
1306
+ return resolvedTarget;
1307
+ }
1308
+ const canvas = resolvedTarget.querySelector("canvas");
1309
+ if (!canvas) {
1310
+ throw new Error("Frame capture target does not contain a canvas.");
1311
+ }
1312
+ return canvas;
1313
+ }
1314
+ function waitForNextAnimationFrame() {
1315
+ return new Promise((resolve) => {
1316
+ requestAnimationFrame(() => resolve());
1317
+ });
1318
+ }
1319
+ async function captureFrame(options) {
1320
+ const type = options.type ?? "image/png";
1321
+ const canvas = resolveCanvasTarget(options.target);
1322
+ if (options.waitForAnimationFrame ?? true) {
1323
+ await waitForNextAnimationFrame();
1324
+ }
1325
+ return {
1326
+ canvas,
1327
+ dataUrl: canvas.toDataURL(type, options.quality),
1328
+ type
1329
+ };
1330
+ }
1331
+ async function captureFrameBlob(options) {
1332
+ const type = options.type ?? "image/png";
1333
+ const canvas = resolveCanvasTarget(options.target);
1334
+ if (options.waitForAnimationFrame ?? true) {
1335
+ await waitForNextAnimationFrame();
1336
+ }
1337
+ const blob = await new Promise((resolve, reject) => {
1338
+ canvas.toBlob(
1339
+ (nextBlob) => {
1340
+ if (nextBlob) {
1341
+ resolve(nextBlob);
1342
+ } else {
1343
+ reject(new Error("Canvas frame capture did not produce a Blob."));
1344
+ }
1345
+ },
1346
+ type,
1347
+ options.quality
1348
+ );
1349
+ });
1350
+ return { canvas, blob, type };
1351
+ }
1352
+ function useFrameCapture(defaultOptions = {}) {
1353
+ const [status, setStatus] = useState("idle");
1354
+ const [error, setError] = useState(null);
1355
+ const reset = useCallback(() => {
1356
+ setStatus("idle");
1357
+ setError(null);
1358
+ }, []);
1359
+ const capture = useCallback(
1360
+ async (options = {}) => {
1361
+ setStatus("capturing");
1362
+ setError(null);
1363
+ try {
1364
+ const result = await captureFrame({ ...defaultOptions, ...options });
1365
+ setStatus("captured");
1366
+ return result;
1367
+ } catch (nextError) {
1368
+ const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
1369
+ setError(error2);
1370
+ setStatus("error");
1371
+ throw error2;
1372
+ }
1373
+ },
1374
+ [defaultOptions]
1375
+ );
1376
+ const captureBlob = useCallback(
1377
+ async (options = {}) => {
1378
+ setStatus("capturing");
1379
+ setError(null);
1380
+ try {
1381
+ const result = await captureFrameBlob({
1382
+ ...defaultOptions,
1383
+ ...options
1384
+ });
1385
+ setStatus("captured");
1386
+ return result;
1387
+ } catch (nextError) {
1388
+ const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
1389
+ setError(error2);
1390
+ setStatus("error");
1391
+ throw error2;
1392
+ }
1393
+ },
1394
+ [defaultOptions]
1395
+ );
1396
+ return {
1397
+ status,
1398
+ error,
1399
+ isCapturing: status === "capturing",
1400
+ capture,
1401
+ captureBlob,
1402
+ reset
1403
+ };
1404
+ }
1297
1405
  var JOINT_TYPE_NAMES2 = ["free", "ball", "slide", "hinge"];
1298
1406
  var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
1299
1407
  var SENSOR_TYPE_NAMES = {
@@ -1420,7 +1528,7 @@ function useBeforePhysicsStep(callback) {
1420
1528
  const callbackRef = useRef(callback);
1421
1529
  callbackRef.current = callback;
1422
1530
  useEffect(() => {
1423
- const wrapped = (model, data) => callbackRef.current(model, data);
1531
+ const wrapped = (input) => callbackRef.current(input);
1424
1532
  beforeStepCallbacks.current.add(wrapped);
1425
1533
  return () => {
1426
1534
  beforeStepCallbacks.current.delete(wrapped);
@@ -1432,7 +1540,7 @@ function useAfterPhysicsStep(callback) {
1432
1540
  const callbackRef = useRef(callback);
1433
1541
  callbackRef.current = callback;
1434
1542
  useEffect(() => {
1435
- const wrapped = (model, data) => callbackRef.current(model, data);
1543
+ const wrapped = (input) => callbackRef.current(input);
1436
1544
  afterStepCallbacks.current.add(wrapped);
1437
1545
  return () => {
1438
1546
  afterStepCallbacks.current.delete(wrapped);
@@ -1576,7 +1684,7 @@ function MujocoSimProvider({
1576
1684
  useEffect(() => {
1577
1685
  if (status === "ready") {
1578
1686
  const api2 = apiRef.current;
1579
- if (onReady) onReady(api2);
1687
+ if (onReady) onReady({ api: api2 });
1580
1688
  if (externalApiRef) {
1581
1689
  if (typeof externalApiRef === "function") {
1582
1690
  externalApiRef(api2);
@@ -1596,7 +1704,7 @@ function MujocoSimProvider({
1596
1704
  data.qfrc_applied[i] = 0;
1597
1705
  }
1598
1706
  for (const cb of beforeStepCallbacks.current) {
1599
- cb(model, data);
1707
+ cb({ model, data });
1600
1708
  }
1601
1709
  const numSubsteps = substepsRef.current;
1602
1710
  if (!interpolateRef.current) {
@@ -1647,14 +1755,14 @@ function MujocoSimProvider({
1647
1755
  interpolationStateRef.current.alpha = Math.min(Math.max(physicsAccumulatorRef.current / stepDt, 0), 1);
1648
1756
  interpolationStateRef.current.valid = true;
1649
1757
  if (!stepped) {
1650
- onStepRef.current?.(data.time);
1758
+ onStepRef.current?.({ time: data.time, model, data });
1651
1759
  return;
1652
1760
  }
1653
1761
  }
1654
1762
  for (const cb of afterStepCallbacks.current) {
1655
- cb(model, data);
1763
+ cb({ model, data });
1656
1764
  }
1657
- onStepRef.current?.(data.time);
1765
+ onStepRef.current?.({ time: data.time, model, data });
1658
1766
  }, -1);
1659
1767
  function ensureInterpolationBuffers(model) {
1660
1768
  const state = interpolationStateRef.current;
@@ -1685,7 +1793,7 @@ function MujocoSimProvider({
1685
1793
  }
1686
1794
  }
1687
1795
  }
1688
- configRef.current.onReset?.(model, data);
1796
+ configRef.current.onReset?.({ model, data });
1689
1797
  mujoco.mj_forward(model, data);
1690
1798
  for (const cb of resetCallbacks.current) {
1691
1799
  cb();
@@ -2145,6 +2253,21 @@ function MujocoSimProvider({
2145
2253
  },
2146
2254
  [gl]
2147
2255
  );
2256
+ const getCanvas = useCallback(() => {
2257
+ return gl.domElement ?? null;
2258
+ }, [gl]);
2259
+ const captureFrameApi = useCallback(
2260
+ (options = {}) => {
2261
+ return captureFrame({ ...options, target: gl.domElement });
2262
+ },
2263
+ [gl]
2264
+ );
2265
+ const captureFrameBlobApi = useCallback(
2266
+ (options = {}) => {
2267
+ return captureFrameBlob({ ...options, target: gl.domElement });
2268
+ },
2269
+ [gl]
2270
+ );
2148
2271
  const project2DTo3D = useCallback(
2149
2272
  (x, y, cameraPos, lookAt) => {
2150
2273
  const virtCam = camera.clone();
@@ -2251,7 +2374,10 @@ function MujocoSimProvider({
2251
2374
  addBody: addBodyApi,
2252
2375
  removeBody: removeBodyApi,
2253
2376
  recompile: recompileApi,
2377
+ getCanvas,
2254
2378
  getCanvasSnapshot,
2379
+ captureFrame: captureFrameApi,
2380
+ captureFrameBlob: captureFrameBlobApi,
2255
2381
  project2DTo3D,
2256
2382
  setBodyMass,
2257
2383
  setGeomFriction,
@@ -2303,7 +2429,10 @@ function MujocoSimProvider({
2303
2429
  addBodyApi,
2304
2430
  removeBodyApi,
2305
2431
  recompileApi,
2432
+ getCanvas,
2306
2433
  getCanvasSnapshot,
2434
+ captureFrameApi,
2435
+ captureFrameBlobApi,
2307
2436
  project2DTo3D,
2308
2437
  setBodyMass,
2309
2438
  setGeomFriction,
@@ -2841,9 +2970,9 @@ var useIkController = createControllerHook(
2841
2970
  }
2842
2971
  }, [config?.siteName, config?.numJoints, config?.joints, config?.actuators, status, mjModelRef, mjDataRef, config]);
2843
2972
  const ikSolveFn = useCallback(
2844
- (pos, quat, currentQ) => {
2973
+ ({ position, quaternion, currentQ, context }) => {
2845
2974
  if (!config) return null;
2846
- if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
2975
+ if (config.ikSolveFn) return config.ikSolveFn({ position, quaternion, currentQ, context });
2847
2976
  const model = mjModelRef.current;
2848
2977
  const data = mjDataRef.current;
2849
2978
  const controlGroup = controlGroupRef.current;
@@ -2853,8 +2982,8 @@ var useIkController = createControllerHook(
2853
2982
  data,
2854
2983
  siteIdRef.current,
2855
2984
  controlGroup.qposAdr,
2856
- pos,
2857
- quat,
2985
+ position,
2986
+ quaternion,
2858
2987
  currentQ,
2859
2988
  { damping: config.damping, maxIterations: config.maxIterations }
2860
2989
  );
@@ -2883,7 +3012,7 @@ var useIkController = createControllerHook(
2883
3012
  target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
2884
3013
  if (t >= 1) ga.active = false;
2885
3014
  });
2886
- useBeforePhysicsStep((model, data) => {
3015
+ useBeforePhysicsStep(({ model, data }) => {
2887
3016
  if (!config || !ikEnabledRef.current) {
2888
3017
  ikCalculatingRef.current = false;
2889
3018
  return;
@@ -2894,12 +3023,21 @@ var useIkController = createControllerHook(
2894
3023
  const controlGroup = controlGroupRef.current;
2895
3024
  if (!controlGroup) return;
2896
3025
  const currentQ = Array.from(controlGroup.readQpos(data));
2897
- const solution = config.ikSolveFn ? config.ikSolveFn(target.position, target.quaternion, currentQ, {
2898
- model,
2899
- data,
2900
- siteId: siteIdRef.current,
2901
- controlGroup
2902
- }) : ikSolveFnRef.current(target.position, target.quaternion, currentQ);
3026
+ const solution = config.ikSolveFn ? config.ikSolveFn({
3027
+ position: target.position,
3028
+ quaternion: target.quaternion,
3029
+ currentQ,
3030
+ context: {
3031
+ model,
3032
+ data,
3033
+ siteId: siteIdRef.current,
3034
+ controlGroup
3035
+ }
3036
+ }) : ikSolveFnRef.current({
3037
+ position: target.position,
3038
+ quaternion: target.quaternion,
3039
+ currentQ
3040
+ });
2903
3041
  if (solution) {
2904
3042
  controlGroup.writeCtrl(data, solution);
2905
3043
  }
@@ -2938,8 +3076,8 @@ var useIkController = createControllerHook(
2938
3076
  if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
2939
3077
  }, [mjDataRef]);
2940
3078
  const solveIK = useCallback(
2941
- (pos, quat, currentQ) => {
2942
- return ikSolveFnRef.current(pos, quat, currentQ);
3079
+ (input) => {
3080
+ return ikSolveFnRef.current(input);
2943
3081
  },
2944
3082
  []
2945
3083
  );
@@ -3153,7 +3291,7 @@ function IkGizmo({ controller, siteName, scale = 0.18, onDrag }) {
3153
3291
  onDrag: (_l, _dl, world) => {
3154
3292
  world.decompose(_pos, _quat, _scale);
3155
3293
  if (onDrag) {
3156
- onDrag(_pos.clone(), _quat.clone());
3294
+ onDrag({ position: _pos.clone(), quaternion: _quat.clone() });
3157
3295
  } else {
3158
3296
  const target = ikTargetRef.current;
3159
3297
  if (target) {
@@ -3327,7 +3465,7 @@ function DragInteraction({
3327
3465
  window.removeEventListener("pointercancel", onPointerUp);
3328
3466
  };
3329
3467
  }, [gl, camera, scene, controls, mjDataRef]);
3330
- useBeforePhysicsStep((model, data) => {
3468
+ useBeforePhysicsStep(({ model, data }) => {
3331
3469
  if (!draggingRef.current || bodyIdRef.current <= 0) return;
3332
3470
  const bid = bodyIdRef.current;
3333
3471
  const mujoco = mujocoRef.current;
@@ -4118,7 +4256,7 @@ function useContacts(bodyName, callback) {
4118
4256
  bodyIdRef.current = findBodyByName(model, bodyName);
4119
4257
  bodyResolvedRef.current = true;
4120
4258
  }, [bodyName, status, mjModelRef]);
4121
- useAfterPhysicsStep((model, data) => {
4259
+ useAfterPhysicsStep(({ model, data }) => {
4122
4260
  if (bodyName && !bodyResolvedRef.current) {
4123
4261
  bodyIdRef.current = findBodyByName(model, bodyName);
4124
4262
  bodyResolvedRef.current = true;
@@ -4224,7 +4362,7 @@ function useTrajectoryPlayer(trajectory, options = {}) {
4224
4362
  const setState = useCallback((next) => {
4225
4363
  if (stateRef.current === next) return;
4226
4364
  stateRef.current = next;
4227
- optionsRef.current.onStateChange?.(next);
4365
+ optionsRef.current.onStateChange?.({ state: next });
4228
4366
  }, []);
4229
4367
  const play = useCallback(() => {
4230
4368
  const traj = trajectoryRef.current;
@@ -4306,7 +4444,7 @@ function useTrajectoryPlayer(trajectory, options = {}) {
4306
4444
  }
4307
4445
  }
4308
4446
  });
4309
- useBeforePhysicsStep((model, data) => {
4447
+ useBeforePhysicsStep(({ model, data }) => {
4310
4448
  if (stateRef.current !== "playing") return;
4311
4449
  if ((optionsRef.current.mode ?? "kinematic") !== "physics") return;
4312
4450
  const traj = trajectoryRef.current;
@@ -4391,7 +4529,10 @@ function TrajectoryPlayer({
4391
4529
  const currentFrame = player.frame;
4392
4530
  if (currentFrame !== lastReportedFrameRef.current && player.playing) {
4393
4531
  lastReportedFrameRef.current = currentFrame;
4394
- onFrameRef.current(currentFrame);
4532
+ onFrameRef.current({
4533
+ frameIndex: currentFrame,
4534
+ frame: trajectory[currentFrame]
4535
+ });
4395
4536
  }
4396
4537
  });
4397
4538
  return null;
@@ -4465,7 +4606,7 @@ function useSitePosition(siteName) {
4465
4606
 
4466
4607
  // src/hooks/useGravityCompensation.ts
4467
4608
  function useGravityCompensation(enabled = true) {
4468
- useBeforePhysicsStep((model, data) => {
4609
+ useBeforePhysicsStep(({ model, data }) => {
4469
4610
  if (!enabled) return;
4470
4611
  for (let i = 0; i < model.nv; i++) {
4471
4612
  data.qfrc_applied[i] += data.qfrc_bias[i];
@@ -4492,7 +4633,7 @@ function useSensor(name) {
4492
4633
  }
4493
4634
  sensorIdRef.current = -1;
4494
4635
  }, [name, status, mjModelRef]);
4495
- useAfterPhysicsStep((_model, data) => {
4636
+ useAfterPhysicsStep(({ data }) => {
4496
4637
  if (sensorIdRef.current < 0) return;
4497
4638
  const adr = sensorAdrRef.current;
4498
4639
  const dim = sensorDimRef.current;
@@ -4589,7 +4730,7 @@ function useJointState(name) {
4589
4730
  }
4590
4731
  jointIdRef.current = -1;
4591
4732
  }, [name, status, mjModelRef]);
4592
- useAfterPhysicsStep((_model, data) => {
4733
+ useAfterPhysicsStep(({ data }) => {
4593
4734
  if (jointIdRef.current < 0) return;
4594
4735
  const qa = qposAdrRef.current;
4595
4736
  const da = dofAdrRef.current;
@@ -4619,7 +4760,7 @@ function useBodyState(name) {
4619
4760
  if (!model || status !== "ready") return;
4620
4761
  bodyIdRef.current = findBodyByName(model, name);
4621
4762
  }, [name, status, mjModelRef]);
4622
- useAfterPhysicsStep((_model, data) => {
4763
+ useAfterPhysicsStep(({ data }) => {
4623
4764
  const bid = bodyIdRef.current;
4624
4765
  if (bid < 0) return;
4625
4766
  const i3 = bid * 3;
@@ -4715,7 +4856,7 @@ function useKeyboardTeleop(config) {
4715
4856
  window.removeEventListener("keyup", onKeyUp);
4716
4857
  };
4717
4858
  }, []);
4718
- useBeforePhysicsStep((_model, data) => {
4859
+ useBeforePhysicsStep(({ data }) => {
4719
4860
  if (!enabledRef.current) return;
4720
4861
  const bindings = bindingsRef.current;
4721
4862
  const cache = actuatorCacheRef.current;
@@ -4743,7 +4884,7 @@ function usePolicy(config) {
4743
4884
  const configRef = useRef(config);
4744
4885
  configRef.current = config;
4745
4886
  isRunningRef.current = config.enabled ?? isRunningRef.current;
4746
- useBeforePhysicsStep((model, data) => {
4887
+ useBeforePhysicsStep(({ model, data }) => {
4747
4888
  if (!isRunningRef.current) return;
4748
4889
  const cfg = configRef.current;
4749
4890
  model.opt?.timestep ?? 2e-3;
@@ -4800,7 +4941,7 @@ function useTrajectoryRecorder(options = {}) {
4800
4941
  const recordingRef = useRef(false);
4801
4942
  const framesRef = useRef([]);
4802
4943
  const fields = options.fields ?? ["qpos"];
4803
- useAfterPhysicsStep((_model, data) => {
4944
+ useAfterPhysicsStep(({ data }) => {
4804
4945
  if (!recordingRef.current) return;
4805
4946
  const frame = {
4806
4947
  time: data.time,
@@ -4889,7 +5030,7 @@ function useGamepad(config) {
4889
5030
  buttonCacheRef.current.set(Number(idx), findActuatorByName(model, name));
4890
5031
  }
4891
5032
  }, [config.axes, config.buttons, status, mjModelRef]);
4892
- useBeforePhysicsStep((_model, data) => {
5033
+ useBeforePhysicsStep(({ data }) => {
4893
5034
  const cfg = configRef.current;
4894
5035
  if (cfg.enabled === false) return;
4895
5036
  const gamepads = navigator.getGamepads?.();
@@ -4967,120 +5108,12 @@ function useVideoRecorder(options = {}) {
4967
5108
  }
4968
5109
  };
4969
5110
  }
4970
- function isTargetRef(target) {
4971
- return Boolean(target && typeof target === "object" && "current" in target);
4972
- }
4973
- function resolveCanvasTarget(target) {
4974
- const resolvedTarget = isTargetRef(target) ? target.current : target;
4975
- if (!resolvedTarget) {
4976
- throw new Error("No frame capture target is available.");
4977
- }
4978
- if (resolvedTarget instanceof HTMLCanvasElement) {
4979
- return resolvedTarget;
4980
- }
4981
- const canvas = resolvedTarget.querySelector("canvas");
4982
- if (!canvas) {
4983
- throw new Error("Frame capture target does not contain a canvas.");
4984
- }
4985
- return canvas;
4986
- }
4987
- function waitForNextAnimationFrame() {
4988
- return new Promise((resolve) => {
4989
- requestAnimationFrame(() => resolve());
4990
- });
4991
- }
4992
- async function captureFrame(options) {
4993
- const type = options.type ?? "image/png";
4994
- const canvas = resolveCanvasTarget(options.target);
4995
- if (options.waitForAnimationFrame ?? true) {
4996
- await waitForNextAnimationFrame();
4997
- }
4998
- return {
4999
- canvas,
5000
- dataUrl: canvas.toDataURL(type, options.quality),
5001
- type
5002
- };
5003
- }
5004
- async function captureFrameBlob(options) {
5005
- const type = options.type ?? "image/png";
5006
- const canvas = resolveCanvasTarget(options.target);
5007
- if (options.waitForAnimationFrame ?? true) {
5008
- await waitForNextAnimationFrame();
5009
- }
5010
- const blob = await new Promise((resolve, reject) => {
5011
- canvas.toBlob(
5012
- (nextBlob) => {
5013
- if (nextBlob) {
5014
- resolve(nextBlob);
5015
- } else {
5016
- reject(new Error("Canvas frame capture did not produce a Blob."));
5017
- }
5018
- },
5019
- type,
5020
- options.quality
5021
- );
5022
- });
5023
- return { canvas, blob, type };
5024
- }
5025
- function useFrameCapture(defaultOptions = {}) {
5026
- const [status, setStatus] = useState("idle");
5027
- const [error, setError] = useState(null);
5028
- const reset = useCallback(() => {
5029
- setStatus("idle");
5030
- setError(null);
5031
- }, []);
5032
- const capture = useCallback(
5033
- async (options = {}) => {
5034
- setStatus("capturing");
5035
- setError(null);
5036
- try {
5037
- const result = await captureFrame({ ...defaultOptions, ...options });
5038
- setStatus("captured");
5039
- return result;
5040
- } catch (nextError) {
5041
- const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
5042
- setError(error2);
5043
- setStatus("error");
5044
- throw error2;
5045
- }
5046
- },
5047
- [defaultOptions]
5048
- );
5049
- const captureBlob = useCallback(
5050
- async (options = {}) => {
5051
- setStatus("capturing");
5052
- setError(null);
5053
- try {
5054
- const result = await captureFrameBlob({
5055
- ...defaultOptions,
5056
- ...options
5057
- });
5058
- setStatus("captured");
5059
- return result;
5060
- } catch (nextError) {
5061
- const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
5062
- setError(error2);
5063
- setStatus("error");
5064
- throw error2;
5065
- }
5066
- },
5067
- [defaultOptions]
5068
- );
5069
- return {
5070
- status,
5071
- error,
5072
- isCapturing: status === "capturing",
5073
- capture,
5074
- captureBlob,
5075
- reset
5076
- };
5077
- }
5078
5111
  function useCtrlNoise(config = {}) {
5079
5112
  const { mjModelRef } = useMujocoContext();
5080
5113
  const configRef = useRef(config);
5081
5114
  configRef.current = config;
5082
5115
  const noiseRef = useRef(null);
5083
- useBeforePhysicsStep((_model, data) => {
5116
+ useBeforePhysicsStep(({ data }) => {
5084
5117
  const cfg = configRef.current;
5085
5118
  if (cfg.enabled === false) return;
5086
5119
  const rate = cfg.rate ?? 0.01;
@@ -5227,6 +5260,12 @@ function useCameraAnimation() {
5227
5260
  * @license
5228
5261
  * SPDX-License-Identifier: Apache-2.0
5229
5262
  */
5263
+ /**
5264
+ * @license
5265
+ * SPDX-License-Identifier: Apache-2.0
5266
+ *
5267
+ * useFrameCapture — still-frame capture for canvas-backed MuJoCo/R3F scenes.
5268
+ */
5230
5269
  /**
5231
5270
  * @license
5232
5271
  * SPDX-License-Identifier: Apache-2.0
@@ -5360,12 +5399,6 @@ function useCameraAnimation() {
5360
5399
  *
5361
5400
  * useVideoRecorder — canvas video recording hook (spec 13.3)
5362
5401
  */
5363
- /**
5364
- * @license
5365
- * SPDX-License-Identifier: Apache-2.0
5366
- *
5367
- * useFrameCapture — still-frame capture for canvas-backed MuJoCo/R3F scenes.
5368
- */
5369
5402
  /**
5370
5403
  * @license
5371
5404
  * SPDX-License-Identifier: Apache-2.0