mujoco-react 10.4.0 → 10.6.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,8 +1,8 @@
1
- import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY } from './chunk-FBXXXPLQ.js';
2
- export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-FBXXXPLQ.js';
1
+ import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, createCaptureCamera, prepareCaptureCamera } from './chunk-EN55TTGH.js';
2
+ export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-EN55TTGH.js';
3
3
  import loadMujoco from '@mujoco/mujoco';
4
4
  import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
5
- import { createContext, forwardRef, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect } from 'react';
5
+ import { createContext, forwardRef, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect, useId } from 'react';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
7
  import { Canvas, useThree, useFrame } from '@react-three/fiber';
8
8
  import * as THREE11 from 'three';
@@ -492,8 +492,10 @@ function sceneObjectToXml(obj) {
492
492
  const solref = obj.solref ? ` solref="${obj.solref}"` : "";
493
493
  const solimp = obj.solimp ? ` solimp="${obj.solimp}"` : "";
494
494
  const condim = obj.condim ? ` condim="${obj.condim}"` : "";
495
+ const contype = obj.contype ?? 1;
496
+ const conaffinity = obj.conaffinity ?? 1;
495
497
  const group = obj.group !== void 0 ? ` group="${obj.group}"` : "";
496
- return `<body name="${obj.name}" pos="${pos}">${joint}<geom name="${geomName}" type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
498
+ return `<body name="${obj.name}" pos="${pos}">${joint}<geom name="${geomName}" type="${obj.type}" size="${size}" rgba="${rgba}" contype="${contype}" conaffinity="${conaffinity}"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
497
499
  }
