mujoco-react 10.0.1 → 10.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/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAPTURE_EXCLUDE_KEY } from './chunk-QTCAVQS6.js';
2
- export { 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-QTCAVQS6.js';
1
+ import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY } from './chunk-3BMNRSS2.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-3BMNRSS2.js';
3
3
  import loadMujoco from '@mujoco/mujoco';
4
4
  import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
5
5
  import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
@@ -122,231 +122,6 @@ var CapsuleGeometry = class extends THREE11.BufferGeometry {
122
122
  self.setAttribute("uv", latheGeometry.getAttribute("uv"));
123
123
  }
124
124
  };
125
- var Reflector = class extends THREE11.Mesh {
126
- isReflector = true;
127
- camera;
128
- reflectorPlane = new THREE11.Plane();
129
- normal = new THREE11.Vector3();
130
- reflectorWorldPosition = new THREE11.Vector3();
131
- cameraWorldPosition = new THREE11.Vector3();
132
- rotationMatrix = new THREE11.Matrix4();
133
- lookAtPosition = new THREE11.Vector3(0, 0, -1);
134
- clipPlane = new THREE11.Vector4();
135
- view = new THREE11.Vector3();
136
- target = new THREE11.Vector3();
137
- q = new THREE11.Vector4();
138
- textureMatrix = new THREE11.Matrix4();
139
- virtualCamera;
140
- renderTarget;
141
- constructor(geometry, options = {}) {
142
- super(geometry);
143
- this.type = "Reflector";
144
- this.camera = new THREE11.PerspectiveCamera();
145
- const color = options.color !== void 0 ? new THREE11.Color(options.color) : new THREE11.Color(8355711);
146
- const textureWidth = options.textureWidth || 512;
147
- const textureHeight = options.textureHeight || 512;
148
- const clipBias = options.clipBias || 0;
149
- const multisample = options.multisample !== void 0 ? options.multisample : 4;
150
- const blendTexture = options.texture || void 0;
151
- const mixStrength = options.mixStrength !== void 0 ? options.mixStrength : 0.25;
152
- this.virtualCamera = this.camera;
153
- this.renderTarget = new THREE11.WebGLRenderTarget(textureWidth, textureHeight, {
154
- samples: multisample,
155
- type: THREE11.HalfFloatType
156
- });
157
- this.material = new THREE11.MeshPhysicalMaterial({
158
- map: blendTexture,
159
- color,
160
- roughness: 0.5,
161
- metalness: 0.1
162
- });
163
- this.material.onBeforeCompile = (shader) => {
164
- shader.uniforms.tDiffuse = { value: this.renderTarget.texture };
165
- shader.uniforms.textureMatrix = { value: this.textureMatrix };
166
- shader.uniforms.mixStrength = { value: mixStrength };
167
- const bodyStart = shader.vertexShader.indexOf("void main() {");
168
- shader.vertexShader = "uniform mat4 textureMatrix;\nvarying vec4 vUvReflection;\n" + shader.vertexShader.slice(0, bodyStart) + shader.vertexShader.slice(bodyStart, -1) + " vUvReflection = textureMatrix * vec4( position, 1.0 );\n}";
169
- const fragmentBodyStart = shader.fragmentShader.indexOf("void main() {");
170
- shader.fragmentShader = "uniform sampler2D tDiffuse;\nuniform float mixStrength;\nvarying vec4 vUvReflection;\n" + shader.fragmentShader.slice(0, fragmentBodyStart) + shader.fragmentShader.slice(fragmentBodyStart, -1) + " vec4 reflectionColor = texture2DProj( tDiffuse, vUvReflection );\n gl_FragColor = vec4( mix( gl_FragColor.rgb, reflectionColor.rgb, mixStrength ), gl_FragColor.a );\n}";
171
- };
172
- this.receiveShadow = true;
173
- this.onBeforeRender = (renderer, scene, camera) => {
174
- this.reflectorWorldPosition.setFromMatrixPosition(this.matrixWorld);
175
- this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);
176
- this.rotationMatrix.extractRotation(this.matrixWorld);
177
- this.normal.set(0, 0, 1);
178
- this.normal.applyMatrix4(this.rotationMatrix);
179
- this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition);
180
- if (this.view.dot(this.normal) > 0) return;
181
- this.view.reflect(this.normal).negate();
182
- this.view.add(this.reflectorWorldPosition);
183
- this.rotationMatrix.extractRotation(camera.matrixWorld);
184
- this.lookAtPosition.set(0, 0, -1);
185
- this.lookAtPosition.applyMatrix4(this.rotationMatrix);
186
- this.lookAtPosition.add(this.cameraWorldPosition);
187
- this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition);
188
- this.target.reflect(this.normal).negate();
189
- this.target.add(this.reflectorWorldPosition);
190
- this.virtualCamera.position.copy(this.view);
191
- this.virtualCamera.up.set(0, 1, 0);
192
- this.virtualCamera.up.applyMatrix4(this.rotationMatrix);
193
- this.virtualCamera.up.reflect(this.normal);
194
- this.virtualCamera.lookAt(this.target);
195
- this.virtualCamera.far = camera.far;
196
- this.virtualCamera.updateMatrixWorld();
197
- this.virtualCamera.projectionMatrix.copy(camera.projectionMatrix);
198
- this.textureMatrix.set(
199
- 0.5,
200
- 0,
201
- 0,
202
- 0.5,
203
- 0,
204
- 0.5,
205
- 0,
206
- 0.5,
207
- 0,
208
- 0,
209
- 0.5,
210
- 0.5,
211
- 0,
212
- 0,
213
- 0,
214
- 1
215
- );
216
- this.textureMatrix.multiply(this.virtualCamera.projectionMatrix);
217
- this.textureMatrix.multiply(this.virtualCamera.matrixWorldInverse);
218
- this.textureMatrix.multiply(this.matrixWorld);
219
- this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition);
220
- this.reflectorPlane.applyMatrix4(this.virtualCamera.matrixWorldInverse);
221
- this.clipPlane.set(this.reflectorPlane.normal.x, this.reflectorPlane.normal.y, this.reflectorPlane.normal.z, this.reflectorPlane.constant);
222
- const projectionMatrix = this.virtualCamera.projectionMatrix;
223
- this.q.x = (Math.sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
224
- this.q.y = (Math.sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
225
- this.q.z = -1;
226
- this.q.w = (1 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];
227
- this.clipPlane.multiplyScalar(2 / this.clipPlane.dot(this.q));
228
- projectionMatrix.elements[2] = this.clipPlane.x;
229
- projectionMatrix.elements[6] = this.clipPlane.y;
230
- projectionMatrix.elements[10] = this.clipPlane.z + 1 - clipBias;
231
- projectionMatrix.elements[14] = this.clipPlane.w;
232
- this.visible = false;
233
- const currentRenderTarget = renderer.getRenderTarget();
234
- const currentXrEnabled = renderer.xr.enabled;
235
- const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
236
- renderer.xr.enabled = false;
237
- renderer.shadowMap.autoUpdate = false;
238
- renderer.setRenderTarget(this.renderTarget);
239
- renderer.state.buffers.depth.setMask(true);
240
- if (renderer.autoClear === false) renderer.clear();
241
- renderer.render(scene, this.virtualCamera);
242
- renderer.xr.enabled = currentXrEnabled;
243
- renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
244
- renderer.setRenderTarget(currentRenderTarget);
245
- const viewport = camera.viewport;
246
- if (viewport !== void 0) {
247
- renderer.state.viewport(viewport);
248
- }
249
- this.visible = true;
250
- };
251
- }
252
- getRenderTarget() {
253
- return this.renderTarget;
254
- }
255
- dispose() {
256
- this.renderTarget.dispose();
257
- const mesh = this;
258
- if (Array.isArray(mesh.material)) {
259
- mesh.material.forEach((m) => m.dispose());
260
- } else {
261
- mesh.material.dispose();
262
- }
263
- }
264
- };
265
-
266
- // src/rendering/GeomBuilder.ts
267
- var GeomBuilder = class {
268
- mujoco;
269
- constructor(mujoco) {
270
- this.mujoco = mujoco;
271
- }
272
- /**
273
- * Creates a Three.js Object3D (usually a Mesh) for a specific geometry in the MuJoCo model.
274
- * Returns null if the geometry shouldn't be rendered (e.g., invisible collision triggers).
275
- */
276
- create(mjModel, g) {
277
- if (mjModel.geom_group[g] === 3) return null;
278
- const type = mjModel.geom_type[g];
279
- const size = mjModel.geom_size.subarray(g * 3, g * 3 + 3);
280
- const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
281
- const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
282
- const matId = mjModel.geom_matid[g];
283
- const color = new THREE11.Color(16777215);
284
- let opacity = 1;
285
- if (matId >= 0) {
286
- const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
287
- color.setRGB(rgba[0], rgba[1], rgba[2]);
288
- opacity = rgba[3];
289
- } else {
290
- const rgba = mjModel.geom_rgba.subarray(g * 4, g * 4 + 4);
291
- color.setRGB(rgba[0], rgba[1], rgba[2]);
292
- opacity = rgba[3];
293
- }
294
- const MG = this.mujoco.mjtGeom;
295
- let geo = null;
296
- const getVal = (v) => v?.value ?? v;
297
- if (type === getVal(MG.mjGEOM_PLANE)) {
298
- geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
299
- } else if (type === getVal(MG.mjGEOM_SPHERE)) {
300
- geo = new THREE11.SphereGeometry(size[0], 24, 24);
301
- } else if (type === getVal(MG.mjGEOM_CAPSULE)) {
302
- geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
303
- geo.rotateX(Math.PI / 2);
304
- } else if (type === getVal(MG.mjGEOM_BOX)) {
305
- geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
306
- } else if (type === getVal(MG.mjGEOM_CYLINDER)) {
307
- geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
308
- geo.rotateX(Math.PI / 2);
309
- } else if (type === getVal(MG.mjGEOM_MESH)) {
310
- const mId = mjModel.geom_dataid[g];
311
- const vAdr = mjModel.mesh_vertadr[mId];
312
- const vNum = mjModel.mesh_vertnum[mId];
313
- const fAdr = mjModel.mesh_faceadr[mId];
314
- const fNum = mjModel.mesh_facenum[mId];
315
- geo = new THREE11.BufferGeometry();
316
- geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
317
- geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
318
- geo.computeVertexNormals();
319
- }
320
- if (geo) {
321
- let mesh;
322
- if (type === getVal(MG.mjGEOM_PLANE)) {
323
- mesh = new Reflector(geo, {
324
- clipBias: 3e-3,
325
- textureWidth: 1024,
326
- textureHeight: 1024,
327
- color,
328
- mixStrength: 0.25
329
- });
330
- } else {
331
- mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
332
- color,
333
- transparent: opacity < 1,
334
- opacity,
335
- roughness: 0.6,
336
- metalness: 0.2
337
- }));
338
- mesh.castShadow = true;
339
- mesh.receiveShadow = true;
340
- }
341
- mesh.position.set(pos[0], pos[1], pos[2]);
342
- mesh.quaternion.set(quat[1], quat[2], quat[3], quat[0]);
343
- mesh.userData.bodyID = mjModel.geom_bodyid[g];
344
- mesh.userData.geomID = g;
345
- return mesh;
346
- }
347
- return null;
348
- }
349
- };
350
125
 
351
126
  // src/core/SceneLoader.ts
