mujoco-react 10.4.0 → 10.5.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-KHZ5U36J.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-KHZ5U36J.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,9 +3787,16 @@ 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
  }
3794
+ function clampJoint(value, limit) {
3795
+ if (!limit) return value;
3796
+ const [min, max] = limit;
3797
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min >= max) return value;
3798
+ return Math.max(min, Math.min(max, value));
3799
+ }
3647
3800
  var GenericIK = class {
3648
3801
  mujoco;
3649
3802
  constructor(mujoco) {
@@ -3668,7 +3821,7 @@ var GenericIK = class {
3668
3821
  savedQpos.set(data.qpos);
3669
3822
  const R_target = quatToMat3(targetQuat);
3670
3823
  const q = new Float64Array(n);
3671
- for (let i = 0; i < n; i++) q[i] = currentQ[i];
3824
+ for (let i = 0; i < n; i++) q[i] = clampJoint(currentQ[i], o.jointLimits?.[i]);
3672
3825
  const J = new Float64Array(6 * n);
3673
3826
  const JJt = new Float64Array(36);
3674
3827
  const rhs = new Float64Array(6);
@@ -3745,7 +3898,7 @@ var GenericIK = class {
3745
3898
  }
3746
3899
  dq[j] = sum;
3747
3900
  }
3748
- for (let i = 0; i < n; i++) q[i] += dq[i];
3901
+ for (let i = 0; i < n; i++) q[i] = clampJoint(q[i] + dq[i], o.jointLimits?.[i]);
3749
3902
  }
3750
3903
  data.qpos.set(savedQpos);
3751
3904
  this.mujoco.mj_forward(model, data);
@@ -3940,19 +4093,20 @@ var useIkController = createControllerHook(
3940
4093
  if (config.ikSolveFn) return config.ikSolveFn({ position, quaternion, currentQ, context });
3941
4094
  const model = mjModelRef.current;
3942
4095
  const data = mjDataRef.current;
3943
- const controlGroup = controlGroupRef.current;
3944
- if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
4096
+ const controlGroup2 = controlGroupRef.current;
4097
+ if (!model || !data || !controlGroup2 || siteIdRef.current === -1) return null;
3945
4098
  return genericIkRef.current.solve(
3946
4099
  model,
3947
4100
  data,
3948
4101
  siteIdRef.current,
3949
- controlGroup.qposAdr,
4102
+ controlGroup2.qposAdr,
3950
4103
  position,
3951
4104
  quaternion,
3952
4105
  currentQ,
3953
4106
  {
3954
4107
  damping: config.damping,
3955
4108
  epsilon: config.epsilon,
4109
+ jointLimits: config.jointLimits ?? controlGroup2.joints.map((joint) => joint.limited ? joint.range : joint.ctrlRange),
3956
4110
  maxIterations: config.maxIterations,
3957
4111
  posWeight: config.posWeight,
3958
4112
  rotWeight: config.rotWeight,
@@ -3992,9 +4146,9 @@ var useIkController = createControllerHook(
3992
4146
  const target = ikTargetRef.current;
3993
4147
  if (!target) return;
3994
4148
  ikCalculatingRef.current = true;
3995
- const controlGroup = controlGroupRef.current;
3996
- if (!controlGroup) return;
3997
- const currentQ = Array.from(controlGroup.readQpos(data));
4149
+ const controlGroup2 = controlGroupRef.current;
4150
+ if (!controlGroup2) return;
4151
+ const currentQ = Array.from(controlGroup2.readQpos(data));
3998
4152
  const solution = config.ikSolveFn ? config.ikSolveFn({
3999
4153
  position: target.position,
4000
4154
  quaternion: target.quaternion,
@@ -4003,7 +4157,7 @@ var useIkController = createControllerHook(
4003
4157
  model,
4004
4158
  data,
4005
4159
  siteId: siteIdRef.current,
4006
- controlGroup
4160
+ controlGroup: controlGroup2
4007
4161
  }
4008
4162
  }) : ikSolveFnRef.current({
4009
4163
  position: target.position,
@@ -4011,7 +4165,7 @@ var useIkController = createControllerHook(
4011
4165
  currentQ
4012
4166
  });
4013
4167
  if (solution) {
4014
- controlGroup.writeCtrl(data, solution);
4168
+ controlGroup2.writeCtrl(data, solution);
4015
4169
  }
4016
4170
  });
4017
4171
  useEffect(() => {
@@ -4848,6 +5002,69 @@ function parseNumberList(value) {
4848
5002
  function addProxyVectors(a, b) {
4849
5003
  return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
4850
5004
  }
5005
+ function streamSignature(options) {
5006
+ return JSON.stringify({
5007
+ cameraName: options.cameraName,
5008
+ siteName: options.siteName,
5009
+ bodyName: options.bodyName,
5010
+ width: options.width,
5011
+ height: options.height,
5012
+ renderIsolation: options.renderIsolation ?? false
5013
+ });
5014
+ }
5015
+ function useCameraStream(canvasRef, options) {
5016
+ const mujoco = useMujoco();
5017
+ const sessionRef = useRef(null);
5018
+ const signatureRef = useRef("");
5019
+ const optionsRef = useRef(options);
5020
+ optionsRef.current = options;
5021
+ const elapsedRef = useRef(0);
5022
+ const inFlightRef = useRef(false);
5023
+ const mountedRef = useRef(true);
5024
+ useEffect(() => {
5025
+ mountedRef.current = true;
5026
+ return () => {
5027
+ mountedRef.current = false;
5028
+ sessionRef.current?.dispose();
5029
+ sessionRef.current = null;
5030
+ signatureRef.current = "";
5031
+ };
5032
+ }, []);
5033
+ useFrame((_state, delta) => {
5034
+ const api = mujoco.api;
5035
+ if (!api || !canvasRef.current) return;
5036
+ const opts = optionsRef.current;
5037
+ if (opts.paused) return;
5038
+ if (opts.fps && opts.fps > 0) {
5039
+ elapsedRef.current += delta;
5040
+ if (elapsedRef.current < 1 / opts.fps) return;
5041
+ }
5042
+ if (inFlightRef.current) return;
5043
+ elapsedRef.current = 0;
5044
+ const signature = streamSignature(opts);
5045
+ if (!sessionRef.current || signatureRef.current !== signature) {
5046
+ sessionRef.current?.dispose();
5047
+ sessionRef.current = api.createCameraFrameCaptureSession(opts);
5048
+ signatureRef.current = signature;
5049
+ }
5050
+ const session = sessionRef.current;
5051
+ inFlightRef.current = true;
5052
+ session.captureAsync(api.resolveCameraCaptureOptions(opts)).then((frame) => {
5053
+ const canvas = canvasRef.current;
5054
+ if (!mountedRef.current || !canvas) return;
5055
+ const ctx = canvas.getContext("2d");
5056
+ if (!ctx) return;
5057
+ if (canvas.width !== frame.width || canvas.height !== frame.height) {
5058
+ canvas.width = frame.width;
5059
+ canvas.height = frame.height;
5060
+ }
5061
+ ctx.drawImage(frame.canvas, 0, 0);
5062
+ }).catch(() => {
5063
+ }).finally(() => {
5064
+ inFlightRef.current = false;
5065
+ });
5066
+ });
5067
+ }
4851
5068
  var JOINT_COLORS = {
4852
5069
  0: 16711680,
4853
5070
  // free - red
@@ -6526,6 +6743,73 @@ function useControlWriter(options) {
6526
6743
  release
6527
6744
  }), [canWrite, owner, read, release, write]);
6528
6745
  }
6746
+
6747
+ // src/hooks/useControlGroup.ts
6748
+ function controlGroup(names) {
6749
+ return { keys: names };
6750
+ }
6751
+ function useControlGroup(group, options = {}) {
6752
+ const generatedOwner = useId();
6753
+ const owner = options.owner ?? `control-group:${generatedOwner}`;
6754
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
6755
+ const names = group.keys;
6756
+ const namesRef = useRef(names);
6757
+ namesRef.current = names;
6758
+ const namesKey = names.join("\0");
6759
+ const selector = useMemo(
6760
+ () => ({ actuators: [...names] }),
6761
+ // eslint-disable-next-line react-hooks/exhaustive-deps
6762
+ [namesKey]
6763
+ );
6764
+ const { canWrite, release } = useControlWriter({ ...options, owner, selector });
6765
+ const indicesRef = useRef([]);
6766
+ useEffect(() => {
6767
+ const model = mjModelRef.current;
6768
+ if (!model || status !== "ready") {
6769
+ indicesRef.current = [];
6770
+ return;
6771
+ }
6772
+ indicesRef.current = namesRef.current.map((name) => {
6773
+ const id = findActuatorByName(model, name);
6774
+ if (id < 0) {
6775
+ console.warn(`[mujoco-react] useControlGroup: actuator "${name}" was not found.`);
6776
+ }
6777
+ return id;
6778
+ });
6779
+ }, [mjModelRef, status, namesKey]);
6780
+ const set = useCallback(
6781
+ (values, setOptions = {}) => {
6782
+ const data = mjDataRef.current;
6783
+ if (!data) return false;
6784
+ if (!setOptions.force && !canWrite()) return false;
6785
+ const groupNames = namesRef.current;
6786
+ const indices = indicesRef.current;
6787
+ for (let i = 0; i < groupNames.length; i += 1) {
6788
+ const value = values[groupNames[i]];
6789
+ if (value === void 0) continue;
6790
+ const adr = indices[i];
6791
+ if (adr >= 0) data.ctrl[adr] = value;
6792
+ }
6793
+ return true;
6794
+ },
6795
+ [canWrite, mjDataRef]
6796
+ );
6797
+ const read = useCallback(() => {
6798
+ const data = mjDataRef.current;
6799
+ const groupNames = namesRef.current;
6800
+ const indices = indicesRef.current;
6801
+ const result = {};
6802
+ for (let i = 0; i < groupNames.length; i += 1) {
6803
+ const adr = indices[i];
6804
+ result[groupNames[i]] = data && adr >= 0 ? data.ctrl[adr] ?? 0 : 0;
6805
+ }
6806
+ return result;
6807
+ }, [mjDataRef]);
6808
+ return useMemo(
6809
+ () => ({ owner, set, read, canWrite, release }),
6810
+ [owner, set, read, canWrite, release]
6811
+ );
6812
+ }
6529
6813
  var geomNameCacheByModel2 = /* @__PURE__ */ new WeakMap();
6530
6814
  var bodyNameCacheByModel = /* @__PURE__ */ new WeakMap();
6531
6815
  function getCachedName(cacheByModel, model, id, address) {
@@ -7822,6 +8106,130 @@ function usePolicyCameraFramesFromMountedStreams(defaultOptions) {
7822
8106
  reset
7823
8107
  };
7824
8108
  }
8109
+ function sessionSignature(stream) {
8110
+ return JSON.stringify({
8111
+ width: stream.width,
8112
+ height: stream.height,
8113
+ channels: stream.channels,
8114
+ renderIsolation: stream.renderIsolation ?? false,
8115
+ cameraName: stream.cameraName,
8116
+ siteName: stream.siteName,
8117
+ bodyName: stream.bodyName
8118
+ });
8119
+ }
8120
+ function addTensorAliases(tensors, stream, tensor, includeObservationImageAliases) {
8121
+ const keys = /* @__PURE__ */ new Set([stream.key, ...stream.aliases ?? []]);
8122
+ if (includeObservationImageAliases) {
8123
+ for (const base of [stream.key, ...stream.aliases ?? []]) {
8124
+ keys.add(`observation.images.${base}`);
8125
+ }
8126
+ }
8127
+ for (const key of keys) tensors[key] = tensor;
8128
+ }
8129
+ function usePolicyCameraTensors(options) {
8130
+ const mujoco = useMujoco();
8131
+ const [status, setStatus] = useState("idle");
8132
+ const [error, setError] = useState(null);
8133
+ const sessionsRef = useRef(/* @__PURE__ */ new Map());
8134
+ const disposeSessions = useCallback(() => {
8135
+ for (const { session } of sessionsRef.current.values()) session.dispose();
8136
+ sessionsRef.current.clear();
8137
+ }, []);
8138
+ useEffect(() => disposeSessions, [disposeSessions]);
8139
+ const reset = useCallback(() => {
8140
+ setStatus("idle");
8141
+ setError(null);
8142
+ }, []);
8143
+ const capture = useCallback(() => {
8144
+ const api = mujoco.api;
8145
+ if (!api) {
8146
+ throw new Error("MuJoCo scene is not ready for policy camera tensor capture.");
8147
+ }
8148
+ setStatus("capturing");
8149
+ setError(null);
8150
+ try {
8151
+ const sessions = sessionsRef.current;
8152
+ const seen = /* @__PURE__ */ new Set();
8153
+ const tensors = {};
8154
+ const sourceParts = [];
8155
+ for (const stream of options.streams) {
8156
+ seen.add(stream.key);
8157
+ const resolved = {
8158
+ ...api.resolveCameraCaptureOptions(stream),
8159
+ channels: stream.channels,
8160
+ layout: stream.layout,
8161
+ range: stream.range
8162
+ };
8163
+ const signature = sessionSignature(stream);
8164
+ let entry = sessions.get(stream.key);
8165
+ if (!entry || entry.signature !== signature) {
8166
+ entry?.session.dispose();
8167
+ entry = {
8168
+ session: api.createCameraFrameCaptureSession(resolved),
8169
+ signature
8170
+ };
8171
+ sessions.set(stream.key, entry);
8172
+ }
8173
+ const tensor = entry.session.captureTensor(resolved);
8174
+ addTensorAliases(
8175
+ tensors,
8176
+ stream,
8177
+ tensor,
8178
+ options.includeObservationImageAliases ?? false
8179
+ );
8180
+ sourceParts.push(`${stream.key}:${tensor.source.kind}`);
8181
+ }
8182
+ for (const key of [...sessions.keys()]) {
8183
+ if (!seen.has(key)) {
8184
+ sessions.get(key)?.session.dispose();
8185
+ sessions.delete(key);
8186
+ }
8187
+ }
8188
+ setStatus("captured");
8189
+ return {
8190
+ tensors,
8191
+ sourceSummary: sourceParts.join(" + ") || "not used by policy",
8192
+ capturedAt: Date.now()
8193
+ };
8194
+ } catch (nextError) {
8195
+ const captureError = nextError instanceof Error ? nextError : new Error("Unable to capture policy camera tensors.");
8196
+ setError(captureError);
8197
+ setStatus("error");
8198
+ throw captureError;
8199
+ }
8200
+ }, [mujoco.api, options.includeObservationImageAliases, options.streams]);
8201
+ return {
8202
+ status,
8203
+ error,
8204
+ isCapturing: status === "capturing",
8205
+ capture,
8206
+ reset
8207
+ };
8208
+ }
8209
+ function usePolicyCameraTensorsFromMountedStreams(options) {
8210
+ const mujoco = useMujoco();
8211
+ const tensorOptions = options.tensor;
8212
+ const mountedOptions = useMemo(() => {
8213
+ const api = mujoco.api;
8214
+ if (!api) {
8215
+ return {
8216
+ streams: [],
8217
+ includeObservationImageAliases: options.includeObservationImageAliases ?? false
8218
+ };
8219
+ }
8220
+ const plan = createPolicyCameraFrameCapturePlanFromApi(api, options);
8221
+ return {
8222
+ streams: plan.streams.map(({ key, aliases, ...stream }) => ({
8223
+ ...stream,
8224
+ ...tensorOptions,
8225
+ key,
8226
+ aliases
8227
+ })),
8228
+ includeObservationImageAliases: plan.includeObservationImageAliases ?? false
8229
+ };
8230
+ }, [mujoco.api, options, tensorOptions]);
8231
+ return usePolicyCameraTensors(mountedOptions);
8232
+ }
7825
8233
  function useCameraSequenceRecorder() {
7826
8234
  const mujoco = useMujoco();
7827
8235
  const [status, setStatus] = useState("idle");
@@ -8126,6 +8534,19 @@ function useCameraAnimation() {
8126
8534
  * @license
8127
8535
  * SPDX-License-Identifier: Apache-2.0
8128
8536
  */
8537
+ /**
8538
+ * @license
8539
+ * SPDX-License-Identifier: Apache-2.0
8540
+ *
8541
+ * Live on-screen camera viewports for MuJoCo scenes. Each view renders the
8542
+ * shared scene from a named MuJoCo camera/site/body into a `gl.scissor` region
8543
+ * tracking a DOM element — no GPU readback, no PNG encoding.
8544
+ *
8545
+ * While at least one viewport is mounted the canvas switches from R3F's
8546
+ * automatic render to a managed render loop (main scene full-frame, then each
8547
+ * viewport). This is incompatible with `EffectComposer`/postprocessing or other
8548
+ * custom render loops; use the offscreen capture APIs in those setups instead.
8549
+ */
8129
8550
  /**
8130
8551
  * @license
8131
8552
  * SPDX-License-Identifier: Apache-2.0
@@ -8174,6 +8595,21 @@ function useCameraAnimation() {
8174
8595
  * light_type: 0 = directional, 1 = spot (maps to mjLIGHT_DIRECTIONAL/mjLIGHT_SPOT)
8175
8596
  * Note: light_directional does NOT exist in WASM — use light_type instead.
8176
8597
  */
8598
+ /**
8599
+ * @license
8600
+ * SPDX-License-Identifier: Apache-2.0
8601
+ *
8602
+ * Stream a live MuJoCo camera into a DOM `<canvas>`. Each frame the scene is
8603
+ * rendered offscreen from the selected camera and blitted into the canvas, so
8604
+ * it composites normally in the DOM (works inside opaque panels) and does NOT
8605
+ * take over the render loop. Prefer this over `useCameraViewport` for camera
8606
+ * tiles embedded in HTML UI; use `useCameraViewport` for transparent overlays
8607
+ * on a full-bleed canvas.
8608
+ *
8609
+ * Uses the async capture path so Gaussian-splat environments render through
8610
+ * their dedicated capture renderer — streaming a splat scene at full rate does
8611
+ * not disturb the main view's splat sort.
8612
+ */
8177
8613
  /**
8178
8614
  * @license
8179
8615
  * SPDX-License-Identifier: Apache-2.0
@@ -8259,6 +8695,14 @@ function useCameraAnimation() {
8259
8695
  *
8260
8696
  * Cooperative actuator/control ownership for policies, IK, teleop, and replay.
8261
8697
  */
8698
+ /**
8699
+ * @license
8700
+ * SPDX-License-Identifier: Apache-2.0
8701
+ *
8702
+ * Typed, named control groups. Define the group once (like a zod schema or a
8703
+ * tanstack route), then read/set actuators by name with inferred keys — no
8704
+ * positional arrays, no `as const`, no owner bookkeeping.
8705
+ */
8262
8706
  /**
8263
8707
  * @license
8264
8708
  * SPDX-License-Identifier: Apache-2.0
@@ -8331,6 +8775,15 @@ function useCameraAnimation() {
8331
8775
  *
8332
8776
  * React wrapper for capturing policy image payloads from Three/MuJoCo cameras.
8333
8777
  */
8778
+ /**
8779
+ * @license
8780
+ * SPDX-License-Identifier: Apache-2.0
8781
+ *
8782
+ * Capture policy observation tensors directly from Three/MuJoCo cameras,
8783
+ * skipping the data-URL/PNG round-trip. Sessions are created once per camera
8784
+ * and reused every step, so live inference and dataset recording read straight
8785
+ * from the GPU into Float32 tensors.
8786
+ */
8334
8787
  /**
8335
8788
  * @license
8336
8789
  * SPDX-License-Identifier: Apache-2.0
@@ -8379,6 +8832,6 @@ function useCameraAnimation() {
8379
8832
  * useCameraAnimation — composable camera animation hook.
8380
8833
  */
8381
8834
 
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 };
8835
+ 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
8836
  //# sourceMappingURL=index.js.map
8384
8837
  //# sourceMappingURL=index.js.map