498
500
  function ensureDir(mujoco, fname) {
499
501
  const dirParts = fname.split("/");
@@ -1170,6 +1172,118 @@ function SceneRenderer({ renderOptions, ...props }) {
1170
1172
  }
1171
1173
  var _previousQuat = new THREE11.Quaternion();
1172
1174
  var _currentQuat = new THREE11.Quaternion();
1175
+ var CameraViewportRegistryContext = createContext(null);
1176
+ var nextViewportId = 0;
1177
+ var VIEWPORT_RENDER_PRIORITY = 1;
1178
+ function CameraViewportRenderer({
1179
+ viewportsRef
1180
+ }) {
1181
+ const gl = useThree((state) => state.gl);
1182
+ const scene = useThree((state) => state.scene);
1183
+ const mainCamera = useThree((state) => state.camera);
1184
+ const mujoco = useMujoco();
1185
+ useFrame(() => {
1186
+ const drawWidth = gl.domElement.width;
1187
+ const drawHeight = gl.domElement.height;
1188
+ gl.setScissorTest(false);
1189
+ gl.setViewport(0, 0, drawWidth, drawHeight);
1190
+ gl.render(scene, mainCamera);
1191
+ const api = mujoco.api;
1192
+ if (!api || viewportsRef.current.size === 0) return;
1193
+ const dpr = gl.getPixelRatio();
1194
+ const canvasRect = gl.domElement.getBoundingClientRect();
1195
+ for (const descriptor of viewportsRef.current.values()) {
1196
+ const element = descriptor.getElement();
1197
+ if (!element) continue;
1198
+ const rect = element.getBoundingClientRect();
1199
+ const isOffscreen = rect.bottom < canvasRect.top || rect.top > canvasRect.bottom || rect.right < canvasRect.left || rect.left > canvasRect.right;
1200
+ if (isOffscreen) continue;
1201
+ const width = Math.floor(rect.width * dpr);
1202
+ const height = Math.floor(rect.height * dpr);
1203
+ if (width <= 0 || height <= 0) continue;
1204
+ const left = Math.floor((rect.left - canvasRect.left) * dpr);
1205
+ const bottom = Math.floor((canvasRect.bottom - rect.bottom) * dpr);
1206
+ let resolved;
1207
+ try {
1208
+ resolved = api.resolveCameraCaptureOptions(descriptor.getOptions());
1209
+ } catch {
1210
+ continue;
1211
+ }
1212
+ if (!descriptor.camera) {
1213
+ descriptor.camera = createCaptureCamera(resolved, mainCamera, width, height);
1214
+ } else {
1215
+ prepareCaptureCamera(descriptor.camera, resolved, mainCamera, width, height);
1216
+ }
1217
+ gl.setViewport(left, bottom, width, height);
1218
+ gl.setScissor(left, bottom, width, height);
1219
+ gl.setScissorTest(true);
1220
+ gl.render(scene, descriptor.camera);
1221
+ }
1222
+ gl.setScissorTest(false);
1223
+ gl.setViewport(0, 0, drawWidth, drawHeight);
1224
+ }, VIEWPORT_RENDER_PRIORITY);
1225
+ return null;
1226
+ }
1227
+ function CameraViewportProvider({ children }) {
1228
+ const viewportsRef = useRef(/* @__PURE__ */ new Map());
1229
+ const [count, setCount] = useState(0);
1230
+ const register = useCallback((descriptor) => {
1231
+ const id = nextViewportId++;
1232
+ viewportsRef.current.set(id, descriptor);
1233
+ setCount((value2) => value2 + 1);
1234
+ return () => {
1235
+ viewportsRef.current.delete(id);
1236
+ setCount((value2) => value2 - 1);
1237
+ };
1238
+ }, []);
1239
+ const value = useMemo(() => ({ register }), [register]);
1240
+ return /* @__PURE__ */ jsxs(CameraViewportRegistryContext.Provider, { value, children: [
1241
+ children,
1242
+ count > 0 && /* @__PURE__ */ jsx(CameraViewportRenderer, { viewportsRef })
1243
+ ] });
1244
+ }
1245
+ function useCameraViewport(elementRef, options) {
1246
+ const registry = useContext(CameraViewportRegistryContext);
1247
+ if (!registry) {
1248
+ throw new Error("useCameraViewport must be used inside <MujocoCanvas>.");
1249
+ }
1250
+ const optionsRef = useRef(options);
1251
+ optionsRef.current = options;
1252
+ useEffect(() => {
1253
+ const descriptor = {
1254
+ getElement: () => elementRef.current,
1255
+ getOptions: () => optionsRef.current,
1256
+ camera: null
1257
+ };
1258
+ return registry.register(descriptor);
1259
+ }, [registry, elementRef]);
1260
+ }
1261
+ function CameraView({ className, style, ...options }) {
1262
+ const gl = useThree((state) => state.gl);
1263
+ const elementRef = useRef(null);
1264
+ if (!elementRef.current && typeof document !== "undefined") {
1265
+ elementRef.current = document.createElement("div");
1266
+ }
1267
+ useEffect(() => {
1268
+ const element = elementRef.current;
1269
+ const parent = gl.domElement.parentElement;
1270
+ if (!element || !parent) return;
1271
+ element.style.position = "absolute";
1272
+ element.style.overflow = "hidden";
1273
+ parent.appendChild(element);
1274
+ return () => {
1275
+ parent.removeChild(element);
1276
+ };
1277
+ }, [gl]);
1278
+ useEffect(() => {
1279
+ const element = elementRef.current;
1280
+ if (!element) return;
1281
+ element.className = className ?? "";
1282
+ if (style) Object.assign(element.style, style);
1283
+ }, [className, style]);
1284
+ useCameraViewport(elementRef, options);
1285
+ return null;
1286
+ }
1173
1287
  function isTargetRef(target) {
1174
1288
  return Boolean(target && typeof target === "object" && "current" in target);
1175
1289
  }
@@ -3021,6 +3135,32 @@ function MujocoSimProvider({
3021
3135
  },
3022
3136
  [camera, gl, resolveCameraCaptureOptions, scene]
3023
3137
  );
3138
+ const createCameraFrameCaptureSessionApi = useCallback(
3139
+ (options = {}) => createCameraFrameCaptureSession(
3140
+ gl,
3141
+ scene,
3142
+ camera,
3143
+ resolveCameraCaptureOptions(options)
3144
+ ),
3145
+ [camera, gl, resolveCameraCaptureOptions, scene]
3146
+ );
3147
+ const captureCameraFrameTensorApi = useCallback(
3148
+ (options = {}) => {
3149
+ const resolved = {
3150
+ ...resolveCameraCaptureOptions(options),
3151
+ channels: options.channels,
3152
+ layout: options.layout,
3153
+ range: options.range
3154
+ };
3155
+ const session = createCameraFrameCaptureSession(gl, scene, camera, resolved);
3156
+ try {
3157
+ return session.captureTensor(resolved);
3158
+ } finally {
3159
+ session.dispose();
3160
+ }
3161
+ },
3162
+ [camera, gl, resolveCameraCaptureOptions, scene]
3163
+ );
3024
3164
  const recordCameraSequenceApi = useCallback(
3025
3165
  async (options) => {
3026
3166
  const frameCount = Math.max(0, Math.floor(options.frames));
@@ -3320,6 +3460,9 @@ function MujocoSimProvider({
3320
3460
  captureFrameBlob: captureFrameBlobApi,
3321
3461
  captureCameraFrame: captureCameraFrameApi,
3322
3462
  captureCameraFrameBlob: captureCameraFrameBlobApi,
3463
+ captureCameraFrameTensor: captureCameraFrameTensorApi,
3464
+ createCameraFrameCaptureSession: createCameraFrameCaptureSessionApi,
3465
+ resolveCameraCaptureOptions,
3323
3466
  recordCameraSequence: recordCameraSequenceApi,
3324
3467
  project2DTo3D,
3325
3468
  projectImagePointTo3D: projectImagePointTo3D2,
@@ -3380,6 +3523,9 @@ function MujocoSimProvider({
3380
3523
  captureFrameBlobApi,
3381
3524
  captureCameraFrameApi,
3382
3525
  captureCameraFrameBlobApi,
3526
+ captureCameraFrameTensorApi,
3527
+ createCameraFrameCaptureSessionApi,
3528
+ resolveCameraCaptureOptions,
3383
3529
  recordCameraSequenceApi,
3384
3530
  project2DTo3D,
3385
3531
  projectImagePointTo3D2,
@@ -3416,7 +3562,7 @@ function MujocoSimProvider({
3416
3562
  );
3417
3563
  return /* @__PURE__ */ jsxs(MujocoSimContext.Provider, { value: contextValue, children: [
3418
3564
  /* @__PURE__ */ jsx(SceneRenderer, { renderOptions }),
3419
- children
3565
+ /* @__PURE__ */ jsx(CameraViewportProvider, { children })
3420
3566
  ] });
3421
3567
  }
3422
3568
  var MujocoCanvas = forwardRef(
@@ -3641,7 +3787,8 @@ function resolveOptions(opts) {
3641
3787
  tolerance: opts?.tolerance ?? DEFAULTS.tolerance,
3642
3788
  epsilon: opts?.epsilon ?? DEFAULTS.epsilon,
3643
3789
  posWeight: opts?.posWeight ?? DEFAULTS.posWeight,
3644
- rotWeight: opts?.rotWeight ?? DEFAULTS.rotWeight
3790
+ rotWeight: opts?.rotWeight ?? DEFAULTS.rotWeight,
3791
+ jointLimits: opts?.jointLimits
3645
3792
  };
3646
3793
  }
3647
3794
  var GenericIK = class {
@@ -3680,6 +3827,8 @@ var GenericIK = class {
3680
3827
  const pertSiteMat = new Float64Array(9);
3681
3828
  let bestQ = null;
3682
3829
  let bestErr = Infinity;
3830
+ const patience = 4;
3831
+ let noImprove = 0;
3683
3832
  if (n === 0) return null;
3684
3833
  for (let iter = 0; iter < o.maxIterations; iter++) {
3685
3834
  for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
@@ -3705,11 +3854,15 @@ var GenericIK = class {
3705
3854
  const errNorm = Math.sqrt(
3706
3855
  error[0] * error[0] + error[1] * error[1] + error[2] * error[2] + error[3] * error[3] + error[4] * error[4] + error[5] * error[5]
3707
3856
  );
3708
- if (errNorm < bestErr) {
3857
+ if (errNorm < bestErr - 1e-9) {
3709
3858
  bestErr = errNorm;
3710
3859
  bestQ = Array.from(q);
3860
+ noImprove = 0;
3861
+ } else {
3862
+ noImprove++;
3711
3863
  }
3712
3864
  if (errNorm < o.tolerance) break;
3865
+ if (noImprove >= patience) break;
3713
3866
  for (let j = 0; j < n; j++) {
3714
3867
  const adr = qposAdr[j];
3715
3868
  const saved = data.qpos[adr];
@@ -3865,6 +4018,12 @@ function solve6x6(A, b, x) {
3865
4018
 
3866
4019
  // src/hooks/useIkController.ts
3867
4020
  var _syncMat4 = new THREE11.Matrix4();
4021
+ function toIkVector3(value) {
4022
+ return "x" in value ? new THREE11.Vector3(value.x, value.y, value.z) : new THREE11.Vector3(value[0], value[1], value[2]);
4023
+ }
4024
+ function toIkQuaternion(value) {
4025
+ return "x" in value ? new THREE11.Quaternion(value.x, value.y, value.z, value.w) : new THREE11.Quaternion(value[0], value[1], value[2], value[3]);
4026
+ }
3868
4027
  function syncGizmoToSite(data, siteId, target) {
3869
4028
  if (siteId === -1) return;
3870
4029
  const sitePos = data.site_xpos.subarray(siteId * 3, siteId * 3 + 3);
@@ -3937,22 +4096,31 @@ var useIkController = createControllerHook(
3937
4096
  const ikSolveFn = useCallback(
3938
4097
  ({ position, quaternion, currentQ, context }) => {
3939
4098
  if (!config) return null;
3940
- if (config.ikSolveFn) return config.ikSolveFn({ position, quaternion, currentQ, context });
4099
+ const targetPosition = toIkVector3(position);
4100
+ const targetQuaternion = toIkQuaternion(quaternion);
4101
+ const normalizedInput = {
4102
+ position: targetPosition,
4103
+ quaternion: targetQuaternion,
4104
+ currentQ,
4105
+ context
4106
+ };
4107
+ if (config.ikSolveFn) return config.ikSolveFn(normalizedInput);
3941
4108
  const model = mjModelRef.current;
3942
4109
  const data = mjDataRef.current;
3943
- const controlGroup = controlGroupRef.current;
3944
- if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
4110
+ const controlGroup2 = controlGroupRef.current;
4111
+ if (!model || !data || !controlGroup2 || siteIdRef.current === -1) return null;
3945
4112
  return genericIkRef.current.solve(
3946
4113
  model,
3947
4114
  data,
3948
4115
  siteIdRef.current,
3949
- controlGroup.qposAdr,
3950
- position,
3951
- quaternion,
4116
+ controlGroup2.qposAdr,
4117
+ targetPosition,
4118
+ targetQuaternion,
3952
4119
  currentQ,
3953
4120
  {
3954
4121
  damping: config.damping,
3955
4122
  epsilon: config.epsilon,
4123
+ jointLimits: config.jointLimits ?? controlGroup2.joints.map((joint) => joint.limited ? joint.range : joint.ctrlRange),
3956
4124
  maxIterations: config.maxIterations,
3957
4125
  posWeight: config.posWeight,
3958
4126
  rotWeight: config.rotWeight,
@@ -3992,9 +4160,9 @@ var useIkController = createControllerHook(
3992
4160
  const target = ikTargetRef.current;
3993
4161
  if (!target) return;
3994
4162
  ikCalculatingRef.current = true;
3995
- const controlGroup = controlGroupRef.current;
3996
- if (!controlGroup) return;
3997
- const currentQ = Array.from(controlGroup.readQpos(data));
4163
+ const controlGroup2 = controlGroupRef.current;
4164
+ if (!controlGroup2) return;
4165
+ const currentQ = Array.from(controlGroup2.readQpos(data));
3998
4166
  const solution = config.ikSolveFn ? config.ikSolveFn({
3999
4167
  position: target.position,
4000
4168
  quaternion: target.quaternion,
@@ -4003,7 +4171,7 @@ var useIkController = createControllerHook(
4003
4171
  model,
4004
4172
  data,
4005
4173
  siteId: siteIdRef.current,
4006
- controlGroup
4174
+ controlGroup: controlGroup2
4007
4175
  }
4008
4176
  }) : ikSolveFnRef.current({
4009
4177
  position: target.position,
@@ -4011,7 +4179,7 @@ var useIkController = createControllerHook(
4011
4179
  currentQ
4012
4180
  });
4013
4181
  if (solution) {
4014
- controlGroup.writeCtrl(data, solution);
4182
+ controlGroup2.writeCtrl(data, solution);
4015
4183
  }
4016
4184
  });
4017
4185
  useEffect(() => {
@@ -4058,7 +4226,7 @@ var useIkController = createControllerHook(
4058
4226
  if (!ikEnabledRef.current) setIkEnabled(true);
4059
4227
  const target = ikTargetRef.current;
4060
4228
  if (!target) return;
4061
- const targetPos = pos.clone();
4229
+ const targetPos = toIkVector3(pos);
4062
4230
  const targetRot = new THREE11.Quaternion().setFromEuler(
4063
4231
  new THREE11.Euler(Math.PI, 0, 0)
4064
4232
  );
@@ -4848,6 +5016,69 @@ function parseNumberList(value) {
4848
5016
  function addProxyVectors(a, b) {
4849
5017
  return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
4850
5018
  }
5019
+ function streamSignature(options) {
5020
+ return JSON.stringify({
5021
+ cameraName: options.cameraName,
5022
+ siteName: options.siteName,
5023
+ bodyName: options.bodyName,
5024
+ width: options.width,
5025
+ height: options.height,
5026
+ renderIsolation: options.renderIsolation ?? false
5027
+ });
5028
+ }
5029
+ function useCameraStream(canvasRef, options) {
5030
+ const mujoco = useMujoco();
5031
+ const sessionRef = useRef(null);
5032
+ const signatureRef = useRef("");
5033
+ const optionsRef = useRef(options);
5034
+ optionsRef.current = options;
5035
+ const elapsedRef = useRef(0);
5036
+ const inFlightRef = useRef(false);
5037
+ const mountedRef = useRef(true);
5038
+ useEffect(() => {
5039
+ mountedRef.current = true;
5040
+ return () => {
5041
+ mountedRef.current = false;
5042
+ sessionRef.current?.dispose();
5043
+ sessionRef.current = null;
5044
+ signatureRef.current = "";
5045
+ };
5046
+ }, []);
5047
+ useFrame((_state, delta) => {
5048
+ const api = mujoco.api;
5049
+ if (!api || !canvasRef.current) return;
5050
+ const opts = optionsRef.current;
5051
+ if (opts.paused) return;
5052
+ if (opts.fps && opts.fps > 0) {
5053
+ elapsedRef.current += delta;
5054
+ if (elapsedRef.current < 1 / opts.fps) return;
5055
+ }
5056
+ if (inFlightRef.current) return;
5057
+ elapsedRef.current = 0;
5058
+ const signature = streamSignature(opts);
5059
+ if (!sessionRef.current || signatureRef.current !== signature) {
5060
+ sessionRef.current?.dispose();
5061
+ sessionRef.current = api.createCameraFrameCaptureSession(opts);
5062
+ signatureRef.current = signature;
5063
+ }
5064
+ const session = sessionRef.current;
5065
+ inFlightRef.current = true;
5066
+ session.captureAsync(api.resolveCameraCaptureOptions(opts)).then((frame) => {
5067
+ const canvas = canvasRef.current;
5068
+ if (!mountedRef.current || !canvas) return;
5069
+ const ctx = canvas.getContext("2d");
5070
+ if (!ctx) return;
5071
+ if (canvas.width !== frame.width || canvas.height !== frame.height) {
5072
+ canvas.width = frame.width;
5073
+ canvas.height = frame.height;
5074
+ }
5075
+ ctx.drawImage(frame.canvas, 0, 0);
5076
+ }).catch(() => {
5077
+ }).finally(() => {
5078
+ inFlightRef.current = false;
5079
+ });
5080
+ });
5081
+ }
4851
5082
  var JOINT_COLORS = {
4852
5083
  0: 16711680,
4853
5084
  // free - red
@@ -6526,6 +6757,73 @@ function useControlWriter(options) {
6526
6757
  release
6527
6758
  }), [canWrite, owner, read, release, write]);
6528
6759
  }
6760
+
6761
+ // src/hooks/useControlGroup.ts
6762
+ function controlGroup(names) {
6763
+ return { keys: names };
6764
+ }
6765
+ function useControlGroup(group, options = {}) {
6766
+ const generatedOwner = useId();
6767
+ const owner = options.owner ?? `control-group:${generatedOwner}`;
6768
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
6769
+ const names = group.keys;
6770
+ const namesRef = useRef(names);
6771
+ namesRef.current = names;
6772
+ const namesKey = names.join("\0");
6773
+ const selector = useMemo(
6774
+ () => ({ actuators: [...names] }),
6775
+ // eslint-disable-next-line react-hooks/exhaustive-deps
6776
+ [namesKey]
6777
+ );
6778
+ const { canWrite, release } = useControlWriter({ ...options, owner, selector });
6779
+ const indicesRef = useRef([]);
6780
+ useEffect(() => {
6781
+ const model = mjModelRef.current;
6782
+ if (!model || status !== "ready") {
6783
+ indicesRef.current = [];
6784
+ return;
6785
+ }
6786
+ indicesRef.current = namesRef.current.map((name) => {
6787
+ const id = findActuatorByName(model, name);
6788
+ if (id < 0) {
6789
+ console.warn(`[mujoco-react] useControlGroup: actuator "${name}" was not found.`);
6790
+ }
6791
+ return id;
6792
+ });
6793
+ }, [mjModelRef, status, namesKey]);
6794
+ const set = useCallback(
6795
+ (values, setOptions = {}) => {
6796
+ const data = mjDataRef.current;
6797
+ if (!data) return false;
6798
+ if (!setOptions.force && !canWrite()) return false;
6799
+ const groupNames = namesRef.current;
6800
+ const indices = indicesRef.current;
6801
+ for (let i = 0; i < groupNames.length; i += 1) {
6802
+ const value = values[groupNames[i]];
6803
+ if (value === void 0) continue;
6804
+ const adr = indices[i];
6805
+ if (adr >= 0) data.ctrl[adr] = value;
6806
+ }
6807
+ return true;
6808
+ },
6809
+ [canWrite, mjDataRef]
6810
+ );
6811
+ const read = useCallback(() => {
6812
+ const data = mjDataRef.current;
6813
+ const groupNames = namesRef.current;
6814
+ const indices = indicesRef.current;
6815
+ const result = {};
6816
+ for (let i = 0; i < groupNames.length; i += 1) {
6817
+ const adr = indices[i];
6818
+ result[groupNames[i]] = data && adr >= 0 ? data.ctrl[adr] ?? 0 : 0;
6819
+ }
6820
+ return result;
6821
+ }, [mjDataRef]);
6822
+ return useMemo(
6823
+ () => ({ owner, set, read, canWrite, release }),
6824
+ [owner, set, read, canWrite, release]
6825
+ );
6826
+ }
6529
6827
  var geomNameCacheByModel2 = /* @__PURE__ */ new WeakMap();
6530
6828
  var bodyNameCacheByModel = /* @__PURE__ */ new WeakMap();
6531
6829
  function getCachedName(cacheByModel, model, id, address) {
@@ -7822,6 +8120,130 @@ function usePolicyCameraFramesFromMountedStreams(defaultOptions) {
7822
8120
  reset
7823
8121
  };
7824
8122
  }
8123
+ function sessionSignature(stream) {
8124
+ return JSON.stringify({
8125
+ width: stream.width,
8126
+ height: stream.height,
8127
+ channels: stream.channels,
8128
+ renderIsolation: stream.renderIsolation ?? false,
8129
+ cameraName: stream.cameraName,
8130
+ siteName: stream.siteName,
8131
+ bodyName: stream.bodyName
8132
+ });
8133
+ }
8134
+ function addTensorAliases(tensors, stream, tensor, includeObservationImageAliases) {
8135
+ const keys = /* @__PURE__ */ new Set([stream.key, ...stream.aliases ?? []]);
8136
+ if (includeObservationImageAliases) {
8137
+ for (const base of [stream.key, ...stream.aliases ?? []]) {
8138
+ keys.add(`observation.images.${base}`);
8139
+ }
8140
+ }
8141
+ for (const key of keys) tensors[key] = tensor;
8142
+ }
8143
+ function usePolicyCameraTensors(options) {
8144
+ const mujoco = useMujoco();
8145
+ const [status, setStatus] = useState("idle");
8146
+ const [error, setError] = useState(null);
8147
+ const sessionsRef = useRef(/* @__PURE__ */ new Map());
8148
+ const disposeSessions = useCallback(() => {
8149
+ for (const { session } of sessionsRef.current.values()) session.dispose();
8150
+ sessionsRef.current.clear();
8151
+ }, []);
8152
+ useEffect(() => disposeSessions, [disposeSessions]);
8153
+ const reset = useCallback(() => {
8154
+ setStatus("idle");
8155
+ setError(null);
8156
+ }, []);
8157
+ const capture = useCallback(() => {
8158
+ const api = mujoco.api;
8159
+ if (!api) {
8160
+ throw new Error("MuJoCo scene is not ready for policy camera tensor capture.");
8161
+ }
8162
+ setStatus("capturing");
8163
+ setError(null);
8164
+ try {
8165
+ const sessions = sessionsRef.current;
8166
+ const seen = /* @__PURE__ */ new Set();
8167
+ const tensors = {};
8168
+ const sourceParts = [];
8169
+ for (const stream of options.streams) {
8170
+ seen.add(stream.key);
8171
+ const resolved = {
8172
+ ...api.resolveCameraCaptureOptions(stream),
8173
+ channels: stream.channels,
8174
+ layout: stream.layout,
8175
+ range: stream.range
8176
+ };
8177
+ const signature = sessionSignature(stream);
8178
+ let entry = sessions.get(stream.key);
8179
+ if (!entry || entry.signature !== signature) {
8180
+ entry?.session.dispose();
8181
+ entry = {
8182
+ session: api.createCameraFrameCaptureSession(resolved),
8183
+ signature
8184
+ };
8185
+ sessions.set(stream.key, entry);
8186
+ }
8187
+ const tensor = entry.session.captureTensor(resolved);
8188
+ addTensorAliases(
8189
+ tensors,
8190
+ stream,
8191
+ tensor,
8192
+ options.includeObservationImageAliases ?? false
8193
+ );
8194
+ sourceParts.push(`${stream.key}:${tensor.source.kind}`);
8195
+ }
8196
+ for (const key of [...sessions.keys()]) {
8197
+ if (!seen.has(key)) {
8198
+ sessions.get(key)?.session.dispose();
8199
+ sessions.delete(key);
8200
+ }
8201
+ }
8202
+ setStatus("captured");
8203
+ return {
8204
+ tensors,
8205
+ sourceSummary: sourceParts.join(" + ") || "not used by policy",
8206
+ capturedAt: Date.now()
8207
+ };
8208
+ } catch (nextError) {
8209
+ const captureError = nextError instanceof Error ? nextError : new Error("Unable to capture policy camera tensors.");
8210
+ setError(captureError);
8211
+ setStatus("error");
8212
+ throw captureError;
8213
+ }
8214
+ }, [mujoco.api, options.includeObservationImageAliases, options.streams]);
8215
+ return {
8216
+ status,
8217
+ error,
8218
+ isCapturing: status === "capturing",
8219
+ capture,
8220
+ reset
8221
+ };
8222
+ }
8223
+ function usePolicyCameraTensorsFromMountedStreams(options) {
8224
+ const mujoco = useMujoco();
8225
+ const tensorOptions = options.tensor;
8226
+ const mountedOptions = useMemo(() => {
8227
+ const api = mujoco.api;
8228
+ if (!api) {
8229
+ return {
8230
+ streams: [],
8231
+ includeObservationImageAliases: options.includeObservationImageAliases ?? false
8232
+ };
8233
+ }
8234
+ const plan = createPolicyCameraFrameCapturePlanFromApi(api, options);
8235
+ return {
8236
+ streams: plan.streams.map(({ key, aliases, ...stream }) => ({
8237
+ ...stream,
8238
+ ...tensorOptions,
8239
+ key,
8240
+ aliases
8241
+ })),
8242
+ includeObservationImageAliases: plan.includeObservationImageAliases ?? false
8243
+ };
8244
+ }, [mujoco.api, options, tensorOptions]);
8245
+ return usePolicyCameraTensors(mountedOptions);
8246
+ }
7825
8247
  function useCameraSequenceRecorder() {
7826
8248
  const mujoco = useMujoco();
7827
8249
  const [status, setStatus] = useState("idle");
@@ -8126,6 +8548,19 @@ function useCameraAnimation() {
8126
8548
  * @license
8127
8549
  * SPDX-License-Identifier: Apache-2.0
8128
8550
  */
8551
+ /**
8552
+ * @license
8553
+ * SPDX-License-Identifier: Apache-2.0
8554
+ *
8555
+ * Live on-screen camera viewports for MuJoCo scenes. Each view renders the
8556
+ * shared scene from a named MuJoCo camera/site/body into a `gl.scissor` region
8557
+ * tracking a DOM element — no GPU readback, no PNG encoding.
8558
+ *
8559
+ * While at least one viewport is mounted the canvas switches from R3F's
8560
+ * automatic render to a managed render loop (main scene full-frame, then each
8561
+ * viewport). This is incompatible with `EffectComposer`/postprocessing or other
8562
+ * custom render loops; use the offscreen capture APIs in those setups instead.
8563
+ */
8129
8564
  /**
8130
8565
  * @license
8131
8566
  * SPDX-License-Identifier: Apache-2.0
@@ -8174,6 +8609,21 @@ function useCameraAnimation() {
8174
8609
  * light_type: 0 = directional, 1 = spot (maps to mjLIGHT_DIRECTIONAL/mjLIGHT_SPOT)
8175
8610
  * Note: light_directional does NOT exist in WASM — use light_type instead.
8176
8611
  */
8612
+ /**
8613
+ * @license
8614
+ * SPDX-License-Identifier: Apache-2.0
8615
+ *
8616
+ * Stream a live MuJoCo camera into a DOM `<canvas>`. Each frame the scene is
8617
+ * rendered offscreen from the selected camera and blitted into the canvas, so
8618
+ * it composites normally in the DOM (works inside opaque panels) and does NOT
8619
+ * take over the render loop. Prefer this over `useCameraViewport` for camera
8620
+ * tiles embedded in HTML UI; use `useCameraViewport` for transparent overlays
8621
+ * on a full-bleed canvas.
8622
+ *
8623
+ * Uses the async capture path so Gaussian-splat environments render through
8624
+ * their dedicated capture renderer — streaming a splat scene at full rate does
8625
+ * not disturb the main view's splat sort.
8626
+ */
8177
8627
  /**
8178
8628
  * @license
8179
8629
  * SPDX-License-Identifier: Apache-2.0
@@ -8259,6 +8709,14 @@ function useCameraAnimation() {
8259
8709
  *
8260
8710
  * Cooperative actuator/control ownership for policies, IK, teleop, and replay.
8261
8711
  */
8712
+ /**
8713
+ * @license
8714
+ * SPDX-License-Identifier: Apache-2.0
8715
+ *
8716
+ * Typed, named control groups. Define the group once (like a zod schema or a
8717
+ * tanstack route), then read/set actuators by name with inferred keys — no
8718
+ * positional arrays, no `as const`, no owner bookkeeping.
8719
+ */
8262
8720
  /**
8263
8721
  * @license
8264
8722
  * SPDX-License-Identifier: Apache-2.0
@@ -8331,6 +8789,15 @@ function useCameraAnimation() {
8331
8789
  *
8332
8790
  * React wrapper for capturing policy image payloads from Three/MuJoCo cameras.
8333
8791
  */
8792
+ /**
8793
+ * @license
8794
+ * SPDX-License-Identifier: Apache-2.0
8795
+ *
8796
+ * Capture policy observation tensors directly from Three/MuJoCo cameras,
8797
+ * skipping the data-URL/PNG round-trip. Sessions are created once per camera
8798
+ * and reused every step, so live inference and dataset recording read straight
8799
+ * from the GPU into Float32 tensors.
8800
+ */
8334
8801
  /**
8335
8802
  * @license
8336
8803
  * SPDX-License-Identifier: Apache-2.0
@@ -8379,6 +8846,6 @@ function useCameraAnimation() {
8379
8846
  * useCameraAnimation — composable camera animation hook.
8380
8847
  */
8381
8848
 
8382
- export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MountedCameraFrameSequenceManifestStatus, MountedCameraFrameSequenceReadinessStatus, MountedCameraFrameSourceSuggestionMatch, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SplatCollisionProxyPreview, TendonRenderer, TrajectoryPlayer, applyPolicyActionToControls, bodyPositionField, buildObservation, canFetchSplatCollisionProxyXml, captureFrame, captureFrameBlob, capturePolicyCameraFrames, capturePolicyCameraFramesFromMountedStreams, clampPolicyActionValue, createContiguousControlGroup, createController, createControllerHook, createMountedCameraFrameSequenceManifest, createMountedCameraFrameSequencePlan, createMountedCameraFrameSequencePlanFromApi, createMountedCameraFrameSequenceReadiness, createMountedCameraFrameSourceSuggestions, createNamedObservationBuilder, createPolicyCameraFrameCapturePlan, createPolicyCameraFrameCapturePlanFromApi, ctrlField, fetchSplatCollisionProxyXml, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, geomPositionField, getActuatedJoints, getCameraFrameCaptureSourceTarget, getControlMap, getMountedCameraFrameCaptureSource, getName, imagePointToNdc, isMountedCameraFrameCaptureSource, loadScene, parseSplatCollisionProxyGeoms, projectImagePointTo3D, qposField, qvelField, readNamedObservation, recordMountedCameraFrameSequence, resolveControlGroup, resolveMountedCameraFrameSource, sitePositionField, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyPose, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useContactEvents, useContactHistory, useContacts, useControlWriter, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGeomPose, useGravityCompensation, useIkController, useJointState, useKeyboardIkTarget, useKeyboardTeleop, useMountedCameraSequenceRecorder, useMujoco, useMujocoWasm, useNamedObservation, useObservation, usePolicy, usePolicyCameraFrames, usePolicyCameraFramesFromMountedStreams, useRemotePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePose, useSitePosition, useSplatCollisionProxyGeoms, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
8849
+ export { Body, CameraView, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, GenericIK, IkGizmo, InstancedGeomRenderer, MountedCameraFrameSequenceManifestStatus, MountedCameraFrameSequenceReadinessStatus, MountedCameraFrameSourceSuggestionMatch, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SplatCollisionProxyPreview, TendonRenderer, TrajectoryPlayer, applyPolicyActionToControls, bodyPositionField, buildObservation, canFetchSplatCollisionProxyXml, captureFrame, captureFrameBlob, capturePolicyCameraFrames, capturePolicyCameraFramesFromMountedStreams, clampPolicyActionValue, controlGroup, createContiguousControlGroup, createController, createControllerHook, createMountedCameraFrameSequenceManifest, createMountedCameraFrameSequencePlan, createMountedCameraFrameSequencePlanFromApi, createMountedCameraFrameSequenceReadiness, createMountedCameraFrameSourceSuggestions, createNamedObservationBuilder, createPolicyCameraFrameCapturePlan, createPolicyCameraFrameCapturePlanFromApi, ctrlField, fetchSplatCollisionProxyXml, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, geomPositionField, getActuatedJoints, getCameraFrameCaptureSourceTarget, getControlMap, getMountedCameraFrameCaptureSource, getName, imagePointToNdc, isMountedCameraFrameCaptureSource, loadScene, parseSplatCollisionProxyGeoms, projectImagePointTo3D, qposField, qvelField, readNamedObservation, recordMountedCameraFrameSequence, resolveControlGroup, resolveMountedCameraFrameSource, sitePositionField, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyPose, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useCameraStream, useCameraViewport, useContactEvents, useContactHistory, useContacts, useControlGroup, useControlWriter, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGeomPose, useGravityCompensation, useIkController, useJointState, useKeyboardIkTarget, useKeyboardTeleop, useMountedCameraSequenceRecorder, useMujoco, useMujocoWasm, useNamedObservation, useObservation, usePolicy, usePolicyCameraFrames, usePolicyCameraFramesFromMountedStreams, usePolicyCameraTensors, usePolicyCameraTensorsFromMountedStreams, useRemotePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePose, useSitePosition, useSplatCollisionProxyGeoms, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
8383
8850
  //# sourceMappingURL=index.js.map
8384
8851
  //# sourceMappingURL=index.js.map