352
127
  var JOINT_TYPE_NAMES = {
@@ -704,8 +479,10 @@ function createContiguousControlGroup(mjModel, count) {
704
479
  }
705
480
  function sceneObjectToXml(obj) {
706
481
  const joint = obj.freejoint ? "<freejoint/>" : "";
482
+ const geomName = obj.geomName ?? `${obj.name}_geom`;
707
483
  const pos = obj.position.map((v) => v.toFixed(3)).join(" ");
708
- const size = obj.size.map((v) => v.toFixed(3)).join(" ");
484
+ const sizeValues = obj.type === "sphere" ? obj.size.slice(0, 1) : obj.type === "cylinder" ? obj.size.slice(0, 2) : obj.size;
485
+ const size = sizeValues.map((v) => v.toFixed(3)).join(" ");
709
486
  const rgba = obj.rgba.join(" ");
710
487
  const mass = obj.mass ? ` mass="${obj.mass}"` : "";
711
488
  const friction = obj.friction ? ` friction="${obj.friction}"` : "";
@@ -713,7 +490,7 @@ function sceneObjectToXml(obj) {
713
490
  const solimp = obj.solimp ? ` solimp="${obj.solimp}"` : "";
714
491
  const condim = obj.condim ? ` condim="${obj.condim}"` : "";
715
492
  const group = obj.group !== void 0 ? ` group="${obj.group}"` : "";
716
- return `<body name="${obj.name}" pos="${pos}">${joint}<geom type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
493
+ 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>`;
717
494
  }
718
495
  function ensureDir(mujoco, fname) {
719
496
  const dirParts = fname.split("/");
@@ -1112,6 +889,137 @@ function collectDependencyPaths(xmlString, currentFile, parser) {
1112
889
  });
1113
890
  return paths;
1114
891
  }
892
+
893
+ // src/rendering/GeomBuilder.ts
894
+ var GeomBuilder = class {
895
+ mujoco;
896
+ textureCache = /* @__PURE__ */ new Map();
897
+ constructor(mujoco) {
898
+ this.mujoco = mujoco;
899
+ }
900
+ getMaterialTexture(mjModel, matId) {
901
+ if (matId < 0 || !mjModel.mat_texid || !mjModel.tex_data) return null;
902
+ const materialCount = Math.max(1, Math.floor(mjModel.mat_rgba.length / 4));
903
+ const textureRoles = Math.max(1, Math.floor(mjModel.mat_texid.length / materialCount));
904
+ let texId = -1;
905
+ for (let role = 0; role < textureRoles; role += 1) {
906
+ const candidate = mjModel.mat_texid[matId * textureRoles + role];
907
+ if (candidate >= 0) {
908
+ texId = candidate;
909
+ break;
910
+ }
911
+ }
912
+ if (texId < 0) return null;
913
+ const cached = this.textureCache.get(texId);
914
+ if (cached) return cached;
915
+ const width = Number(mjModel.tex_width[texId]);
916
+ const height = Number(mjModel.tex_height[texId]);
917
+ const channels = Number(mjModel.tex_nchannel[texId]);
918
+ const offset = Number(mjModel.tex_adr[texId]);
919
+ if (width <= 0 || height <= 0 || channels <= 0 || offset < 0) return null;
920
+ const source = mjModel.tex_data.subarray(offset, offset + width * height * channels);
921
+ const rgba = new Uint8Array(width * height * 4);
922
+ for (let i = 0, j = 0; i < width * height; i += 1, j += channels) {
923
+ const r = source[j] ?? 255;
924
+ const g = channels > 1 ? source[j + 1] : r;
925
+ const b = channels > 2 ? source[j + 2] : r;
926
+ const a = channels > 3 ? source[j + 3] : 255;
927
+ const out = i * 4;
928
+ rgba[out] = r;
929
+ rgba[out + 1] = g;
930
+ rgba[out + 2] = b;
931
+ rgba[out + 3] = a;
932
+ }
933
+ const texture = new THREE11.DataTexture(rgba, width, height, THREE11.RGBAFormat);
934
+ texture.colorSpace = THREE11.LinearSRGBColorSpace;
935
+ texture.wrapS = THREE11.RepeatWrapping;
936
+ texture.wrapT = THREE11.RepeatWrapping;
937
+ texture.flipY = true;
938
+ const repeatOffset = matId * 2;
939
+ const repeatS = mjModel.mat_texrepeat?.[repeatOffset] ?? 1;
940
+ const repeatT = mjModel.mat_texrepeat?.[repeatOffset + 1] ?? 1;
941
+ texture.repeat.set(repeatS || 1, repeatT || 1);
942
+ texture.needsUpdate = true;
943
+ this.textureCache.set(texId, texture);
944
+ return texture;
945
+ }
946
+ /**
947
+ * Creates a Three.js Object3D (usually a Mesh) for a specific geometry in the MuJoCo model.
948
+ * Returns null if the geometry shouldn't be rendered (e.g., invisible collision triggers).
949
+ */
950
+ create(mjModel, g) {
951
+ if (mjModel.geom_group[g] === 3) return null;
952
+ const type = mjModel.geom_type[g];
953
+ const size = mjModel.geom_size.subarray(g * 3, g * 3 + 3);
954
+ const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
955
+ const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
956
+ const matId = mjModel.geom_matid[g];
957
+ const color = new THREE11.Color(16777215);
958
+ const map = this.getMaterialTexture(mjModel, matId);
959
+ let opacity = 1;
960
+ if (matId >= 0) {
961
+ const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
962
+ color.setRGB(rgba[0], rgba[1], rgba[2]);
963
+ opacity = rgba[3];
964
+ } else {
965
+ const rgba = mjModel.geom_rgba.subarray(g * 4, g * 4 + 4);
966
+ color.setRGB(rgba[0], rgba[1], rgba[2]);
967
+ opacity = rgba[3];
968
+ }
969
+ const MG = this.mujoco.mjtGeom;
970
+ let geo = null;
971
+ const getVal = (v) => v?.value ?? v;
972
+ if (type === getVal(MG.mjGEOM_PLANE)) {
973
+ geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
974
+ } else if (type === getVal(MG.mjGEOM_SPHERE)) {
975
+ geo = new THREE11.SphereGeometry(size[0], 24, 24);
976
+ } else if (type === getVal(MG.mjGEOM_CAPSULE)) {
977
+ geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
978
+ geo.rotateX(Math.PI / 2);
979
+ } else if (type === getVal(MG.mjGEOM_BOX)) {
980
+ geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
981
+ } else if (type === getVal(MG.mjGEOM_CYLINDER)) {
982
+ geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
983
+ geo.rotateX(Math.PI / 2);
984
+ } else if (type === getVal(MG.mjGEOM_MESH)) {
985
+ const mId = mjModel.geom_dataid[g];
986
+ const vAdr = mjModel.mesh_vertadr[mId];
987
+ const vNum = mjModel.mesh_vertnum[mId];
988
+ const fAdr = mjModel.mesh_faceadr[mId];
989
+ const fNum = mjModel.mesh_facenum[mId];
990
+ geo = new THREE11.BufferGeometry();
991
+ geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
992
+ geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
993
+ geo.computeVertexNormals();
994
+ }
995
+ if (geo) {
996
+ const isPlane = type === getVal(MG.mjGEOM_PLANE);
997
+ const materialMap = isPlane && map ? map.clone() : map;
998
+ if (isPlane && materialMap) {
999
+ materialMap.repeat.multiplyScalar(2.5);
1000
+ materialMap.needsUpdate = true;
1001
+ }
1002
+ const mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
1003
+ color,
1004
+ map: materialMap,
1005
+ transparent: opacity < 1,
1006
+ opacity,
1007
+ roughness: 0.6,
1008
+ metalness: 0
1009
+ }));
1010
+ mesh.castShadow = type !== getVal(MG.mjGEOM_PLANE);
1011
+ mesh.receiveShadow = true;
1012
+ mesh.position.set(pos[0], pos[1], pos[2]);
1013
+ mesh.quaternion.set(quat[1], quat[2], quat[3], quat[0]);
1014
+ mesh.userData.bodyID = mjModel.geom_bodyid[g];
1015
+ mesh.userData.geomID = g;
1016
+ mesh.userData.geomGroup = mjModel.geom_group[g];
1017
+ mesh.userData.geomName = getName(mjModel, mjModel.name_geomadr[g]);
1018
+ return mesh;
1019
+ }
1020
+ return null;
1021
+ }
1022
+ };
1115
1023
  function SceneRenderer(props) {
1116
1024
  const {
1117
1025
  mjModelRef,
@@ -1158,7 +1066,7 @@ function SceneRenderer(props) {
1158
1066
  }
1159
1067
  bodyRefs.current = refs;
1160
1068
  }, [status, geomBuilder, mjModelRef]);
1161
- useFrame(() => {
1069
+ const syncBodiesToData = useCallback(() => {
1162
1070
  const data = mjDataRef.current;
1163
1071
  if (!data) return;
1164
1072
  const bodies = bodyRefs.current;
@@ -1203,12 +1111,17 @@ function SceneRenderer(props) {
1203
1111
  );
1204
1112
  }
1205
1113
  }
1206
- });
1114
+ }, [interpolateRef, interpolationStateRef, mjDataRef]);
1115
+ useFrame(syncBodiesToData);
1207
1116
  return /* @__PURE__ */ jsx(
1208
1117
  "group",
1209
1118
  {
1210
1119
  ...props,
1211
1120
  ref: groupRef,
1121
+ userData: {
1122
+ ...props.userData,
1123
+ [CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY]: syncBodiesToData
1124
+ },
1212
1125
  onDoubleClick: (e) => {
1213
1126
  if (typeof props.onDoubleClick === "function") props.onDoubleClick(e);
1214
1127
  e.stopPropagation();
@@ -1735,6 +1648,125 @@ async function recordMountedCameraFrameSequence(api, options) {
1735
1648
  readiness
1736
1649
  };
1737
1650
  }
1651
+ var _raycaster = new THREE11.Raycaster();
1652
+ var _ndc = new THREE11.Vector2();
1653
+ function toVector3(value, fallback) {
1654
+ if (!value) return fallback.clone();
1655
+ return value instanceof THREE11.Vector3 ? value.clone() : new THREE11.Vector3(value[0], value[1], value[2]);
1656
+ }
1657
+ function applyCameraPose(camera, options, fallbackCamera) {
1658
+ camera.position.copy(toVector3(options.position, fallbackCamera.position));
1659
+ camera.up.copy(toVector3(options.up, fallbackCamera.up));
1660
+ if (options.quaternion) {
1661
+ if (options.quaternion instanceof THREE11.Quaternion) {
1662
+ camera.quaternion.copy(options.quaternion);
1663
+ } else {
1664
+ camera.quaternion.set(
1665
+ options.quaternion[0],
1666
+ options.quaternion[1],
1667
+ options.quaternion[2],
1668
+ options.quaternion[3]
1669
+ );
1670
+ }
1671
+ } else if (options.lookAt) {
1672
+ camera.lookAt(toVector3(options.lookAt, new THREE11.Vector3()));
1673
+ } else {
1674
+ camera.quaternion.copy(fallbackCamera.quaternion);
1675
+ }
1676
+ camera.updateMatrixWorld();
1677
+ }
1678
+ function createProjectionCamera(fallbackCamera, options, width, height) {
1679
+ const camera = options.camera ? options.camera.clone() : fallbackCamera instanceof THREE11.PerspectiveCamera ? fallbackCamera.clone() : new THREE11.PerspectiveCamera(45, width / height, 0.01, 100);
1680
+ if (camera instanceof THREE11.PerspectiveCamera) {
1681
+ camera.aspect = width / height;
1682
+ camera.fov = options.fov ?? camera.fov;
1683
+ camera.near = options.near ?? camera.near;
1684
+ camera.far = options.far ?? camera.far;
1685
+ camera.updateProjectionMatrix();
1686
+ }
1687
+ applyCameraPose(camera, options, fallbackCamera);
1688
+ return camera;
1689
+ }
1690
+ function getProjectionSource(options) {
1691
+ if (options.source) return options.source;
1692
+ if (options.cameraName) return { kind: "mujoco-camera", cameraName: options.cameraName };
1693
+ if (options.siteName) return { kind: "mujoco-site", siteName: options.siteName };
1694
+ if (options.bodyName) return { kind: "mujoco-body", bodyName: options.bodyName };
1695
+ if (options.camera) return { kind: "custom-camera" };
1696
+ if (options.position || options.lookAt || options.quaternion) return { kind: "explicit-pose" };
1697
+ return { kind: "fallback-camera" };
1698
+ }
1699
+ function imageSize(renderer, options) {
1700
+ return [
1701
+ Math.max(1, Math.floor(options.imageWidth ?? options.width ?? renderer.domElement.width)),
1702
+ Math.max(1, Math.floor(options.imageHeight ?? options.height ?? renderer.domElement.height))
1703
+ ];
1704
+ }
1705
+ function imagePointToNdc(x, y, coordinateSpace = "normalized", width = 1, height = 1) {
1706
+ if (coordinateSpace === "ndc") return [x, y];
1707
+ if (coordinateSpace === "normalized-1000") {
1708
+ return [x / 1e3 * 2 - 1, 1 - y / 1e3 * 2];
1709
+ }
1710
+ if (coordinateSpace === "pixel") {
1711
+ return [x / width * 2 - 1, 1 - y / height * 2];
1712
+ }
1713
+ return [x * 2 - 1, 1 - y * 2];
1714
+ }
1715
+ function isProjectionCandidate(object, options) {
1716
+ if (!object.visible) return false;
1717
+ if (object.userData[CAPTURE_EXCLUDE_KEY]) return false;
1718
+ const geomGroup = object.userData.geomGroup;
1719
+ const geomName = object.userData.geomName;
1720
+ if (options.hiddenGeomNames && typeof geomName === "string" && options.hiddenGeomNames.includes(geomName)) {
1721
+ return false;
1722
+ }
1723
+ if (options.hiddenGeomGroups && typeof geomGroup === "number" && options.hiddenGeomGroups.includes(geomGroup)) {
1724
+ return false;
1725
+ }
1726
+ if (options.visibleGeomGroups && typeof geomGroup === "number" && !options.visibleGeomGroups.includes(geomGroup)) {
1727
+ return false;
1728
+ }
1729
+ return true;
1730
+ }
1731
+ function findBodyId(object) {
1732
+ let current = object;
1733
+ while (current && current.userData.bodyID === void 0 && current.parent) {
1734
+ current = current.parent;
1735
+ }
1736
+ return typeof current?.userData.bodyID === "number" ? current.userData.bodyID : -1;
1737
+ }
1738
+ function projectImagePointTo3D(renderer, scene, fallbackCamera, options) {
1739
+ const [width, height] = imageSize(renderer, options);
1740
+ const [ndcX, ndcY] = imagePointToNdc(
1741
+ options.x,
1742
+ options.y,
1743
+ options.coordinateSpace,
1744
+ width,
1745
+ height
1746
+ );
1747
+ const projectionCamera = createProjectionCamera(fallbackCamera, options, width, height);
1748
+ scene.updateMatrixWorld(true);
1749
+ _ndc.set(ndcX, ndcY);
1750
+ _raycaster.setFromCamera(_ndc, projectionCamera);
1751
+ _raycaster.far = options.maxDistance ?? Infinity;
1752
+ const objects = [];
1753
+ scene.traverse((object) => {
1754
+ if (object.isMesh && isProjectionCandidate(object, options)) {
1755
+ objects.push(object);
1756
+ }
1757
+ });
1758
+ const [hit] = _raycaster.intersectObjects(objects, true);
1759
+ if (!hit) return null;
1760
+ return {
1761
+ point: hit.point.clone(),
1762
+ bodyId: findBodyId(hit.object),
1763
+ geomId: typeof hit.object.userData.geomID === "number" ? hit.object.userData.geomID : -1,
1764
+ distance: hit.distance,
1765
+ ndc: [ndcX, ndcY],
1766
+ imageSize: [width, height],
1767
+ source: getProjectionSource(options)
1768
+ };
1769
+ }
1738
1770
  var JOINT_TYPE_NAMES2 = ["free", "ball", "slide", "hinge"];
1739
1771
  var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
1740
1772
  var SENSOR_TYPE_NAMES = {
@@ -1862,6 +1894,42 @@ function omitResolvedCameraSelectors(options) {
1862
1894
  const { cameraName, siteName, bodyName, ...rest } = options;
1863
1895
  return rest;
1864
1896
  }
1897
+ function vector3FromCaptureValue(value) {
1898
+ return value instanceof THREE11.Vector3 ? value.clone() : new THREE11.Vector3(value[0], value[1], value[2]);
1899
+ }
1900
+ function quaternionFromCaptureValue(value) {
1901
+ return value instanceof THREE11.Quaternion ? value.clone() : new THREE11.Quaternion(value[0], value[1], value[2], value[3]);
1902
+ }
1903
+ function applyMountedCameraPoseOffsets(options, position, quaternion) {
1904
+ const resolvedPosition = new THREE11.Vector3(position[0], position[1], position[2]);
1905
+ const resolvedQuaternion = new THREE11.Quaternion(
1906
+ quaternion[0],
1907
+ quaternion[1],
1908
+ quaternion[2],
1909
+ quaternion[3]
1910
+ );
1911
+ if (options.positionOffset) {
1912
+ resolvedPosition.add(
1913
+ vector3FromCaptureValue(options.positionOffset).applyQuaternion(resolvedQuaternion)
1914
+ );
1915
+ }
1916
+ if (options.quaternionOffset) {
1917
+ resolvedQuaternion.multiply(quaternionFromCaptureValue(options.quaternionOffset)).normalize();
1918
+ }
1919
+ return {
1920
+ position: [
1921
+ resolvedPosition.x,
1922
+ resolvedPosition.y,
1923
+ resolvedPosition.z
1924
+ ],
1925
+ quaternion: [
1926
+ resolvedQuaternion.x,
1927
+ resolvedQuaternion.y,
1928
+ resolvedQuaternion.z,
1929
+ resolvedQuaternion.w
1930
+ ]
1931
+ };
1932
+ }
1865
1933
  function countMountedCameraSelectors(options) {
1866
1934
  return Number(Boolean(options.cameraName)) + Number(Boolean(options.siteName)) + Number(Boolean(options.bodyName));
1867
1935
  }
@@ -2553,10 +2621,10 @@ function MujocoSimProvider({
2553
2621
  `MuJoCo camera "${options.cameraName}" does not expose a capture pose.`
2554
2622
  );
2555
2623
  }
2624
+ const pose = applyMountedCameraPoseOffsets(options, position, quaternion);
2556
2625
  return {
2557
2626
  ...baseOptions,
2558
- position,
2559
- quaternion,
2627
+ ...pose,
2560
2628
  fov: options.fov ?? model.cam_fovy?.[cameraId],
2561
2629
  source: { kind: "mujoco-camera", cameraName: options.cameraName }
2562
2630
  };
@@ -2566,10 +2634,14 @@ function MujocoSimProvider({
2566
2634
  if (siteId < 0) {
2567
2635
  throw new Error(`MuJoCo site "${options.siteName}" was not found.`);
2568
2636
  }
2637
+ const pose = applyMountedCameraPoseOffsets(
2638
+ options,
2639
+ vector3FromArray(data.site_xpos, siteId * 3),
2640
+ quaternionFromXmat(data.site_xmat, siteId * 9)
2641
+ );
2569
2642
  return {
2570
2643
  ...baseOptions,
2571
- position: vector3FromArray(data.site_xpos, siteId * 3),
2572
- quaternion: quaternionFromXmat(data.site_xmat, siteId * 9),
2644
+ ...pose,
2573
2645
  source: { kind: "mujoco-site", siteName: options.siteName }
2574
2646
  };
2575
2647
  }
@@ -2583,10 +2655,14 @@ function MujocoSimProvider({
2583
2655
  `MuJoCo body "${options.bodyName}" does not expose world orientation data.`
2584
2656
  );
2585
2657
  }
2658
+ const pose = applyMountedCameraPoseOffsets(
2659
+ options,
2660
+ vector3FromArray(data.xpos, bodyId * 3),
2661
+ quaternionFromXmat(data.xmat, bodyId * 9)
2662
+ );
2586
2663
  return {
2587
2664
  ...baseOptions,
2588
- position: vector3FromArray(data.xpos, bodyId * 3),
2589
- quaternion: quaternionFromXmat(data.xmat, bodyId * 9),
2665
+ ...pose,
2590
2666
  source: { kind: "mujoco-body", bodyName: options.bodyName }
2591
2667
  };
2592
2668
  }
@@ -2994,6 +3070,30 @@ function MujocoSimProvider({
2994
3070
  },
2995
3071
  [camera, gl]
2996
3072
  );
3073
+ const projectImagePointTo3D2 = useCallback(
3074
+ (options) => {
3075
+ const {
3076
+ x,
3077
+ y,
3078
+ coordinateSpace,
3079
+ imageWidth,
3080
+ imageHeight,
3081
+ maxDistance,
3082
+ ...captureOptions
3083
+ } = options;
3084
+ const resolvedCaptureOptions = resolveCameraCaptureOptions(captureOptions);
3085
+ return projectImagePointTo3D(gl, scene, camera, {
3086
+ ...resolvedCaptureOptions,
3087
+ x,
3088
+ y,
3089
+ coordinateSpace,
3090
+ imageWidth,
3091
+ imageHeight,
3092
+ maxDistance
3093
+ });
3094
+ },
3095
+ [camera, gl, resolveCameraCaptureOptions, scene]
3096
+ );
2997
3097
  const setBodyMass = useCallback((name, mass) => {
2998
3098
  const model = mjModelRef.current;
2999
3099
  if (!model) return;
@@ -3078,6 +3178,7 @@ function MujocoSimProvider({
3078
3178
  captureCameraFrameBlob: captureCameraFrameBlobApi,
3079
3179
  recordCameraSequence: recordCameraSequenceApi,
3080
3180
  project2DTo3D,
3181
+ projectImagePointTo3D: projectImagePointTo3D2,
3081
3182
  setBodyMass,
3082
3183
  setGeomFriction,
3083
3184
  setGeomSize,
@@ -3137,6 +3238,7 @@ function MujocoSimProvider({
3137
3238
  captureCameraFrameBlobApi,
3138
3239
  recordCameraSequenceApi,
3139
3240
  project2DTo3D,
3241
+ projectImagePointTo3D2,
3140
3242
  setBodyMass,
3141
3243
  setGeomFriction,
3142
3244
  setGeomSize
@@ -3724,8 +3826,8 @@ var useIkController = createControllerHook(
3724
3826
  const ga = gizmoAnimRef.current;
3725
3827
  const target = ikTargetRef.current;
3726
3828
  if (!ga.active || !target) return;
3727
- const now = performance.now();
3728
- const elapsed = now - ga.startTime;
3829
+ const now2 = performance.now();
3830
+ const elapsed = now2 - ga.startTime;
3729
3831
  const t = Math.min(elapsed / ga.duration, 1);
3730
3832
  const ease = 1 - Math.pow(1 - t, 3);
3731
3833
  target.position.lerpVectors(ga.startPos, ga.endPos, ease);
@@ -4088,7 +4190,7 @@ var _point = new Float64Array(3);
4088
4190
  var _bodyPos = new THREE11.Vector3();
4089
4191
  var _bodyQuat = new THREE11.Quaternion();
4090
4192
  var _worldHit = new THREE11.Vector3();
4091
- var _raycaster = new THREE11.Raycaster();
4193
+ var _raycaster2 = new THREE11.Raycaster();
4092
4194
  var _mouse = new THREE11.Vector2();
4093
4195
  function DragInteraction({
4094
4196
  stiffness = 250,
@@ -4137,8 +4239,8 @@ function DragInteraction({
4137
4239
  (evt.clientX - rect.left) / rect.width * 2 - 1,
4138
4240
  -((evt.clientY - rect.top) / rect.height) * 2 + 1
4139
4241
  );
4140
- _raycaster.setFromCamera(_mouse, camera);
4141
- const hits = _raycaster.intersectObjects(scene.children, true);
4242
+ _raycaster2.setFromCamera(_mouse, camera);
4243
+ const hits = _raycaster2.intersectObjects(scene.children, true);
4142
4244
  for (const hit of hits) {
4143
4245
  let obj = hit.object;
4144
4246
  while (obj && obj.userData.bodyID === void 0 && obj.parent) {
@@ -4183,8 +4285,8 @@ function DragInteraction({
4183
4285
  (evt.clientX - rect.left) / rect.width * 2 - 1,
4184
4286
  -((evt.clientY - rect.top) / rect.height) * 2 + 1
4185
4287
  );
4186
- _raycaster.setFromCamera(_mouse, camera);
4187
- mouseWorldRef.current.copy(_raycaster.ray.origin).addScaledVector(_raycaster.ray.direction, grabDistanceRef.current);
4288
+ _raycaster2.setFromCamera(_mouse, camera);
4289
+ mouseWorldRef.current.copy(_raycaster2.ray.origin).addScaledVector(_raycaster2.ray.direction, grabDistanceRef.current);
4188
4290
  };
4189
4291
  const onPointerUp = () => {
4190
4292
  if (!draggingRef.current) return;
@@ -5532,12 +5634,12 @@ function useTrajectoryPlayer(trajectory, options = {}) {
5532
5634
  if ((optionsRef.current.mode ?? "kinematic") !== "kinematic") return;
5533
5635
  const traj = trajectoryRef.current;
5534
5636
  if (traj.length === 0) return;
5535
- const now = performance.now();
5637
+ const now2 = performance.now();
5536
5638
  const fps = optionsRef.current.fps ?? 30;
5537
5639
  const frameInterval = 1e3 / (fps * speedRef.current);
5538
- const elapsed = now - lastFrameTimeRef.current;
5640
+ const elapsed = now2 - lastFrameTimeRef.current;
5539
5641
  if (elapsed < frameInterval) return;
5540
- lastFrameTimeRef.current = now;
5642
+ lastFrameTimeRef.current = now2;
5541
5643
  const model = mjModelRef.current;
5542
5644
  const data = mjDataRef.current;
5543
5645
  if (!model || !data) return;
@@ -5894,6 +5996,93 @@ function useBodyState(name) {
5894
5996
  });
5895
5997
  return { position, quaternion, linearVelocity, angularVelocity };
5896
5998
  }
5999
+ var _matrix2 = new THREE11.Matrix4();
6000
+ function quaternionFromMatrixArray(target, values, offset) {
6001
+ _matrix2.set(
6002
+ values[offset],
6003
+ values[offset + 1],
6004
+ values[offset + 2],
6005
+ 0,
6006
+ values[offset + 3],
6007
+ values[offset + 4],
6008
+ values[offset + 5],
6009
+ 0,
6010
+ values[offset + 6],
6011
+ values[offset + 7],
6012
+ values[offset + 8],
6013
+ 0,
6014
+ 0,
6015
+ 0,
6016
+ 0,
6017
+ 1
6018
+ );
6019
+ target.setFromRotationMatrix(_matrix2);
6020
+ }
6021
+ function quaternionFromMujocoQuat2(target, values, offset) {
6022
+ target.set(
6023
+ values[offset + 1] ?? 0,
6024
+ values[offset + 2] ?? 0,
6025
+ values[offset + 3] ?? 0,
6026
+ values[offset] ?? 1
6027
+ );
6028
+ }
6029
+ function useNamedPose(kind, name) {
6030
+ const { mjModelRef, status } = useMujocoContext();
6031
+ const idRef = useRef(-1);
6032
+ const foundRef = useRef(false);
6033
+ const positionRef = useRef(new THREE11.Vector3());
6034
+ const quaternionRef = useRef(new THREE11.Quaternion());
6035
+ useEffect(() => {
6036
+ const model = mjModelRef.current;
6037
+ if (!model || status !== "ready") {
6038
+ idRef.current = -1;
6039
+ foundRef.current = false;
6040
+ return;
6041
+ }
6042
+ if (kind === "body") idRef.current = findBodyByName(model, name);
6043
+ else if (kind === "geom") idRef.current = findGeomByName(model, name);
6044
+ else idRef.current = findSiteByName(model, name);
6045
+ foundRef.current = idRef.current >= 0;
6046
+ }, [kind, name, status, mjModelRef]);
6047
+ useAfterPhysicsStep(({ data }) => {
6048
+ const id = idRef.current;
6049
+ if (id < 0) return;
6050
+ if (kind === "body") {
6051
+ const p2 = id * 3;
6052
+ positionRef.current.set(data.xpos[p2], data.xpos[p2 + 1], data.xpos[p2 + 2]);
6053
+ if (data.xmat) {
6054
+ quaternionFromMatrixArray(quaternionRef.current, data.xmat, id * 9);
6055
+ } else {
6056
+ quaternionFromMujocoQuat2(quaternionRef.current, data.xquat, id * 4);
6057
+ }
6058
+ return;
6059
+ }
6060
+ if (kind === "geom") {
6061
+ const p2 = id * 3;
6062
+ positionRef.current.set(data.geom_xpos[p2], data.geom_xpos[p2 + 1], data.geom_xpos[p2 + 2]);
6063
+ quaternionFromMatrixArray(quaternionRef.current, data.geom_xmat, id * 9);
6064
+ return;
6065
+ }
6066
+ const p = id * 3;
6067
+ positionRef.current.set(data.site_xpos[p], data.site_xpos[p + 1], data.site_xpos[p + 2]);
6068
+ quaternionFromMatrixArray(quaternionRef.current, data.site_xmat, id * 9);
6069
+ });
6070
+ return {
6071
+ id: idRef,
6072
+ found: foundRef,
6073
+ position: positionRef,
6074
+ quaternion: quaternionRef
6075
+ };
6076
+ }
6077
+ function useBodyPose(name) {
6078
+ return useNamedPose("body", name);
6079
+ }
6080
+ function useGeomPose(name) {
6081
+ return useNamedPose("geom", name);
6082
+ }
6083
+ function useSitePose(name) {
6084
+ return useNamedPose("site", name);
6085
+ }
5897
6086
  function useCtrl(name) {
5898
6087
  const { mjModelRef, mjDataRef, status } = useMujocoContext();
5899
6088
  const actuatorIdRef = useRef(-1);
@@ -5927,10 +6116,203 @@ function useCtrl(name) {
5927
6116
  }
5928
6117
  }), [name, mjDataRef]);
5929
6118
  }
5930
- function useKeyboardTeleop(config) {
5931
- const { mjModelRef, mjDataRef, status } = useMujocoContext();
5932
- const pressedRef = useRef(/* @__PURE__ */ new Set());
5933
- const toggleStateRef = useRef(/* @__PURE__ */ new Map());
6119
+ var claimsByModel = /* @__PURE__ */ new WeakMap();
6120
+ function getClaims(model) {
6121
+ let claims = claimsByModel.get(model);
6122
+ if (!claims) {
6123
+ claims = /* @__PURE__ */ new Map();
6124
+ claimsByModel.set(model, claims);
6125
+ }
6126
+ return claims;
6127
+ }
6128
+ function releaseClaims(model, token) {
6129
+ if (!model) return;
6130
+ const claims = claimsByModel.get(model);
6131
+ if (!claims) return;
6132
+ for (const [actuatorIndex, claim] of claims) {
6133
+ if (claim.token === token) claims.delete(actuatorIndex);
6134
+ }
6135
+ }
6136
+ function useControlWriter(options) {
6137
+ const {
6138
+ owner,
6139
+ selector,
6140
+ enabled = true,
6141
+ warnOnConflict = true,
6142
+ allowSameOwner = true,
6143
+ onConflict
6144
+ } = options;
6145
+ const { api, mjModelRef, mjDataRef, status } = useMujocoContext();
6146
+ const tokenRef = useRef(Symbol(owner));
6147
+ const claimedModelRef = useRef(null);
6148
+ const groupRef = useRef(null);
6149
+ const conflictsRef = useRef([]);
6150
+ const onConflictRef = useRef(onConflict);
6151
+ onConflictRef.current = onConflict;
6152
+ const release = useCallback(() => {
6153
+ releaseClaims(claimedModelRef.current, tokenRef.current);
6154
+ claimedModelRef.current = null;
6155
+ conflictsRef.current = [];
6156
+ }, []);
6157
+ useEffect(() => {
6158
+ release();
6159
+ if (!enabled || status !== "ready") {
6160
+ groupRef.current = null;
6161
+ return;
6162
+ }
6163
+ const model = mjModelRef.current;
6164
+ if (!model) {
6165
+ groupRef.current = null;
6166
+ return;
6167
+ }
6168
+ const group = selector ? api.resolveControlGroup(selector) : api.getControlMap();
6169
+ groupRef.current = group;
6170
+ if (!group) return;
6171
+ const claims = getClaims(model);
6172
+ const conflicts = [];
6173
+ for (const actuatorIndex of group.ctrlAdr) {
6174
+ const existing = claims.get(actuatorIndex);
6175
+ if (existing && existing.token !== tokenRef.current && (!allowSameOwner || existing.owner !== owner)) {
6176
+ conflicts.push({
6177
+ actuatorIndex,
6178
+ owner: existing.owner,
6179
+ requestedBy: owner
6180
+ });
6181
+ }
6182
+ }
6183
+ conflictsRef.current = conflicts;
6184
+ if (conflicts.length > 0) {
6185
+ onConflictRef.current?.(conflicts);
6186
+ if (warnOnConflict) {
6187
+ console.warn(
6188
+ `[mujoco-react] Control writer "${owner}" conflicts with existing writer(s): ${conflicts.map((conflict) => `${conflict.actuatorIndex}:${conflict.owner}`).join(", ")}`
6189
+ );
6190
+ }
6191
+ return;
6192
+ }
6193
+ for (const actuatorIndex of group.ctrlAdr) {
6194
+ claims.set(actuatorIndex, { owner, token: tokenRef.current });
6195
+ }
6196
+ claimedModelRef.current = model;
6197
+ return release;
6198
+ }, [allowSameOwner, api, enabled, mjModelRef, owner, release, selector, status, warnOnConflict]);
6199
+ const canWrite = useCallback(() => enabled && groupRef.current !== null && conflictsRef.current.length === 0, [enabled]);
6200
+ const read = useCallback(() => {
6201
+ const data = mjDataRef.current;
6202
+ const group = groupRef.current;
6203
+ if (!data || !group) return new Float64Array(0);
6204
+ return group.readCtrl(data);
6205
+ }, [mjDataRef]);
6206
+ const write = useCallback((values, writeOptions = {}) => {
6207
+ const data = mjDataRef.current;
6208
+ const group = groupRef.current;
6209
+ if (!data || !group) return false;
6210
+ if (!writeOptions.force && !canWrite()) return false;
6211
+ group.writeCtrl(data, values);
6212
+ return true;
6213
+ }, [canWrite, mjDataRef]);
6214
+ return useMemo(() => ({
6215
+ owner,
6216
+ group: groupRef,
6217
+ conflicts: conflictsRef,
6218
+ canWrite,
6219
+ read,
6220
+ write,
6221
+ release
6222
+ }), [canWrite, owner, read, release, write]);
6223
+ }
6224
+ var geomNameCacheByModel2 = /* @__PURE__ */ new WeakMap();
6225
+ var bodyNameCacheByModel = /* @__PURE__ */ new WeakMap();
6226
+ function getCachedName(cacheByModel, model, id, address) {
6227
+ if (id < 0) return "";
6228
+ let cache = cacheByModel.get(model);
6229
+ if (!cache) {
6230
+ cache = /* @__PURE__ */ new Map();
6231
+ cacheByModel.set(model, cache);
6232
+ }
6233
+ let name = cache.get(id);
6234
+ if (name === void 0) {
6235
+ name = getName(model, address);
6236
+ cache.set(id, name);
6237
+ }
6238
+ return name;
6239
+ }
6240
+ function matchesFilter(entry, bodyNames, geomNames, includeWorldBody) {
6241
+ if (!includeWorldBody && (entry.body1 === 0 || entry.body2 === 0)) return false;
6242
+ if (bodyNames && !bodyNames.includes(entry.body1Name) && !bodyNames.includes(entry.body2Name)) {
6243
+ return false;
6244
+ }
6245
+ if (geomNames && !geomNames.includes(entry.geom1Name) && !geomNames.includes(entry.geom2Name)) {
6246
+ return false;
6247
+ }
6248
+ return true;
6249
+ }
6250
+ function useContactHistory(options = {}) {
6251
+ const entriesRef = useRef([]);
6252
+ const optionsRef = useRef(options);
6253
+ optionsRef.current = options;
6254
+ const clear = useCallback(() => {
6255
+ entriesRef.current = [];
6256
+ }, []);
6257
+ const countPair = useCallback((nameA, nameB) => {
6258
+ let count = 0;
6259
+ for (const entry of entriesRef.current) {
6260
+ const matchesBodies = entry.body1Name === nameA && entry.body2Name === nameB || entry.body1Name === nameB && entry.body2Name === nameA;
6261
+ const matchesGeoms = entry.geom1Name === nameA && entry.geom2Name === nameB || entry.geom1Name === nameB && entry.geom2Name === nameA;
6262
+ if (matchesBodies || matchesGeoms) count += 1;
6263
+ }
6264
+ return count;
6265
+ }, []);
6266
+ useAfterPhysicsStep(({ model, data }) => {
6267
+ if ((data.ncon ?? 0) <= 0) return;
6268
+ const {
6269
+ maxLength = 2e3,
6270
+ bodyNames,
6271
+ geomNames,
6272
+ includeWorldBody = false
6273
+ } = optionsRef.current;
6274
+ if (maxLength <= 0) return;
6275
+ const nextEntries = [];
6276
+ withContacts(data, (contacts) => {
6277
+ for (let index = 0; index < data.ncon; index += 1) {
6278
+ const contact = getContact(contacts, index);
6279
+ if (!contact) break;
6280
+ const body1 = model.geom_bodyid[contact.geom1] ?? -1;
6281
+ const body2 = model.geom_bodyid[contact.geom2] ?? -1;
6282
+ const entry = {
6283
+ geom1: contact.geom1,
6284
+ geom2: contact.geom2,
6285
+ geom1Name: getCachedName(geomNameCacheByModel2, model, contact.geom1, model.name_geomadr[contact.geom1]),
6286
+ geom2Name: getCachedName(geomNameCacheByModel2, model, contact.geom2, model.name_geomadr[contact.geom2]),
6287
+ body1,
6288
+ body2,
6289
+ body1Name: body1 >= 0 ? getCachedName(bodyNameCacheByModel, model, body1, model.name_bodyadr[body1]) : "",
6290
+ body2Name: body2 >= 0 ? getCachedName(bodyNameCacheByModel, model, body2, model.name_bodyadr[body2]) : "",
6291
+ pos: [contact.pos[0], contact.pos[1], contact.pos[2]],
6292
+ depth: contact.dist,
6293
+ time: data.time
6294
+ };
6295
+ if (matchesFilter(entry, bodyNames, geomNames, includeWorldBody)) {
6296
+ nextEntries.push(entry);
6297
+ }
6298
+ }
6299
+ });
6300
+ if (nextEntries.length === 0) return;
6301
+ entriesRef.current.push(...nextEntries);
6302
+ if (entriesRef.current.length > maxLength) {
6303
+ entriesRef.current.splice(0, entriesRef.current.length - maxLength);
6304
+ }
6305
+ });
6306
+ return {
6307
+ entries: entriesRef,
6308
+ clear,
6309
+ countPair
6310
+ };
6311
+ }
6312
+ function useKeyboardTeleop(config) {
6313
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
6314
+ const pressedRef = useRef(/* @__PURE__ */ new Set());
6315
+ const toggleStateRef = useRef(/* @__PURE__ */ new Map());
5934
6316
  const enabledRef = useRef(config.enabled ?? true);
5935
6317
  enabledRef.current = config.enabled ?? true;
5936
6318
  const bindingsRef = useRef(config.bindings);
@@ -6119,29 +6501,114 @@ function useKeyboardIkTarget(config) {
6119
6501
  }
6120
6502
  });
6121
6503
  }
6504
+ function isPromiseLike(value) {
6505
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
6506
+ }
6507
+ function isPolicyActionChunk(value) {
6508
+ return Array.isArray(value) && value.length > 0 && (Array.isArray(value[0]) || ArrayBuffer.isView(value[0]));
6509
+ }
6510
+ function toPolicyActions(output) {
6511
+ return isPolicyActionChunk(output) ? [...output] : [output];
6512
+ }
6513
+ function enqueuePolicyActions(queue, actions, observation, strategy) {
6514
+ if (strategy === "replace") {
6515
+ queue.splice(0, queue.length);
6516
+ }
6517
+ queue.push(...actions.map((action) => ({ action, observation })));
6518
+ }
6122
6519
  function usePolicy(config) {
6123
6520
  const lastActionTimeRef = useRef(0);
6124
6521
  const lastObservationRef = useRef(null);
6125
6522
  const lastActionRef = useRef(null);
6523
+ const actionQueueRef = useRef([]);
6524
+ const inFlightRef = useRef(false);
6525
+ const lastErrorRef = useRef(null);
6526
+ const epochRef = useRef(0);
6126
6527
  const isRunningRef = useRef(config.enabled ?? true);
6127
6528
  const configRef = useRef(config);
6128
6529
  configRef.current = config;
6129
6530
  isRunningRef.current = config.enabled ?? isRunningRef.current;
6531
+ const clearQueue = useCallback(() => {
6532
+ epochRef.current += 1;
6533
+ actionQueueRef.current.splice(0, actionQueueRef.current.length);
6534
+ inFlightRef.current = false;
6535
+ lastErrorRef.current = null;
6536
+ }, []);
6537
+ const reset = useCallback(() => {
6538
+ clearQueue();
6539
+ lastActionTimeRef.current = 0;
6540
+ lastObservationRef.current = null;
6541
+ lastActionRef.current = null;
6542
+ }, [clearQueue]);
6130
6543
  useBeforePhysicsStep(({ model, data }) => {
6131
6544
  if (!isRunningRef.current) return;
6132
6545
  const cfg = configRef.current;
6133
6546
  model.opt?.timestep ?? 2e-3;
6134
6547
  const interval = 1 / cfg.frequency;
6135
6548
  if (data.time - lastActionTimeRef.current >= interval) {
6549
+ const queuedAction = actionQueueRef.current.shift();
6550
+ if (queuedAction) {
6551
+ cfg.onAction({
6552
+ action: queuedAction.action,
6553
+ observation: queuedAction.observation,
6554
+ model,
6555
+ data
6556
+ });
6557
+ lastActionTimeRef.current = data.time;
6558
+ lastActionRef.current = queuedAction.action;
6559
+ }
6560
+ const prefetchThreshold = cfg.prefetchThreshold ?? 0;
6561
+ const shouldInfer = !inFlightRef.current && (!queuedAction || actionQueueRef.current.length <= prefetchThreshold);
6562
+ if (!shouldInfer) return;
6136
6563
  const observation = cfg.onObservation({ model, data });
6137
- const action = cfg.infer ? cfg.infer({ observation, model, data }) : observation;
6138
- cfg.onAction({ action, observation, model, data });
6564
+ const result = cfg.infer ? cfg.infer({ observation, model, data }) : observation;
6565
+ if (isPromiseLike(result)) {
6566
+ const epoch = epochRef.current;
6567
+ inFlightRef.current = true;
6568
+ result.then((output) => {
6569
+ if (epoch !== epochRef.current) return;
6570
+ enqueuePolicyActions(
6571
+ actionQueueRef.current,
6572
+ toPolicyActions(output),
6573
+ observation,
6574
+ cfg.queueStrategy ?? "append"
6575
+ );
6576
+ lastErrorRef.current = null;
6577
+ }).catch((error) => {
6578
+ if (epoch !== epochRef.current) return;
6579
+ lastErrorRef.current = error;
6580
+ cfg.onError?.(error);
6581
+ }).finally(() => {
6582
+ if (epoch !== epochRef.current) return;
6583
+ inFlightRef.current = false;
6584
+ });
6585
+ } else {
6586
+ const actions = toPolicyActions(result);
6587
+ if (queuedAction) {
6588
+ enqueuePolicyActions(
6589
+ actionQueueRef.current,
6590
+ actions,
6591
+ observation,
6592
+ cfg.queueStrategy ?? "append"
6593
+ );
6594
+ } else {
6595
+ const [action, ...queuedActions] = actions;
6596
+ if (!action) return;
6597
+ enqueuePolicyActions(
6598
+ actionQueueRef.current,
6599
+ queuedActions,
6600
+ observation,
6601
+ cfg.queueStrategy ?? "append"
6602
+ );
6603
+ cfg.onAction({ action, observation, model, data });
6604
+ lastActionRef.current = action;
6605
+ }
6606
+ }
6139
6607
  lastActionTimeRef.current = data.time;
6140
6608
  lastObservationRef.current = observation;
6141
- lastActionRef.current = action;
6142
6609
  }
6143
6610
  });
6144
- return {
6611
+ return useMemo(() => ({
6145
6612
  get isRunning() {
6146
6613
  return isRunningRef.current;
6147
6614
  },
@@ -6150,15 +6617,304 @@ function usePolicy(config) {
6150
6617
  },
6151
6618
  stop: () => {
6152
6619
  isRunningRef.current = false;
6620
+ if (configRef.current.clearQueueOnStop) reset();
6621
+ },
6622
+ clearQueue,
6623
+ reset,
6624
+ get inFlight() {
6625
+ return inFlightRef.current;
6626
+ },
6627
+ get queuedActions() {
6628
+ return actionQueueRef.current.length;
6153
6629
  },
6154
6630
  get lastObservation() {
6155
6631
  return lastObservationRef.current;
6156
6632
  },
6157
6633
  get lastAction() {
6158
6634
  return lastActionRef.current;
6635
+ },
6636
+ get lastError() {
6637
+ return lastErrorRef.current;
6159
6638
  }
6639
+ }), [clearQueue, reset]);
6640
+ }
6641
+ function now() {
6642
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
6643
+ }
6644
+ function isAbortError(error) {
6645
+ return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
6646
+ }
6647
+ function createAbortError(message) {
6648
+ if (typeof DOMException !== "undefined") {
6649
+ return new DOMException(message, "AbortError");
6650
+ }
6651
+ const error = new Error(message);
6652
+ error.name = "AbortError";
6653
+ return error;
6654
+ }
6655
+ function abortController(controller, reason) {
6656
+ if (!controller || controller.signal.aborted) return;
6657
+ if (reason !== void 0) {
6658
+ controller.abort(reason);
6659
+ } else {
6660
+ controller.abort();
6661
+ }
6662
+ }
6663
+ function createMergedAbortSignal(localSignal, externalSignal) {
6664
+ if (!externalSignal) return localSignal;
6665
+ if (externalSignal.aborted) {
6666
+ const controller2 = new AbortController();
6667
+ abortController(controller2, externalSignal.reason);
6668
+ return controller2.signal;
6669
+ }
6670
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.any === "function") {
6671
+ return AbortSignal.any([localSignal, externalSignal]);
6672
+ }
6673
+ const controller = new AbortController();
6674
+ const abortFromLocal = () => abortController(controller, localSignal.reason);
6675
+ const abortFromExternal = () => abortController(controller, externalSignal.reason);
6676
+ localSignal.addEventListener("abort", abortFromLocal, { once: true });
6677
+ externalSignal.addEventListener("abort", abortFromExternal, { once: true });
6678
+ return controller.signal;
6679
+ }
6680
+ function vectorToArray(vector) {
6681
+ return Array.from(vector, (value) => Number(value));
6682
+ }
6683
+ function isPolicyVectorArray(value) {
6684
+ return Array.isArray(value) && value.every((entry) => Array.isArray(entry) || ArrayBuffer.isView(entry));
6685
+ }
6686
+ function isPolicyVector(value) {
6687
+ return Array.isArray(value) || ArrayBuffer.isView(value);
6688
+ }
6689
+ function defaultBuildRemotePolicyRequest(input) {
6690
+ const observation = vectorToArray(input.observation);
6691
+ return {
6692
+ observation,
6693
+ state: observation,
6694
+ time: input.data.time,
6695
+ reset: input.reset
6160
6696
  };
6161
6697
  }
6698
+ async function defaultReadRemotePolicyResponse(response) {
6699
+ const text = await response.text();
6700
+ if (text.length === 0) return null;
6701
+ return JSON.parse(text);
6702
+ }
6703
+ function defaultParseRemotePolicyResponse(responseBody) {
6704
+ if (responseBody && typeof responseBody === "object") {
6705
+ const body = responseBody;
6706
+ if (typeof body.error === "string" && body.error.length > 0) {
6707
+ throw new Error(body.error);
6708
+ }
6709
+ if (isPolicyVectorArray(body.actions) && body.actions.length > 0) {
6710
+ return body.actions;
6711
+ }
6712
+ if (isPolicyVector(body.action)) {
6713
+ return body.action;
6714
+ }
6715
+ }
6716
+ if (isPolicyVectorArray(responseBody) && responseBody.length > 0) {
6717
+ return responseBody;
6718
+ }
6719
+ if (isPolicyVector(responseBody)) {
6720
+ return responseBody;
6721
+ }
6722
+ throw new Error("Remote policy response must include `action` or `actions`.");
6723
+ }
6724
+ function createHttpError(response, responseBody) {
6725
+ const suffix = responseBody && typeof responseBody === "object" && "error" in responseBody ? `: ${String(responseBody.error)}` : "";
6726
+ return new Error(`Remote policy request failed with HTTP ${response.status}${suffix}`);
6727
+ }
6728
+ function useRemotePolicy(config) {
6729
+ const configRef = useRef(config);
6730
+ configRef.current = config;
6731
+ const requestCountRef = useRef(0);
6732
+ const responseCountRef = useRef(0);
6733
+ const remoteStatusRef = useRef("idle");
6734
+ const lastRequestBodyRef = useRef(null);
6735
+ const lastResponseBodyRef = useRef(null);
6736
+ const lastHttpStatusRef = useRef(null);
6737
+ const lastRequestMsRef = useRef(null);
6738
+ const abortControllerRef = useRef(null);
6739
+ const remoteEpochRef = useRef(0);
6740
+ const policy = usePolicy({
6741
+ ...config,
6742
+ infer: async ({ observation, model, data }) => {
6743
+ const cfg = configRef.current;
6744
+ abortController(abortControllerRef.current, createAbortError("Remote policy request was superseded."));
6745
+ const controller = new AbortController();
6746
+ abortControllerRef.current = controller;
6747
+ const signal = createMergedAbortSignal(controller.signal, cfg.signal);
6748
+ const remoteEpoch = remoteEpochRef.current;
6749
+ const requestIndex = requestCountRef.current;
6750
+ const requestInput = {
6751
+ observation,
6752
+ model,
6753
+ data,
6754
+ reset: requestIndex === 0,
6755
+ requestIndex,
6756
+ signal
6757
+ };
6758
+ requestCountRef.current += 1;
6759
+ const requestStartedAt = now();
6760
+ const body = await (cfg.buildRequest?.(requestInput) ?? defaultBuildRemotePolicyRequest(requestInput));
6761
+ signal.throwIfAborted();
6762
+ if (remoteEpoch !== remoteEpochRef.current) {
6763
+ throw createAbortError("Remote policy request was reset.");
6764
+ }
6765
+ lastRequestBodyRef.current = body;
6766
+ remoteStatusRef.current = "requesting";
6767
+ cfg.onRequest?.({
6768
+ ...requestInput,
6769
+ body,
6770
+ requestStartedAt
6771
+ });
6772
+ let response = null;
6773
+ let responseBody = null;
6774
+ try {
6775
+ const headers = new Headers(cfg.headers);
6776
+ if (!headers.has("content-type")) {
6777
+ headers.set("content-type", "application/json");
6778
+ }
6779
+ const fetcher = cfg.fetcher ?? fetch;
6780
+ response = await fetcher(String(cfg.endpoint), {
6781
+ ...cfg.requestInit,
6782
+ method: cfg.method ?? "POST",
6783
+ credentials: cfg.credentials,
6784
+ headers,
6785
+ signal,
6786
+ body: typeof body === "string" ? body : JSON.stringify(body)
6787
+ });
6788
+ if (remoteEpoch === remoteEpochRef.current) {
6789
+ lastHttpStatusRef.current = response.status;
6790
+ }
6791
+ responseBody = await (cfg.readResponse?.(response) ?? defaultReadRemotePolicyResponse(response));
6792
+ signal.throwIfAborted();
6793
+ if (remoteEpoch !== remoteEpochRef.current) {
6794
+ throw createAbortError("Remote policy request was reset.");
6795
+ }
6796
+ lastResponseBodyRef.current = responseBody;
6797
+ if (!response.ok) {
6798
+ throw createHttpError(response, responseBody);
6799
+ }
6800
+ const responseFinishedAt = now();
6801
+ const info = {
6802
+ ...requestInput,
6803
+ body,
6804
+ requestStartedAt,
6805
+ response,
6806
+ responseBody,
6807
+ responseFinishedAt,
6808
+ requestMs: responseFinishedAt - requestStartedAt
6809
+ };
6810
+ if (remoteEpoch === remoteEpochRef.current) {
6811
+ lastRequestMsRef.current = info.requestMs;
6812
+ responseCountRef.current += 1;
6813
+ }
6814
+ cfg.onResponse?.(info);
6815
+ const output = await (cfg.parseResponse?.(responseBody, info) ?? defaultParseRemotePolicyResponse(responseBody));
6816
+ if (remoteEpoch === remoteEpochRef.current) {
6817
+ remoteStatusRef.current = "ready";
6818
+ }
6819
+ return output;
6820
+ } catch (error) {
6821
+ if (response && remoteEpoch === remoteEpochRef.current) {
6822
+ lastHttpStatusRef.current = response.status;
6823
+ }
6824
+ if (isAbortError(error) || signal.aborted) {
6825
+ if (remoteEpoch === remoteEpochRef.current) {
6826
+ remoteStatusRef.current = "aborted";
6827
+ }
6828
+ throw error;
6829
+ }
6830
+ if (remoteEpoch === remoteEpochRef.current) {
6831
+ remoteStatusRef.current = "error";
6832
+ }
6833
+ throw error;
6834
+ } finally {
6835
+ if (abortControllerRef.current === controller) {
6836
+ abortControllerRef.current = null;
6837
+ }
6838
+ }
6839
+ }
6840
+ });
6841
+ return useMemo(() => {
6842
+ const abort = (reason) => {
6843
+ abortController(abortControllerRef.current, reason);
6844
+ if (abortControllerRef.current) {
6845
+ remoteStatusRef.current = "aborted";
6846
+ }
6847
+ };
6848
+ const resetRemoteState = () => {
6849
+ remoteEpochRef.current += 1;
6850
+ abort(createAbortError("Remote policy request was reset."));
6851
+ requestCountRef.current = 0;
6852
+ responseCountRef.current = 0;
6853
+ remoteStatusRef.current = "idle";
6854
+ lastRequestBodyRef.current = null;
6855
+ lastResponseBodyRef.current = null;
6856
+ lastHttpStatusRef.current = null;
6857
+ lastRequestMsRef.current = null;
6858
+ };
6859
+ return {
6860
+ get isRunning() {
6861
+ return policy.isRunning;
6862
+ },
6863
+ start: policy.start,
6864
+ stop: () => {
6865
+ if (configRef.current.abortOnStop ?? true) {
6866
+ abort(createAbortError("Remote policy request was stopped."));
6867
+ }
6868
+ policy.stop();
6869
+ if (configRef.current.clearQueueOnStop) {
6870
+ resetRemoteState();
6871
+ }
6872
+ },
6873
+ clearQueue: policy.clearQueue,
6874
+ abort,
6875
+ reset: () => {
6876
+ resetRemoteState();
6877
+ policy.reset();
6878
+ },
6879
+ get inFlight() {
6880
+ return policy.inFlight;
6881
+ },
6882
+ get queuedActions() {
6883
+ return policy.queuedActions;
6884
+ },
6885
+ get lastObservation() {
6886
+ return policy.lastObservation;
6887
+ },
6888
+ get lastAction() {
6889
+ return policy.lastAction;
6890
+ },
6891
+ get lastError() {
6892
+ return policy.lastError;
6893
+ },
6894
+ get remoteStatus() {
6895
+ return remoteStatusRef.current;
6896
+ },
6897
+ get requestCount() {
6898
+ return requestCountRef.current;
6899
+ },
6900
+ get responseCount() {
6901
+ return responseCountRef.current;
6902
+ },
6903
+ get lastRequestBody() {
6904
+ return lastRequestBodyRef.current;
6905
+ },
6906
+ get lastResponseBody() {
6907
+ return lastResponseBodyRef.current;
6908
+ },
6909
+ get lastHttpStatus() {
6910
+ return lastHttpStatusRef.current;
6911
+ },
6912
+ get lastRequestMs() {
6913
+ return lastRequestMsRef.current;
6914
+ }
6915
+ };
6916
+ }, [policy]);
6917
+ }
6162
6918
  var EMPTY_OBSERVATION = {
6163
6919
  values: new Float32Array(0),
6164
6920
  layout: []
@@ -6179,6 +6935,134 @@ function useObservation(config) {
6179
6935
  }
6180
6936
  }), [mjDataRef, mjModelRef]);
6181
6937
  }
6938
+
6939
+ // src/policyObservation.ts
6940
+ function pushValues(target, value, size) {
6941
+ if (typeof value === "number") {
6942
+ target.push(value);
6943
+ for (let index = 1; index < size; index += 1) target.push(0);
6944
+ return;
6945
+ }
6946
+ for (let index = 0; index < size; index += 1) {
6947
+ target.push(Number(value[index] ?? 0));
6948
+ }
6949
+ }
6950
+ function readNamedObservation(model, data, options) {
6951
+ const values = [];
6952
+ const layout = [];
6953
+ const missing = options.missing ?? "skip";
6954
+ for (const field of options.fields) {
6955
+ const start = values.length;
6956
+ const value = field.read({ model, data });
6957
+ if (value === null || value === void 0) {
6958
+ if (missing === "skip") continue;
6959
+ if (missing === "throw") {
6960
+ throw new Error(`Unable to read named observation field "${field.name}".`);
6961
+ }
6962
+ for (let index = 0; index < field.size; index += 1) values.push(0);
6963
+ } else {
6964
+ pushValues(values, value, field.size);
6965
+ }
6966
+ layout.push({
6967
+ name: field.name,
6968
+ start,
6969
+ size: field.size,
6970
+ units: field.units
6971
+ });
6972
+ }
6973
+ return {
6974
+ values: options.output === "float64" ? new Float64Array(values) : new Float32Array(values),
6975
+ layout
6976
+ };
6977
+ }
6978
+ function createNamedObservationBuilder(options) {
6979
+ return (model, data) => readNamedObservation(model, data, options);
6980
+ }
6981
+ function qposField(name, index, units = "qpos") {
6982
+ return {
6983
+ name,
6984
+ size: 1,
6985
+ units,
6986
+ read: ({ data }) => data.qpos[index]
6987
+ };
6988
+ }
6989
+ function qvelField(name, index, units = "qvel") {
6990
+ return {
6991
+ name,
6992
+ size: 1,
6993
+ units,
6994
+ read: ({ data }) => data.qvel[index]
6995
+ };
6996
+ }
6997
+ function ctrlField(name, index, units = "ctrl") {
6998
+ return {
6999
+ name,
7000
+ size: 1,
7001
+ units,
7002
+ read: ({ data }) => data.ctrl[index]
7003
+ };
7004
+ }
7005
+ function bodyPositionField(name, units = "world_position") {
7006
+ return {
7007
+ name: `body:${name}:xpos`,
7008
+ size: 3,
7009
+ units,
7010
+ read: ({ model, data }) => {
7011
+ const bodyId = findBodyByName(model, name);
7012
+ if (bodyId < 0) return null;
7013
+ const offset = bodyId * 3;
7014
+ return data.xpos.subarray(offset, offset + 3);
7015
+ }
7016
+ };
7017
+ }
7018
+ function geomPositionField(name, units = "world_position") {
7019
+ return {
7020
+ name: `geom:${name}:xpos`,
7021
+ size: 3,
7022
+ units,
7023
+ read: ({ model, data }) => {
7024
+ const geomId = findGeomByName(model, name);
7025
+ if (geomId < 0) return null;
7026
+ const offset = geomId * 3;
7027
+ return data.geom_xpos.subarray(offset, offset + 3);
7028
+ }
7029
+ };
7030
+ }
7031
+ function sitePositionField(name, units = "world_position") {
7032
+ return {
7033
+ name: `site:${name}:xpos`,
7034
+ size: 3,
7035
+ units,
7036
+ read: ({ model, data }) => {
7037
+ const siteId = findSiteByName(model, name);
7038
+ if (siteId < 0) return null;
7039
+ const offset = siteId * 3;
7040
+ return data.site_xpos.subarray(offset, offset + 3);
7041
+ }
7042
+ };
7043
+ }
7044
+
7045
+ // src/hooks/useNamedObservation.ts
7046
+ var EMPTY_NAMED_OBSERVATION = {
7047
+ values: new Float32Array(0),
7048
+ layout: []
7049
+ };
7050
+ function useNamedObservation(options) {
7051
+ const { mjModelRef, mjDataRef } = useMujocoContext();
7052
+ const optionsRef = useRef(options);
7053
+ optionsRef.current = options;
7054
+ return useMemo(() => ({
7055
+ read() {
7056
+ const model = mjModelRef.current;
7057
+ const data = mjDataRef.current;
7058
+ if (!model || !data) return EMPTY_NAMED_OBSERVATION;
7059
+ return readNamedObservation(model, data, optionsRef.current);
7060
+ },
7061
+ readValues() {
7062
+ return this.read().values;
7063
+ }
7064
+ }), [mjDataRef, mjModelRef]);
7065
+ }
6182
7066
  function useTrajectoryRecorder(options = {}) {
6183
7067
  const { mjModelRef } = useMujocoContext();
6184
7068
  const recordingRef = useRef(false);
@@ -6414,6 +7298,223 @@ function useCameraFrameCapture(defaultOptions = {}) {
6414
7298
  reset
6415
7299
  };
6416
7300
  }
7301
+
7302
+ // src/policyCameraFrames.ts
7303
+ function addPolicyImageAliases(images, stream, frame, includeObservationImageAliases) {
7304
+ const keys = /* @__PURE__ */ new Set();
7305
+ keys.add(stream.key);
7306
+ for (const alias of stream.aliases ?? []) keys.add(alias);
7307
+ if (includeObservationImageAliases) {
7308
+ keys.add(`observation.images.${stream.key}`);
7309
+ for (const alias of stream.aliases ?? []) {
7310
+ keys.add(`observation.images.${alias}`);
7311
+ }
7312
+ }
7313
+ for (const key of keys) {
7314
+ images[key] = frame.dataUrl;
7315
+ }
7316
+ }
7317
+ function describeFrameSource(key, frame) {
7318
+ return `${key}:${frame.source.kind}`;
7319
+ }
7320
+ function hasExplicitPolicyCameraSource(options) {
7321
+ return Boolean(
7322
+ options?.camera || options?.position || options?.quaternion || options?.source
7323
+ );
7324
+ }
7325
+ function createPolicyCameraFrameCapturePlan(options) {
7326
+ const {
7327
+ cameraKeys,
7328
+ defaults,
7329
+ streamOptions,
7330
+ includeObservationImageAliases,
7331
+ requireAll,
7332
+ ...sourceOptions
7333
+ } = options;
7334
+ const mountedPlan = createMountedCameraFrameSequencePlan(cameraKeys, {
7335
+ ...sourceOptions,
7336
+ defaults,
7337
+ cameraOptions: streamOptions
7338
+ });
7339
+ const streams = [];
7340
+ const missingKeys = new Set(mountedPlan.missingKeys);
7341
+ for (const key of cameraKeys) {
7342
+ const perStreamOptions = streamOptions?.[key];
7343
+ if (hasExplicitPolicyCameraSource(perStreamOptions)) {
7344
+ missingKeys.delete(key);
7345
+ streams.push({
7346
+ ...defaults,
7347
+ ...perStreamOptions,
7348
+ key,
7349
+ aliases: perStreamOptions?.aliases
7350
+ });
7351
+ continue;
7352
+ }
7353
+ const mountedCamera = mountedPlan.cameras.find((camera) => camera.key === key);
7354
+ if (!mountedCamera) continue;
7355
+ const { key: _mountedKey, ...captureOptions } = mountedCamera;
7356
+ streams.push({
7357
+ ...captureOptions,
7358
+ key,
7359
+ aliases: perStreamOptions?.aliases
7360
+ });
7361
+ }
7362
+ const result = {
7363
+ cameraKeys: [...cameraKeys],
7364
+ streams,
7365
+ includeObservationImageAliases,
7366
+ mountedPlan,
7367
+ missingKeys: [...missingKeys]
7368
+ };
7369
+ if (requireAll && result.missingKeys.length > 0) {
7370
+ throw new Error(
7371
+ `Unable to resolve policy camera stream${result.missingKeys.length === 1 ? "" : "s"} for ${result.missingKeys.join(", ")}.`
7372
+ );
7373
+ }
7374
+ return result;
7375
+ }
7376
+ function createPolicyCameraFrameCapturePlanFromApi(api, options) {
7377
+ return createPolicyCameraFrameCapturePlan({
7378
+ ...options,
7379
+ cameras: api.getCameras(),
7380
+ sites: api.getSites(),
7381
+ bodies: api.getBodies()
7382
+ });
7383
+ }
7384
+ async function capturePolicyCameraFrames(target, options) {
7385
+ const includeObservationImageAliases = options.includeObservationImageAliases ?? true;
7386
+ const entries = await Promise.all(
7387
+ options.streams.map(async ({ key, aliases, ...captureOptions }) => {
7388
+ const frame = await target.captureCameraFrame(captureOptions);
7389
+ return [key, { frame, aliases }];
7390
+ })
7391
+ );
7392
+ const frames = {};
7393
+ const images = {};
7394
+ const sourceParts = [];
7395
+ for (const [key, { frame, aliases }] of entries) {
7396
+ const stream = { key, aliases };
7397
+ frames[key] = frame;
7398
+ addPolicyImageAliases(
7399
+ images,
7400
+ stream,
7401
+ frame,
7402
+ includeObservationImageAliases
7403
+ );
7404
+ sourceParts.push(describeFrameSource(key, frame));
7405
+ }
7406
+ return {
7407
+ frames,
7408
+ images,
7409
+ sourceSummary: sourceParts.length > 0 ? sourceParts.join(" + ") : "not used by policy",
7410
+ capturedAt: Date.now()
7411
+ };
7412
+ }
7413
+ async function capturePolicyCameraFramesFromMountedStreams(target, options) {
7414
+ const plan = createPolicyCameraFrameCapturePlanFromApi(target, options);
7415
+ const result = await capturePolicyCameraFrames(target, plan);
7416
+ return { ...result, plan };
7417
+ }
7418
+
7419
+ // src/hooks/usePolicyCameraFrames.ts
7420
+ function mergePolicyCameraFrameCaptureOptions(defaultOptions, options) {
7421
+ return {
7422
+ ...defaultOptions,
7423
+ ...options,
7424
+ cameraKeys: options.cameraKeys ?? defaultOptions.cameraKeys,
7425
+ aliases: {
7426
+ ...defaultOptions.aliases,
7427
+ ...options.aliases
7428
+ },
7429
+ defaults: {
7430
+ ...defaultOptions.defaults,
7431
+ ...options.defaults
7432
+ },
7433
+ streamOptions: {
7434
+ ...defaultOptions.streamOptions,
7435
+ ...options.streamOptions
7436
+ }
7437
+ };
7438
+ }
7439
+ function usePolicyCameraFrames(defaultOptions) {
7440
+ const mujoco = useMujoco();
7441
+ const [status, setStatus] = useState("idle");
7442
+ const [error, setError] = useState(null);
7443
+ const reset = useCallback(() => {
7444
+ setStatus("idle");
7445
+ setError(null);
7446
+ }, []);
7447
+ const capture = useCallback(
7448
+ async (options = {}) => {
7449
+ if (!mujoco.api) {
7450
+ throw new Error("MuJoCo scene is not ready for policy camera capture.");
7451
+ }
7452
+ setStatus("capturing");
7453
+ setError(null);
7454
+ try {
7455
+ const result = await capturePolicyCameraFrames(mujoco.api, {
7456
+ ...defaultOptions,
7457
+ ...options,
7458
+ streams: options.streams ?? defaultOptions.streams
7459
+ });
7460
+ setStatus("captured");
7461
+ return result;
7462
+ } catch (nextError) {
7463
+ const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture policy camera frames.");
7464
+ setError(error2);
7465
+ setStatus("error");
7466
+ throw error2;
7467
+ }
7468
+ },
7469
+ [defaultOptions, mujoco.api]
7470
+ );
7471
+ return {
7472
+ status,
7473
+ error,
7474
+ isCapturing: status === "capturing",
7475
+ capture,
7476
+ reset
7477
+ };
7478
+ }
7479
+ function usePolicyCameraFramesFromMountedStreams(defaultOptions) {
7480
+ const mujoco = useMujoco();
7481
+ const [status, setStatus] = useState("idle");
7482
+ const [error, setError] = useState(null);
7483
+ const reset = useCallback(() => {
7484
+ setStatus("idle");
7485
+ setError(null);
7486
+ }, []);
7487
+ const capture = useCallback(
7488
+ async (options = {}) => {
7489
+ if (!mujoco.api) {
7490
+ throw new Error("MuJoCo scene is not ready for mounted policy camera capture.");
7491
+ }
7492
+ setStatus("capturing");
7493
+ setError(null);
7494
+ try {
7495
+ const result = await capturePolicyCameraFramesFromMountedStreams(
7496
+ mujoco.api,
7497
+ mergePolicyCameraFrameCaptureOptions(defaultOptions, options)
7498
+ );
7499
+ setStatus("captured");
7500
+ return result;
7501
+ } catch (nextError) {
7502
+ const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture mounted policy camera frames.");
7503
+ setError(error2);
7504
+ setStatus("error");
7505
+ throw error2;
7506
+ }
7507
+ },
7508
+ [defaultOptions, mujoco.api]
7509
+ );
7510
+ return {
7511
+ status,
7512
+ error,
7513
+ isCapturing: status === "capturing",
7514
+ capture,
7515
+ reset
7516
+ };
7517
+ }
6417
7518
  function useCameraSequenceRecorder() {
6418
7519
  const mujoco = useMujoco();
6419
7520
  const [status, setStatus] = useState("idle");
@@ -6534,6 +7635,38 @@ function useMountedCameraSequenceRecorder(defaultOptions = {}) {
6534
7635
  reset
6535
7636
  };
6536
7637
  }
7638
+
7639
+ // src/policyControls.ts
7640
+ function clampPolicyActionValue(model, actuatorIndex, value) {
7641
+ const ranges = model.actuator_ctrlrange;
7642
+ const min = ranges?.[actuatorIndex * 2] ?? -Infinity;
7643
+ const max = ranges?.[actuatorIndex * 2 + 1] ?? Infinity;
7644
+ return Math.max(min, Math.min(max, value));
7645
+ }
7646
+ function applyPolicyActionToControls(model, data, action, options = {}) {
7647
+ const actuatorOffset = options.actuatorOffset ?? 0;
7648
+ const actionSize = options.actionSize ?? action.length;
7649
+ const shouldClamp = options.clamp ?? true;
7650
+ const shouldSkipInvalid = options.skipInvalid ?? true;
7651
+ const count = Math.max(
7652
+ 0,
7653
+ Math.min(actionSize, action.length, data.ctrl.length - actuatorOffset, model.nu - actuatorOffset)
7654
+ );
7655
+ const applied = [];
7656
+ const skipped = [];
7657
+ for (let index = 0; index < count; index += 1) {
7658
+ const actuatorIndex = actuatorOffset + index;
7659
+ const value = Number(action[index]);
7660
+ if (shouldSkipInvalid && !Number.isFinite(value)) {
7661
+ skipped.push(actuatorIndex);
7662
+ continue;
7663
+ }
7664
+ const nextValue = shouldClamp ? clampPolicyActionValue(model, actuatorIndex, value) : value;
7665
+ data.ctrl[actuatorIndex] = nextValue;
7666
+ applied.push(nextValue);
7667
+ }
7668
+ return { applied, skipped, actuatorOffset };
7669
+ }
6537
7670
  function useCtrlNoise(config = {}) {
6538
7671
  const { mjModelRef } = useMujocoContext();
6539
7672
  const configRef = useRef(config);
@@ -6628,8 +7761,8 @@ function useCameraAnimation() {
6628
7761
  useFrame((state) => {
6629
7762
  const ca = cameraAnimRef.current;
6630
7763
  if (!ca.active) return;
6631
- const now = performance.now();
6632
- const progress = Math.min((now - ca.startTime) / ca.duration, 1);
7764
+ const now2 = performance.now();
7765
+ const progress = Math.min((now2 - ca.startTime) / ca.duration, 1);
6633
7766
  const ease = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
6634
7767
  camera.position.lerpVectors(ca.startPos, ca.endPos, ease);
6635
7768
  camera.quaternion.slerpQuaternions(ca.startRot, ca.endRot, ease);
@@ -6698,6 +7831,12 @@ function useCameraAnimation() {
6698
7831
  *
6699
7832
  * Helpers for resolving dataset camera streams to mounted MuJoCo resources.
6700
7833
  */
7834
+ /**
7835
+ * @license
7836
+ * SPDX-License-Identifier: Apache-2.0
7837
+ *
7838
+ * Project detector/image coordinates from a camera view into the rendered MuJoCo scene.
7839
+ */
6701
7840
  /**
6702
7841
  * @license
6703
7842
  * SPDX-License-Identifier: Apache-2.0
@@ -6795,12 +7934,30 @@ function useCameraAnimation() {
6795
7934
  *
6796
7935
  * useBodyState — per-body position/velocity tracking (spec 2.2)
6797
7936
  */
7937
+ /**
7938
+ * @license
7939
+ * SPDX-License-Identifier: Apache-2.0
7940
+ *
7941
+ * Ref-based world pose hooks for named MuJoCo bodies, geoms, and sites.
7942
+ */
6798
7943
  /**
6799
7944
  * @license
6800
7945
  * SPDX-License-Identifier: Apache-2.0
6801
7946
  *
6802
7947
  * useCtrl — handle-based read/write access to a named actuator's ctrl value (spec 3.1)
6803
7948
  */
7949
+ /**
7950
+ * @license
7951
+ * SPDX-License-Identifier: Apache-2.0
7952
+ *
7953
+ * Cooperative actuator/control ownership for policies, IK, teleop, and replay.
7954
+ */
7955
+ /**
7956
+ * @license
7957
+ * SPDX-License-Identifier: Apache-2.0
7958
+ *
7959
+ * Bounded contact history for rollout verification and debugging.
7960
+ */
6804
7961
  /**
6805
7962
  * @license
6806
7963
  * SPDX-License-Identifier: Apache-2.0
@@ -6813,6 +7970,24 @@ function useCameraAnimation() {
6813
7970
  *
6814
7971
  * usePolicy — policy decimation loop hook (spec 10.1)
6815
7972
  */
7973
+ /**
7974
+ * @license
7975
+ * SPDX-License-Identifier: Apache-2.0
7976
+ *
7977
+ * useRemotePolicy — HTTP JSON inference wrapper around usePolicy.
7978
+ */
7979
+ /**
7980
+ * @license
7981
+ * SPDX-License-Identifier: Apache-2.0
7982
+ *
7983
+ * Named policy observation builders with layout and units metadata.
7984
+ */
7985
+ /**
7986
+ * @license
7987
+ * SPDX-License-Identifier: Apache-2.0
7988
+ *
7989
+ * Stable React handle for named policy observations.
7990
+ */
6816
7991
  /**
6817
7992
  * @license
6818
7993
  * SPDX-License-Identifier: Apache-2.0
@@ -6837,6 +8012,18 @@ function useCameraAnimation() {
6837
8012
  *
6838
8013
  * React state wrapper around MuJoCo/R3F offscreen camera-frame capture.
6839
8014
  */
8015
+ /**
8016
+ * @license
8017
+ * SPDX-License-Identifier: Apache-2.0
8018
+ *
8019
+ * Helpers for turning Three/MuJoCo camera captures into policy image payloads.
8020
+ */
8021
+ /**
8022
+ * @license
8023
+ * SPDX-License-Identifier: Apache-2.0
8024
+ *
8025
+ * React wrapper for capturing policy image payloads from Three/MuJoCo cameras.
8026
+ */
6840
8027
  /**
6841
8028
  * @license
6842
8029
  * SPDX-License-Identifier: Apache-2.0
@@ -6849,6 +8036,12 @@ function useCameraAnimation() {
6849
8036
  *
6850
8037
  * React state wrapper for named MuJoCo camera/site/body sequence recording.
6851
8038
  */
8039
+ /**
8040
+ * @license
8041
+ * SPDX-License-Identifier: Apache-2.0
8042
+ *
8043
+ * Helpers for applying policy action vectors to MuJoCo controls.
8044
+ */
6852
8045
  /**
6853
8046
  * @license
6854
8047
  * SPDX-License-Identifier: Apache-2.0
@@ -6879,6 +8072,6 @@ function useCameraAnimation() {
6879
8072
  * useCameraAnimation — composable camera animation hook.
6880
8073
  */
6881
8074
 
6882
- export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MountedCameraFrameSequenceManifestStatus, MountedCameraFrameSequenceReadinessStatus, MountedCameraFrameSourceSuggestionMatch, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SplatCollisionProxyPreview, TendonRenderer, TrajectoryPlayer, buildObservation, canFetchSplatCollisionProxyXml, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, createMountedCameraFrameSequenceManifest, createMountedCameraFrameSequencePlan, createMountedCameraFrameSequencePlanFromApi, createMountedCameraFrameSequenceReadiness, createMountedCameraFrameSourceSuggestions, fetchSplatCollisionProxyXml, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getCameraFrameCaptureSourceTarget, getControlMap, getMountedCameraFrameCaptureSource, getName, isMountedCameraFrameCaptureSource, loadScene, parseSplatCollisionProxyGeoms, recordMountedCameraFrameSequence, resolveControlGroup, resolveMountedCameraFrameSource, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardIkTarget, useKeyboardTeleop, useMountedCameraSequenceRecorder, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useSplatCollisionProxyGeoms, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
8075
+ 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 };
6883
8076
  //# sourceMappingURL=index.js.map
6884
8077
  //# sourceMappingURL=index.js.map