mujoco-react 0.3.0 → 2.0.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,6 +1,6 @@
1
1
  import loadMujoco from 'mujoco-js';
2
2
  import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo } from 'react';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
4
  import { Canvas, useThree, useFrame } from '@react-three/fiber';
5
5
  import * as THREE11 from 'three';
6
6
  import { PivotControls } from '@react-three/drei';
@@ -67,6 +67,248 @@ function getContact(data, i) {
67
67
  return void 0;
68
68
  }
69
69
  }
70
+ var CapsuleGeometry = class extends THREE11.BufferGeometry {
71
+ parameters;
72
+ constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
73
+ super();
74
+ this.type = "CapsuleGeometry";
75
+ this.parameters = { radius, length, capSegments, radialSegments };
76
+ const path = new THREE11.Path();
77
+ path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
78
+ path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
79
+ const latheGeometry = new THREE11.LatheGeometry(path.getPoints(capSegments), radialSegments);
80
+ const self = this;
81
+ self.setIndex(latheGeometry.getIndex());
82
+ self.setAttribute("position", latheGeometry.getAttribute("position"));
83
+ self.setAttribute("normal", latheGeometry.getAttribute("normal"));
84
+ self.setAttribute("uv", latheGeometry.getAttribute("uv"));
85
+ }
86
+ };
87
+ var Reflector = class extends THREE11.Mesh {
88
+ isReflector = true;
89
+ camera;
90
+ reflectorPlane = new THREE11.Plane();
91
+ normal = new THREE11.Vector3();
92
+ reflectorWorldPosition = new THREE11.Vector3();
93
+ cameraWorldPosition = new THREE11.Vector3();
94
+ rotationMatrix = new THREE11.Matrix4();
95
+ lookAtPosition = new THREE11.Vector3(0, 0, -1);
96
+ clipPlane = new THREE11.Vector4();
97
+ view = new THREE11.Vector3();
98
+ target = new THREE11.Vector3();
99
+ q = new THREE11.Vector4();
100
+ textureMatrix = new THREE11.Matrix4();
101
+ virtualCamera;
102
+ renderTarget;
103
+ constructor(geometry, options = {}) {
104
+ super(geometry);
105
+ this.type = "Reflector";
106
+ this.camera = new THREE11.PerspectiveCamera();
107
+ const color = options.color !== void 0 ? new THREE11.Color(options.color) : new THREE11.Color(8355711);
108
+ const textureWidth = options.textureWidth || 512;
109
+ const textureHeight = options.textureHeight || 512;
110
+ const clipBias = options.clipBias || 0;
111
+ const multisample = options.multisample !== void 0 ? options.multisample : 4;
112
+ const blendTexture = options.texture || void 0;
113
+ const mixStrength = options.mixStrength !== void 0 ? options.mixStrength : 0.25;
114
+ this.virtualCamera = this.camera;
115
+ this.renderTarget = new THREE11.WebGLRenderTarget(textureWidth, textureHeight, {
116
+ samples: multisample,
117
+ type: THREE11.HalfFloatType
118
+ });
119
+ this.material = new THREE11.MeshPhysicalMaterial({
120
+ map: blendTexture,
121
+ color,
122
+ roughness: 0.5,
123
+ metalness: 0.1
124
+ });
125
+ this.material.onBeforeCompile = (shader) => {
126
+ shader.uniforms.tDiffuse = { value: this.renderTarget.texture };
127
+ shader.uniforms.textureMatrix = { value: this.textureMatrix };
128
+ shader.uniforms.mixStrength = { value: mixStrength };
129
+ const bodyStart = shader.vertexShader.indexOf("void main() {");
130
+ 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}";
131
+ const fragmentBodyStart = shader.fragmentShader.indexOf("void main() {");
132
+ 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}";
133
+ };
134
+ this.receiveShadow = true;
135
+ this.onBeforeRender = (renderer, scene, camera) => {
136
+ this.reflectorWorldPosition.setFromMatrixPosition(this.matrixWorld);
137
+ this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);
138
+ this.rotationMatrix.extractRotation(this.matrixWorld);
139
+ this.normal.set(0, 0, 1);
140
+ this.normal.applyMatrix4(this.rotationMatrix);
141
+ this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition);
142
+ if (this.view.dot(this.normal) > 0) return;
143
+ this.view.reflect(this.normal).negate();
144
+ this.view.add(this.reflectorWorldPosition);
145
+ this.rotationMatrix.extractRotation(camera.matrixWorld);
146
+ this.lookAtPosition.set(0, 0, -1);
147
+ this.lookAtPosition.applyMatrix4(this.rotationMatrix);
148
+ this.lookAtPosition.add(this.cameraWorldPosition);
149
+ this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition);
150
+ this.target.reflect(this.normal).negate();
151
+ this.target.add(this.reflectorWorldPosition);
152
+ this.virtualCamera.position.copy(this.view);
153
+ this.virtualCamera.up.set(0, 1, 0);
154
+ this.virtualCamera.up.applyMatrix4(this.rotationMatrix);
155
+ this.virtualCamera.up.reflect(this.normal);
156
+ this.virtualCamera.lookAt(this.target);
157
+ this.virtualCamera.far = camera.far;
158
+ this.virtualCamera.updateMatrixWorld();
159
+ this.virtualCamera.projectionMatrix.copy(camera.projectionMatrix);
160
+ this.textureMatrix.set(
161
+ 0.5,
162
+ 0,
163
+ 0,
164
+ 0.5,
165
+ 0,
166
+ 0.5,
167
+ 0,
168
+ 0.5,
169
+ 0,
170
+ 0,
171
+ 0.5,
172
+ 0.5,
173
+ 0,
174
+ 0,
175
+ 0,
176
+ 1
177
+ );
178
+ this.textureMatrix.multiply(this.virtualCamera.projectionMatrix);
179
+ this.textureMatrix.multiply(this.virtualCamera.matrixWorldInverse);
180
+ this.textureMatrix.multiply(this.matrixWorld);
181
+ this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition);
182
+ this.reflectorPlane.applyMatrix4(this.virtualCamera.matrixWorldInverse);
183
+ this.clipPlane.set(this.reflectorPlane.normal.x, this.reflectorPlane.normal.y, this.reflectorPlane.normal.z, this.reflectorPlane.constant);
184
+ const projectionMatrix = this.virtualCamera.projectionMatrix;
185
+ this.q.x = (Math.sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
186
+ this.q.y = (Math.sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
187
+ this.q.z = -1;
188
+ this.q.w = (1 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];
189
+ this.clipPlane.multiplyScalar(2 / this.clipPlane.dot(this.q));
190
+ projectionMatrix.elements[2] = this.clipPlane.x;
191
+ projectionMatrix.elements[6] = this.clipPlane.y;
192
+ projectionMatrix.elements[10] = this.clipPlane.z + 1 - clipBias;
193
+ projectionMatrix.elements[14] = this.clipPlane.w;
194
+ this.visible = false;
195
+ const currentRenderTarget = renderer.getRenderTarget();
196
+ const currentXrEnabled = renderer.xr.enabled;
197
+ const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
198
+ renderer.xr.enabled = false;
199
+ renderer.shadowMap.autoUpdate = false;
200
+ renderer.setRenderTarget(this.renderTarget);
201
+ renderer.state.buffers.depth.setMask(true);
202
+ if (renderer.autoClear === false) renderer.clear();
203
+ renderer.render(scene, this.virtualCamera);
204
+ renderer.xr.enabled = currentXrEnabled;
205
+ renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
206
+ renderer.setRenderTarget(currentRenderTarget);
207
+ const viewport = camera.viewport;
208
+ if (viewport !== void 0) {
209
+ renderer.state.viewport(viewport);
210
+ }
211
+ this.visible = true;
212
+ };
213
+ }
214
+ getRenderTarget() {
215
+ return this.renderTarget;
216
+ }
217
+ dispose() {
218
+ this.renderTarget.dispose();
219
+ const mesh = this;
220
+ if (Array.isArray(mesh.material)) {
221
+ mesh.material.forEach((m) => m.dispose());
222
+ } else {
223
+ mesh.material.dispose();
224
+ }
225
+ }
226
+ };
227
+
228
+ // src/rendering/GeomBuilder.ts
229
+ var GeomBuilder = class {
230
+ mujoco;
231
+ constructor(mujoco) {
232
+ this.mujoco = mujoco;
233
+ }
234
+ /**
235
+ * Creates a Three.js Object3D (usually a Mesh) for a specific geometry in the MuJoCo model.
236
+ * Returns null if the geometry shouldn't be rendered (e.g., invisible collision triggers).
237
+ */
238
+ create(mjModel, g) {
239
+ if (mjModel.geom_group[g] === 3) return null;
240
+ const type = mjModel.geom_type[g];
241
+ const size = mjModel.geom_size.subarray(g * 3, g * 3 + 3);
242
+ const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
243
+ const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
244
+ const matId = mjModel.geom_matid[g];
245
+ const color = new THREE11.Color(16777215);
246
+ let opacity = 1;
247
+ if (matId >= 0) {
248
+ const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
249
+ color.setRGB(rgba[0], rgba[1], rgba[2]);
250
+ opacity = rgba[3];
251
+ } else {
252
+ const rgba = mjModel.geom_rgba.subarray(g * 4, g * 4 + 4);
253
+ color.setRGB(rgba[0], rgba[1], rgba[2]);
254
+ opacity = rgba[3];
255
+ }
256
+ const MG = this.mujoco.mjtGeom;
257
+ let geo = null;
258
+ const getVal = (v) => v?.value ?? v;
259
+ if (type === getVal(MG.mjGEOM_PLANE)) {
260
+ geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
261
+ } else if (type === getVal(MG.mjGEOM_SPHERE)) {
262
+ geo = new THREE11.SphereGeometry(size[0], 24, 24);
263
+ } else if (type === getVal(MG.mjGEOM_CAPSULE)) {
264
+ geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
265
+ geo.rotateX(Math.PI / 2);
266
+ } else if (type === getVal(MG.mjGEOM_BOX)) {
267
+ geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
268
+ } else if (type === getVal(MG.mjGEOM_CYLINDER)) {
269
+ geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
270
+ geo.rotateX(Math.PI / 2);
271
+ } else if (type === getVal(MG.mjGEOM_MESH)) {
272
+ const mId = mjModel.geom_dataid[g];
273
+ const vAdr = mjModel.mesh_vertadr[mId];
274
+ const vNum = mjModel.mesh_vertnum[mId];
275
+ const fAdr = mjModel.mesh_faceadr[mId];
276
+ const fNum = mjModel.mesh_facenum[mId];
277
+ geo = new THREE11.BufferGeometry();
278
+ geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
279
+ geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
280
+ geo.computeVertexNormals();
281
+ }
282
+ if (geo) {
283
+ let mesh;
284
+ if (type === getVal(MG.mjGEOM_PLANE)) {
285
+ mesh = new Reflector(geo, {
286
+ clipBias: 3e-3,
287
+ textureWidth: 1024,
288
+ textureHeight: 1024,
289
+ color,
290
+ mixStrength: 0.25
291
+ });
292
+ } else {
293
+ mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
294
+ color,
295
+ transparent: opacity < 1,
296
+ opacity,
297
+ roughness: 0.6,
298
+ metalness: 0.2
299
+ }));
300
+ mesh.castShadow = true;
301
+ mesh.receiveShadow = true;
302
+ }
303
+ mesh.position.set(pos[0], pos[1], pos[2]);
304
+ mesh.quaternion.set(quat[1], quat[2], quat[3], quat[0]);
305
+ mesh.userData.bodyID = mjModel.geom_bodyid[g];
306
+ mesh.userData.geomID = g;
307
+ return mesh;
308
+ }
309
+ return null;
310
+ }
311
+ };
70
312
 
71
313
  // src/core/SceneLoader.ts
72
314
  function getName(mjModel, address) {
@@ -225,8 +467,6 @@ async function loadScene(mujoco, config, onProgress) {
225
467
  onProgress?.("Loading model...");
226
468
  const mjModel = mujoco.MjModel.loadFromXML(`/working/${config.sceneFile}`);
227
469
  const mjData = new mujoco.MjData(mjModel);
228
- const siteId = findSiteByName(mjModel, config.tcpSiteName ?? "tcp");
229
- const gripperId = findActuatorByName(mjModel, config.gripperActuatorName ?? "gripper");
230
470
  if (config.homeJoints) {
231
471
  const homeCount = Math.min(config.homeJoints.length, mjModel.nu);
232
472
  for (let i = 0; i < homeCount; i++) {
@@ -238,7 +478,7 @@ async function loadScene(mujoco, config, onProgress) {
238
478
  }
239
479
  }
240
480
  mujoco.mj_forward(mjModel, mjData);
241
- return { mjModel, mjData, siteId, gripperId };
481
+ return { mjModel, mjData };
242
482
  }
243
483
  function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
244
484
  const xmlDoc = parser.parseFromString(xmlString, "text/xml");
@@ -267,20 +507,98 @@ function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
267
507
  if (!downloaded.has(fullPath)) queue.push(fullPath);
268
508
  });
269
509
  }
270
- var JOINT_TYPE_NAMES = ["free", "ball", "slide", "hinge"];
271
- var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
272
- var SENSOR_TYPE_NAMES = {
273
- 0: "touch",
274
- 1: "accelerometer",
275
- 2: "velocimeter",
276
- 3: "gyro",
277
- 4: "force",
278
- 5: "torque",
279
- 6: "magnetometer",
280
- 7: "rangefinder",
281
- 8: "camprojection",
282
- 9: "jointpos",
283
- 10: "jointvel",
510
+ function SceneRenderer(props) {
511
+ const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, status } = useMujocoSim();
512
+ const groupRef = useRef(null);
513
+ const bodyRefs = useRef([]);
514
+ const prevModelRef = useRef(null);
515
+ const geomBuilder = useMemo(() => {
516
+ if (status !== "ready") return null;
517
+ return new GeomBuilder(mujocoRef.current);
518
+ }, [status, mujocoRef]);
519
+ useEffect(() => {
520
+ if (status !== "ready" || !geomBuilder) return;
521
+ const model = mjModelRef.current;
522
+ const group = groupRef.current;
523
+ if (!model || !group) return;
524
+ if (prevModelRef.current === model) return;
525
+ prevModelRef.current = model;
526
+ while (group.children.length > 0) {
527
+ group.remove(group.children[0]);
528
+ }
529
+ const refs = [];
530
+ for (let i = 0; i < model.nbody; i++) {
531
+ const bodyGroup = new THREE11.Group();
532
+ bodyGroup.userData.bodyID = i;
533
+ for (let g = 0; g < model.ngeom; g++) {
534
+ if (model.geom_bodyid[g] === i) {
535
+ const mesh = geomBuilder.create(model, g);
536
+ if (mesh) bodyGroup.add(mesh);
537
+ }
538
+ }
539
+ group.add(bodyGroup);
540
+ refs.push(bodyGroup);
541
+ }
542
+ bodyRefs.current = refs;
543
+ }, [status, geomBuilder, mjModelRef]);
544
+ useFrame(() => {
545
+ const data = mjDataRef.current;
546
+ if (!data) return;
547
+ const bodies = bodyRefs.current;
548
+ for (let i = 0; i < bodies.length; i++) {
549
+ const ref = bodies[i];
550
+ if (!ref) continue;
551
+ ref.position.set(
552
+ data.xpos[i * 3],
553
+ data.xpos[i * 3 + 1],
554
+ data.xpos[i * 3 + 2]
555
+ );
556
+ ref.quaternion.set(
557
+ data.xquat[i * 4 + 1],
558
+ data.xquat[i * 4 + 2],
559
+ data.xquat[i * 4 + 3],
560
+ data.xquat[i * 4]
561
+ );
562
+ }
563
+ });
564
+ return /* @__PURE__ */ jsx(
565
+ "group",
566
+ {
567
+ ...props,
568
+ ref: groupRef,
569
+ onDoubleClick: (e) => {
570
+ if (typeof props.onDoubleClick === "function") props.onDoubleClick(e);
571
+ e.stopPropagation();
572
+ let obj = e.object;
573
+ while (obj && obj.userData.bodyID === void 0 && obj.parent) {
574
+ obj = obj.parent;
575
+ }
576
+ const bodyID = obj?.userData.bodyID;
577
+ if (typeof bodyID === "number" && bodyID > 0) {
578
+ const model = mjModelRef.current;
579
+ if (model && bodyID < model.nbody && onSelectionRef.current) {
580
+ const name = getName(model, model.name_bodyadr[bodyID]);
581
+ onSelectionRef.current(bodyID, name);
582
+ }
583
+ }
584
+ }
585
+ }
586
+ );
587
+ }
588
+ var JOINT_TYPE_NAMES = ["free", "ball", "slide", "hinge"];
589
+ var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
590
+ var SENSOR_TYPE_NAMES = {
591
+ 0: "touch",
592
+ 1: "accelerometer",
593
+ 2: "velocimeter",
594
+ 3: "gyro",
595
+ 4: "force",
596
+ 5: "torque",
597
+ 6: "magnetometer",
598
+ 7: "rangefinder",
599
+ 8: "camprojection",
600
+ 9: "jointpos",
601
+ 10: "jointvel",
284
602
  11: "tendonpos",
285
603
  12: "tendonvel",
286
604
  13: "actuatorpos",
@@ -372,7 +690,6 @@ function MujocoSimProvider({
372
690
  substeps,
373
691
  paused,
374
692
  speed,
375
- interpolate,
376
693
  children
377
694
  }) {
378
695
  const { gl, camera } = useThree();
@@ -384,12 +701,8 @@ function MujocoSimProvider({
384
701
  const pausedRef = useRef(paused ?? false);
385
702
  const speedRef = useRef(speed ?? 1);
386
703
  const substepsRef = useRef(substeps ?? 1);
387
- const interpolateRef = useRef(interpolate ?? false);
388
704
  const stepsToRunRef = useRef(0);
389
705
  const loadGenRef = useRef(0);
390
- useRef(null);
391
- useRef(null);
392
- useRef(0);
393
706
  const onSelectionRef = useRef(onSelection);
394
707
  onSelectionRef.current = onSelection;
395
708
  const onStepRef = useRef(onStep);
@@ -407,9 +720,6 @@ function MujocoSimProvider({
407
720
  useEffect(() => {
408
721
  substepsRef.current = substeps ?? 1;
409
722
  }, [substeps]);
410
- useEffect(() => {
411
- interpolateRef.current = interpolate ?? false;
412
- }, [interpolate]);
413
723
  useEffect(() => {
414
724
  if (!gravity) return;
415
725
  const model = mjModelRef.current;
@@ -477,7 +787,7 @@ function MujocoSimProvider({
477
787
  }
478
788
  }
479
789
  }, [status]);
480
- useFrame(() => {
790
+ useFrame((_state, delta) => {
481
791
  const model = mjModelRef.current;
482
792
  const data = mjDataRef.current;
483
793
  if (!model || !data) return;
@@ -497,7 +807,8 @@ function MujocoSimProvider({
497
807
  stepsToRunRef.current = 0;
498
808
  } else {
499
809
  const startSimTime = data.time;
500
- const frameTime = 1 / 60 * speedRef.current;
810
+ const clampedDelta = Math.min(delta, 1 / 15);
811
+ const frameTime = clampedDelta * speedRef.current;
501
812
  while (data.time - startSimTime < frameTime) {
502
813
  for (let s = 0; s < numSubsteps; s++) {
503
814
  mujoco.mj_step(model, data);
@@ -1104,7 +1415,10 @@ function MujocoSimProvider({
1104
1415
  }),
1105
1416
  [api, status]
1106
1417
  );
1107
- return /* @__PURE__ */ jsx(MujocoSimContext.Provider, { value: contextValue, children });
1418
+ return /* @__PURE__ */ jsxs(MujocoSimContext.Provider, { value: contextValue, children: [
1419
+ /* @__PURE__ */ jsx(SceneRenderer, {}),
1420
+ children
1421
+ ] });
1108
1422
  }
1109
1423
  var MujocoCanvas = forwardRef(
1110
1424
  function MujocoCanvas2({
@@ -1113,15 +1427,12 @@ var MujocoCanvas = forwardRef(
1113
1427
  onError,
1114
1428
  onStep,
1115
1429
  onSelection,
1116
- // Declarative physics config (spec 1.1)
1430
+ // Declarative physics config
1117
1431
  gravity,
1118
1432
  timestep,
1119
1433
  substeps,
1120
1434
  paused,
1121
1435
  speed,
1122
- interpolate,
1123
- gravityCompensation,
1124
- mjcfLights,
1125
1436
  children,
1126
1437
  ...canvasProps
1127
1438
  }, ref) {
@@ -1149,12 +1460,34 @@ var MujocoCanvas = forwardRef(
1149
1460
  substeps,
1150
1461
  paused,
1151
1462
  speed,
1152
- interpolate,
1153
1463
  children
1154
1464
  }
1155
1465
  ) });
1156
1466
  }
1157
1467
  );
1468
+ var MujocoPhysics = forwardRef(
1469
+ function MujocoPhysics2({ onError, children, ...props }, ref) {
1470
+ const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
1471
+ useEffect(() => {
1472
+ if (wasmStatus === "error" && onError) {
1473
+ onError(new Error(wasmError ?? "WASM load failed"));
1474
+ }
1475
+ }, [wasmStatus, wasmError, onError]);
1476
+ if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
1477
+ return null;
1478
+ }
1479
+ return /* @__PURE__ */ jsx(
1480
+ MujocoSimProvider,
1481
+ {
1482
+ mujoco,
1483
+ apiRef: ref,
1484
+ onError,
1485
+ ...props,
1486
+ children
1487
+ }
1488
+ );
1489
+ }
1490
+ );
1158
1491
  function shallowEqual(a, b) {
1159
1492
  const keysA = Object.keys(a);
1160
1493
  const keysB = Object.keys(b);
@@ -1437,526 +1770,208 @@ function syncGizmoToSite(data, siteId, target) {
1437
1770
  0,
1438
1771
  siteMat[6],
1439
1772
  siteMat[7],
1440
- siteMat[8],
1441
- 0,
1442
- 0,
1443
- 0,
1444
- 0,
1445
- 1
1446
- );
1447
- target.quaternion.setFromRotationMatrix(_syncMat4);
1448
- }
1449
- function IkControllerImpl({
1450
- config,
1451
- children
1452
- }) {
1453
- const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } = useMujocoSim();
1454
- const ikEnabledRef = useRef(false);
1455
- const ikCalculatingRef = useRef(false);
1456
- const ikTargetRef = useRef(new THREE11.Group());
1457
- const siteIdRef = useRef(-1);
1458
- const genericIkRef = useRef(new GenericIK(mujocoRef.current));
1459
- const firstIkEnableRef = useRef(true);
1460
- const needsInitialSync = useRef(true);
1461
- const gizmoAnimRef = useRef({
1462
- active: false,
1463
- startPos: new THREE11.Vector3(),
1464
- endPos: new THREE11.Vector3(),
1465
- startRot: new THREE11.Quaternion(),
1466
- endRot: new THREE11.Quaternion(),
1467
- startTime: 0,
1468
- duration: 1e3
1469
- });
1470
- useEffect(() => {
1471
- const model = mjModelRef.current;
1472
- if (!model || status !== "ready") {
1473
- siteIdRef.current = -1;
1474
- return;
1475
- }
1476
- siteIdRef.current = findSiteByName(model, config.siteName);
1477
- const data = mjDataRef.current;
1478
- if (data && ikTargetRef.current) {
1479
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1480
- }
1481
- }, [config.siteName, status, mjModelRef, mjDataRef]);
1482
- const ikSolveFn = useCallback(
1483
- (pos, quat, currentQ) => {
1484
- if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
1485
- const model = mjModelRef.current;
1486
- const data = mjDataRef.current;
1487
- if (!model || !data || siteIdRef.current === -1) return null;
1488
- return genericIkRef.current.solve(
1489
- model,
1490
- data,
1491
- siteIdRef.current,
1492
- config.numJoints,
1493
- pos,
1494
- quat,
1495
- currentQ,
1496
- {
1497
- damping: config.damping,
1498
- maxIterations: config.maxIterations
1499
- }
1500
- );
1501
- },
1502
- [config.ikSolveFn, config.numJoints, config.damping, config.maxIterations, mjModelRef, mjDataRef]
1503
- );
1504
- const ikSolveFnRef = useRef(ikSolveFn);
1505
- ikSolveFnRef.current = ikSolveFn;
1506
- useFrame(() => {
1507
- if (needsInitialSync.current && siteIdRef.current !== -1) {
1508
- const data = mjDataRef.current;
1509
- if (data && ikTargetRef.current) {
1510
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1511
- needsInitialSync.current = false;
1512
- }
1513
- }
1514
- const ga = gizmoAnimRef.current;
1515
- const target = ikTargetRef.current;
1516
- if (!ga.active || !target) return;
1517
- const now = performance.now();
1518
- const elapsed = now - ga.startTime;
1519
- const t = Math.min(elapsed / ga.duration, 1);
1520
- const ease = 1 - Math.pow(1 - t, 3);
1521
- target.position.lerpVectors(ga.startPos, ga.endPos, ease);
1522
- target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
1523
- if (t >= 1) ga.active = false;
1524
- });
1525
- useBeforePhysicsStep((model, data) => {
1526
- if (!ikEnabledRef.current) {
1527
- ikCalculatingRef.current = false;
1528
- return;
1529
- }
1530
- const target = ikTargetRef.current;
1531
- if (!target) return;
1532
- ikCalculatingRef.current = true;
1533
- const numJoints = config.numJoints;
1534
- const currentQ = [];
1535
- for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
1536
- const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
1537
- if (solution) {
1538
- for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
1539
- }
1540
- });
1541
- useEffect(() => {
1542
- const cb = () => {
1543
- const data = mjDataRef.current;
1544
- if (data && ikTargetRef.current) {
1545
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1546
- }
1547
- gizmoAnimRef.current.active = false;
1548
- firstIkEnableRef.current = true;
1549
- ikEnabledRef.current = false;
1550
- needsInitialSync.current = true;
1551
- };
1552
- resetCallbacks.current.add(cb);
1553
- return () => {
1554
- resetCallbacks.current.delete(cb);
1555
- };
1556
- }, [resetCallbacks, mjDataRef]);
1557
- const setIkEnabled = useCallback(
1558
- (enabled) => {
1559
- ikEnabledRef.current = enabled;
1560
- const data = mjDataRef.current;
1561
- if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
1562
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1563
- firstIkEnableRef.current = false;
1564
- }
1565
- },
1566
- [mjDataRef]
1567
- );
1568
- const syncTargetToSiteApi = useCallback(() => {
1569
- const data = mjDataRef.current;
1570
- const target = ikTargetRef.current;
1571
- if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
1572
- }, [mjDataRef]);
1573
- const solveIK = useCallback(
1574
- (pos, quat, currentQ) => {
1575
- return ikSolveFnRef.current(pos, quat, currentQ);
1576
- },
1577
- []
1578
- );
1579
- const moveTarget = useCallback(
1580
- (pos, duration = 0) => {
1581
- if (!ikEnabledRef.current) setIkEnabled(true);
1582
- const target = ikTargetRef.current;
1583
- if (!target) return;
1584
- const targetPos = pos.clone();
1585
- const targetRot = new THREE11.Quaternion().setFromEuler(
1586
- new THREE11.Euler(Math.PI, 0, 0)
1587
- );
1588
- if (duration > 0) {
1589
- const ga = gizmoAnimRef.current;
1590
- ga.active = true;
1591
- ga.startPos.copy(target.position);
1592
- ga.endPos.copy(targetPos);
1593
- ga.startRot.copy(target.quaternion);
1594
- ga.endRot.copy(targetRot);
1595
- ga.startTime = performance.now();
1596
- ga.duration = duration;
1597
- } else {
1598
- gizmoAnimRef.current.active = false;
1599
- target.position.copy(targetPos);
1600
- target.quaternion.copy(targetRot);
1601
- }
1602
- },
1603
- [setIkEnabled]
1604
- );
1605
- const getGizmoStats = useCallback(
1606
- () => {
1607
- const target = ikTargetRef.current;
1608
- if (!ikCalculatingRef.current || !target) return null;
1609
- return {
1610
- pos: target.position.clone(),
1611
- rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
1612
- };
1613
- },
1614
- []
1615
- );
1616
- const contextValue = useMemo(
1617
- () => ({
1618
- ikEnabledRef,
1619
- ikCalculatingRef,
1620
- ikTargetRef,
1621
- siteIdRef,
1622
- setIkEnabled,
1623
- moveTarget,
1624
- syncTargetToSite: syncTargetToSiteApi,
1625
- solveIK,
1626
- getGizmoStats
1627
- }),
1628
- [setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats]
1629
- );
1630
- return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
1631
- }
1632
- var IkController = createController(
1633
- {
1634
- name: "IkController",
1635
- defaultConfig: {
1636
- damping: 0.01,
1637
- maxIterations: 50
1638
- }
1639
- },
1640
- IkControllerImpl
1641
- );
1642
- var CapsuleGeometry = class extends THREE11.BufferGeometry {
1643
- parameters;
1644
- constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
1645
- super();
1646
- this.type = "CapsuleGeometry";
1647
- this.parameters = { radius, length, capSegments, radialSegments };
1648
- const path = new THREE11.Path();
1649
- path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
1650
- path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
1651
- const latheGeometry = new THREE11.LatheGeometry(path.getPoints(capSegments), radialSegments);
1652
- const self = this;
1653
- self.setIndex(latheGeometry.getIndex());
1654
- self.setAttribute("position", latheGeometry.getAttribute("position"));
1655
- self.setAttribute("normal", latheGeometry.getAttribute("normal"));
1656
- self.setAttribute("uv", latheGeometry.getAttribute("uv"));
1657
- }
1658
- };
1659
- var Reflector = class extends THREE11.Mesh {
1660
- isReflector = true;
1661
- camera;
1662
- reflectorPlane = new THREE11.Plane();
1663
- normal = new THREE11.Vector3();
1664
- reflectorWorldPosition = new THREE11.Vector3();
1665
- cameraWorldPosition = new THREE11.Vector3();
1666
- rotationMatrix = new THREE11.Matrix4();
1667
- lookAtPosition = new THREE11.Vector3(0, 0, -1);
1668
- clipPlane = new THREE11.Vector4();
1669
- view = new THREE11.Vector3();
1670
- target = new THREE11.Vector3();
1671
- q = new THREE11.Vector4();
1672
- textureMatrix = new THREE11.Matrix4();
1673
- virtualCamera;
1674
- renderTarget;
1675
- constructor(geometry, options = {}) {
1676
- super(geometry);
1677
- this.type = "Reflector";
1678
- this.camera = new THREE11.PerspectiveCamera();
1679
- const color = options.color !== void 0 ? new THREE11.Color(options.color) : new THREE11.Color(8355711);
1680
- const textureWidth = options.textureWidth || 512;
1681
- const textureHeight = options.textureHeight || 512;
1682
- const clipBias = options.clipBias || 0;
1683
- const multisample = options.multisample !== void 0 ? options.multisample : 4;
1684
- const blendTexture = options.texture || void 0;
1685
- const mixStrength = options.mixStrength !== void 0 ? options.mixStrength : 0.25;
1686
- this.virtualCamera = this.camera;
1687
- this.renderTarget = new THREE11.WebGLRenderTarget(textureWidth, textureHeight, {
1688
- samples: multisample,
1689
- type: THREE11.HalfFloatType
1690
- });
1691
- this.material = new THREE11.MeshPhysicalMaterial({
1692
- map: blendTexture,
1693
- color,
1694
- roughness: 0.5,
1695
- metalness: 0.1
1696
- });
1697
- this.material.onBeforeCompile = (shader) => {
1698
- shader.uniforms.tDiffuse = { value: this.renderTarget.texture };
1699
- shader.uniforms.textureMatrix = { value: this.textureMatrix };
1700
- shader.uniforms.mixStrength = { value: mixStrength };
1701
- const bodyStart = shader.vertexShader.indexOf("void main() {");
1702
- 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}";
1703
- const fragmentBodyStart = shader.fragmentShader.indexOf("void main() {");
1704
- 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}";
1705
- };
1706
- this.receiveShadow = true;
1707
- this.onBeforeRender = (renderer, scene, camera) => {
1708
- this.reflectorWorldPosition.setFromMatrixPosition(this.matrixWorld);
1709
- this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);
1710
- this.rotationMatrix.extractRotation(this.matrixWorld);
1711
- this.normal.set(0, 0, 1);
1712
- this.normal.applyMatrix4(this.rotationMatrix);
1713
- this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition);
1714
- if (this.view.dot(this.normal) > 0) return;
1715
- this.view.reflect(this.normal).negate();
1716
- this.view.add(this.reflectorWorldPosition);
1717
- this.rotationMatrix.extractRotation(camera.matrixWorld);
1718
- this.lookAtPosition.set(0, 0, -1);
1719
- this.lookAtPosition.applyMatrix4(this.rotationMatrix);
1720
- this.lookAtPosition.add(this.cameraWorldPosition);
1721
- this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition);
1722
- this.target.reflect(this.normal).negate();
1723
- this.target.add(this.reflectorWorldPosition);
1724
- this.virtualCamera.position.copy(this.view);
1725
- this.virtualCamera.up.set(0, 1, 0);
1726
- this.virtualCamera.up.applyMatrix4(this.rotationMatrix);
1727
- this.virtualCamera.up.reflect(this.normal);
1728
- this.virtualCamera.lookAt(this.target);
1729
- this.virtualCamera.far = camera.far;
1730
- this.virtualCamera.updateMatrixWorld();
1731
- this.virtualCamera.projectionMatrix.copy(camera.projectionMatrix);
1732
- this.textureMatrix.set(
1733
- 0.5,
1734
- 0,
1735
- 0,
1736
- 0.5,
1737
- 0,
1738
- 0.5,
1739
- 0,
1740
- 0.5,
1741
- 0,
1742
- 0,
1743
- 0.5,
1744
- 0.5,
1745
- 0,
1746
- 0,
1747
- 0,
1748
- 1
1749
- );
1750
- this.textureMatrix.multiply(this.virtualCamera.projectionMatrix);
1751
- this.textureMatrix.multiply(this.virtualCamera.matrixWorldInverse);
1752
- this.textureMatrix.multiply(this.matrixWorld);
1753
- this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition);
1754
- this.reflectorPlane.applyMatrix4(this.virtualCamera.matrixWorldInverse);
1755
- this.clipPlane.set(this.reflectorPlane.normal.x, this.reflectorPlane.normal.y, this.reflectorPlane.normal.z, this.reflectorPlane.constant);
1756
- const projectionMatrix = this.virtualCamera.projectionMatrix;
1757
- this.q.x = (Math.sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
1758
- this.q.y = (Math.sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
1759
- this.q.z = -1;
1760
- this.q.w = (1 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];
1761
- this.clipPlane.multiplyScalar(2 / this.clipPlane.dot(this.q));
1762
- projectionMatrix.elements[2] = this.clipPlane.x;
1763
- projectionMatrix.elements[6] = this.clipPlane.y;
1764
- projectionMatrix.elements[10] = this.clipPlane.z + 1 - clipBias;
1765
- projectionMatrix.elements[14] = this.clipPlane.w;
1766
- this.visible = false;
1767
- const currentRenderTarget = renderer.getRenderTarget();
1768
- const currentXrEnabled = renderer.xr.enabled;
1769
- const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
1770
- renderer.xr.enabled = false;
1771
- renderer.shadowMap.autoUpdate = false;
1772
- renderer.setRenderTarget(this.renderTarget);
1773
- renderer.state.buffers.depth.setMask(true);
1774
- if (renderer.autoClear === false) renderer.clear();
1775
- renderer.render(scene, this.virtualCamera);
1776
- renderer.xr.enabled = currentXrEnabled;
1777
- renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
1778
- renderer.setRenderTarget(currentRenderTarget);
1779
- const viewport = camera.viewport;
1780
- if (viewport !== void 0) {
1781
- renderer.state.viewport(viewport);
1782
- }
1783
- this.visible = true;
1784
- };
1785
- }
1786
- getRenderTarget() {
1787
- return this.renderTarget;
1788
- }
1789
- dispose() {
1790
- this.renderTarget.dispose();
1791
- const mesh = this;
1792
- if (Array.isArray(mesh.material)) {
1793
- mesh.material.forEach((m) => m.dispose());
1794
- } else {
1795
- mesh.material.dispose();
1796
- }
1797
- }
1798
- };
1799
-
1800
- // src/rendering/GeomBuilder.ts
1801
- var GeomBuilder = class {
1802
- mujoco;
1803
- constructor(mujoco) {
1804
- this.mujoco = mujoco;
1805
- }
1806
- /**
1807
- * Creates a Three.js Object3D (usually a Mesh) for a specific geometry in the MuJoCo model.
1808
- * Returns null if the geometry shouldn't be rendered (e.g., invisible collision triggers).
1809
- */
1810
- create(mjModel, g) {
1811
- if (mjModel.geom_group[g] === 3) return null;
1812
- const type = mjModel.geom_type[g];
1813
- const size = mjModel.geom_size.subarray(g * 3, g * 3 + 3);
1814
- const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
1815
- const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
1816
- const matId = mjModel.geom_matid[g];
1817
- const color = new THREE11.Color(16777215);
1818
- let opacity = 1;
1819
- if (matId >= 0) {
1820
- const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
1821
- color.setRGB(rgba[0], rgba[1], rgba[2]);
1822
- opacity = rgba[3];
1823
- } else {
1824
- const rgba = mjModel.geom_rgba.subarray(g * 4, g * 4 + 4);
1825
- color.setRGB(rgba[0], rgba[1], rgba[2]);
1826
- opacity = rgba[3];
1827
- }
1828
- const MG = this.mujoco.mjtGeom;
1829
- let geo = null;
1830
- const getVal = (v) => v?.value ?? v;
1831
- if (type === getVal(MG.mjGEOM_PLANE)) {
1832
- geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
1833
- } else if (type === getVal(MG.mjGEOM_SPHERE)) {
1834
- geo = new THREE11.SphereGeometry(size[0], 24, 24);
1835
- } else if (type === getVal(MG.mjGEOM_CAPSULE)) {
1836
- geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
1837
- geo.rotateX(Math.PI / 2);
1838
- } else if (type === getVal(MG.mjGEOM_BOX)) {
1839
- geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
1840
- } else if (type === getVal(MG.mjGEOM_CYLINDER)) {
1841
- geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
1842
- geo.rotateX(Math.PI / 2);
1843
- } else if (type === getVal(MG.mjGEOM_MESH)) {
1844
- const mId = mjModel.geom_dataid[g];
1845
- const vAdr = mjModel.mesh_vertadr[mId];
1846
- const vNum = mjModel.mesh_vertnum[mId];
1847
- const fAdr = mjModel.mesh_faceadr[mId];
1848
- const fNum = mjModel.mesh_facenum[mId];
1849
- geo = new THREE11.BufferGeometry();
1850
- geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
1851
- geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
1852
- geo.computeVertexNormals();
1853
- }
1854
- if (geo) {
1855
- let mesh;
1856
- if (type === getVal(MG.mjGEOM_PLANE)) {
1857
- mesh = new Reflector(geo, {
1858
- clipBias: 3e-3,
1859
- textureWidth: 1024,
1860
- textureHeight: 1024,
1861
- color,
1862
- mixStrength: 0.25
1863
- });
1864
- } else {
1865
- mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
1866
- color,
1867
- transparent: opacity < 1,
1868
- opacity,
1869
- roughness: 0.6,
1870
- metalness: 0.2
1871
- }));
1872
- mesh.castShadow = true;
1873
- mesh.receiveShadow = true;
1874
- }
1875
- mesh.position.set(pos[0], pos[1], pos[2]);
1876
- mesh.quaternion.set(quat[1], quat[2], quat[3], quat[0]);
1877
- mesh.userData.bodyID = mjModel.geom_bodyid[g];
1878
- mesh.userData.geomID = g;
1879
- return mesh;
1880
- }
1881
- return null;
1882
- }
1883
- };
1884
- function SceneRenderer() {
1885
- const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, status } = useMujocoSim();
1886
- const groupRef = useRef(null);
1887
- const bodyRefs = useRef([]);
1888
- const prevModelRef = useRef(null);
1889
- const geomBuilder = useMemo(() => {
1890
- if (status !== "ready") return null;
1891
- return new GeomBuilder(mujocoRef.current);
1892
- }, [status, mujocoRef]);
1773
+ siteMat[8],
1774
+ 0,
1775
+ 0,
1776
+ 0,
1777
+ 0,
1778
+ 1
1779
+ );
1780
+ target.quaternion.setFromRotationMatrix(_syncMat4);
1781
+ }
1782
+ function IkControllerImpl({
1783
+ config,
1784
+ children
1785
+ }) {
1786
+ const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } = useMujocoSim();
1787
+ const ikEnabledRef = useRef(false);
1788
+ const ikCalculatingRef = useRef(false);
1789
+ const ikTargetRef = useRef(new THREE11.Group());
1790
+ const siteIdRef = useRef(-1);
1791
+ const genericIkRef = useRef(new GenericIK(mujocoRef.current));
1792
+ const firstIkEnableRef = useRef(true);
1793
+ const needsInitialSync = useRef(true);
1794
+ const gizmoAnimRef = useRef({
1795
+ active: false,
1796
+ startPos: new THREE11.Vector3(),
1797
+ endPos: new THREE11.Vector3(),
1798
+ startRot: new THREE11.Quaternion(),
1799
+ endRot: new THREE11.Quaternion(),
1800
+ startTime: 0,
1801
+ duration: 1e3
1802
+ });
1893
1803
  useEffect(() => {
1894
- if (status !== "ready" || !geomBuilder) return;
1895
1804
  const model = mjModelRef.current;
1896
- const group = groupRef.current;
1897
- if (!model || !group) return;
1898
- if (prevModelRef.current === model) return;
1899
- prevModelRef.current = model;
1900
- while (group.children.length > 0) {
1901
- group.remove(group.children[0]);
1805
+ if (!model || status !== "ready") {
1806
+ siteIdRef.current = -1;
1807
+ return;
1902
1808
  }
1903
- const refs = [];
1904
- for (let i = 0; i < model.nbody; i++) {
1905
- const bodyGroup = new THREE11.Group();
1906
- bodyGroup.userData.bodyID = i;
1907
- for (let g = 0; g < model.ngeom; g++) {
1908
- if (model.geom_bodyid[g] === i) {
1909
- const mesh = geomBuilder.create(model, g);
1910
- if (mesh) bodyGroup.add(mesh);
1809
+ siteIdRef.current = findSiteByName(model, config.siteName);
1810
+ const data = mjDataRef.current;
1811
+ if (data && ikTargetRef.current) {
1812
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1813
+ }
1814
+ }, [config.siteName, status, mjModelRef, mjDataRef]);
1815
+ const ikSolveFn = useCallback(
1816
+ (pos, quat, currentQ) => {
1817
+ if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
1818
+ const model = mjModelRef.current;
1819
+ const data = mjDataRef.current;
1820
+ if (!model || !data || siteIdRef.current === -1) return null;
1821
+ return genericIkRef.current.solve(
1822
+ model,
1823
+ data,
1824
+ siteIdRef.current,
1825
+ config.numJoints,
1826
+ pos,
1827
+ quat,
1828
+ currentQ,
1829
+ {
1830
+ damping: config.damping,
1831
+ maxIterations: config.maxIterations
1911
1832
  }
1833
+ );
1834
+ },
1835
+ [config.ikSolveFn, config.numJoints, config.damping, config.maxIterations, mjModelRef, mjDataRef]
1836
+ );
1837
+ const ikSolveFnRef = useRef(ikSolveFn);
1838
+ ikSolveFnRef.current = ikSolveFn;
1839
+ useFrame(() => {
1840
+ if (needsInitialSync.current && siteIdRef.current !== -1) {
1841
+ const data = mjDataRef.current;
1842
+ if (data && ikTargetRef.current) {
1843
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1844
+ needsInitialSync.current = false;
1912
1845
  }
1913
- group.add(bodyGroup);
1914
- refs.push(bodyGroup);
1915
1846
  }
1916
- bodyRefs.current = refs;
1917
- }, [status, geomBuilder, mjModelRef]);
1918
- useFrame(() => {
1919
- const data = mjDataRef.current;
1920
- if (!data) return;
1921
- const bodies = bodyRefs.current;
1922
- for (let i = 0; i < bodies.length; i++) {
1923
- const ref = bodies[i];
1924
- if (!ref) continue;
1925
- ref.position.set(
1926
- data.xpos[i * 3],
1927
- data.xpos[i * 3 + 1],
1928
- data.xpos[i * 3 + 2]
1929
- );
1930
- ref.quaternion.set(
1931
- data.xquat[i * 4 + 1],
1932
- data.xquat[i * 4 + 2],
1933
- data.xquat[i * 4 + 3],
1934
- data.xquat[i * 4]
1935
- );
1847
+ const ga = gizmoAnimRef.current;
1848
+ const target = ikTargetRef.current;
1849
+ if (!ga.active || !target) return;
1850
+ const now = performance.now();
1851
+ const elapsed = now - ga.startTime;
1852
+ const t = Math.min(elapsed / ga.duration, 1);
1853
+ const ease = 1 - Math.pow(1 - t, 3);
1854
+ target.position.lerpVectors(ga.startPos, ga.endPos, ease);
1855
+ target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
1856
+ if (t >= 1) ga.active = false;
1857
+ });
1858
+ useBeforePhysicsStep((model, data) => {
1859
+ if (!ikEnabledRef.current) {
1860
+ ikCalculatingRef.current = false;
1861
+ return;
1862
+ }
1863
+ const target = ikTargetRef.current;
1864
+ if (!target) return;
1865
+ ikCalculatingRef.current = true;
1866
+ const numJoints = config.numJoints;
1867
+ const currentQ = [];
1868
+ for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
1869
+ const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
1870
+ if (solution) {
1871
+ for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
1936
1872
  }
1937
1873
  });
1938
- return /* @__PURE__ */ jsx(
1939
- "group",
1940
- {
1941
- ref: groupRef,
1942
- onDoubleClick: (e) => {
1943
- e.stopPropagation();
1944
- let obj = e.object;
1945
- while (obj && obj.userData.bodyID === void 0 && obj.parent) {
1946
- obj = obj.parent;
1947
- }
1948
- const bodyID = obj?.userData.bodyID;
1949
- if (typeof bodyID === "number" && bodyID > 0) {
1950
- const model = mjModelRef.current;
1951
- if (model && bodyID < model.nbody && onSelectionRef.current) {
1952
- const name = getName(model, model.name_bodyadr[bodyID]);
1953
- onSelectionRef.current(bodyID, name);
1954
- }
1955
- }
1874
+ useEffect(() => {
1875
+ const cb = () => {
1876
+ const data = mjDataRef.current;
1877
+ if (data && ikTargetRef.current) {
1878
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1956
1879
  }
1957
- }
1880
+ gizmoAnimRef.current.active = false;
1881
+ firstIkEnableRef.current = true;
1882
+ ikEnabledRef.current = false;
1883
+ needsInitialSync.current = true;
1884
+ };
1885
+ resetCallbacks.current.add(cb);
1886
+ return () => {
1887
+ resetCallbacks.current.delete(cb);
1888
+ };
1889
+ }, [resetCallbacks, mjDataRef]);
1890
+ const setIkEnabled = useCallback(
1891
+ (enabled) => {
1892
+ ikEnabledRef.current = enabled;
1893
+ const data = mjDataRef.current;
1894
+ if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
1895
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1896
+ firstIkEnableRef.current = false;
1897
+ }
1898
+ },
1899
+ [mjDataRef]
1900
+ );
1901
+ const syncTargetToSiteApi = useCallback(() => {
1902
+ const data = mjDataRef.current;
1903
+ const target = ikTargetRef.current;
1904
+ if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
1905
+ }, [mjDataRef]);
1906
+ const solveIK = useCallback(
1907
+ (pos, quat, currentQ) => {
1908
+ return ikSolveFnRef.current(pos, quat, currentQ);
1909
+ },
1910
+ []
1911
+ );
1912
+ const moveTarget = useCallback(
1913
+ (pos, duration = 0) => {
1914
+ if (!ikEnabledRef.current) setIkEnabled(true);
1915
+ const target = ikTargetRef.current;
1916
+ if (!target) return;
1917
+ const targetPos = pos.clone();
1918
+ const targetRot = new THREE11.Quaternion().setFromEuler(
1919
+ new THREE11.Euler(Math.PI, 0, 0)
1920
+ );
1921
+ if (duration > 0) {
1922
+ const ga = gizmoAnimRef.current;
1923
+ ga.active = true;
1924
+ ga.startPos.copy(target.position);
1925
+ ga.endPos.copy(targetPos);
1926
+ ga.startRot.copy(target.quaternion);
1927
+ ga.endRot.copy(targetRot);
1928
+ ga.startTime = performance.now();
1929
+ ga.duration = duration;
1930
+ } else {
1931
+ gizmoAnimRef.current.active = false;
1932
+ target.position.copy(targetPos);
1933
+ target.quaternion.copy(targetRot);
1934
+ }
1935
+ },
1936
+ [setIkEnabled]
1937
+ );
1938
+ const getGizmoStats = useCallback(
1939
+ () => {
1940
+ const target = ikTargetRef.current;
1941
+ if (!ikCalculatingRef.current || !target) return null;
1942
+ return {
1943
+ pos: target.position.clone(),
1944
+ rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
1945
+ };
1946
+ },
1947
+ []
1948
+ );
1949
+ const contextValue = useMemo(
1950
+ () => ({
1951
+ ikEnabledRef,
1952
+ ikCalculatingRef,
1953
+ ikTargetRef,
1954
+ siteIdRef,
1955
+ setIkEnabled,
1956
+ moveTarget,
1957
+ syncTargetToSite: syncTargetToSiteApi,
1958
+ solveIK,
1959
+ getGizmoStats
1960
+ }),
1961
+ [setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats]
1958
1962
  );
1963
+ return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
1959
1964
  }
1965
+ var IkController = createController(
1966
+ {
1967
+ name: "IkController",
1968
+ defaultConfig: {
1969
+ damping: 0.01,
1970
+ maxIterations: 50
1971
+ }
1972
+ },
1973
+ IkControllerImpl
1974
+ );
1960
1975
  var _mat4 = new THREE11.Matrix4();
1961
1976
  var _pos = new THREE11.Vector3();
1962
1977
  var _quat = new THREE11.Quaternion();
@@ -2057,7 +2072,8 @@ function ContactMarkers({
2057
2072
  maxContacts = 100,
2058
2073
  radius = 8e-3,
2059
2074
  color = "#22d3ee",
2060
- visible = true
2075
+ visible = true,
2076
+ ...groupProps
2061
2077
  } = {}) {
2062
2078
  const { mjDataRef, status } = useMujocoSim();
2063
2079
  const meshRef = useRef(null);
@@ -2085,10 +2101,10 @@ function ContactMarkers({
2085
2101
  mesh.instanceMatrix.needsUpdate = true;
2086
2102
  });
2087
2103
  if (status !== "ready") return null;
2088
- return /* @__PURE__ */ jsxs("instancedMesh", { ref: meshRef, args: [void 0, void 0, maxContacts], frustumCulled: false, renderOrder: 999, children: [
2104
+ return /* @__PURE__ */ jsx("group", { ...groupProps, children: /* @__PURE__ */ jsxs("instancedMesh", { ref: meshRef, args: [void 0, void 0, maxContacts], frustumCulled: false, renderOrder: 999, children: [
2089
2105
  /* @__PURE__ */ jsx("sphereGeometry", { args: [radius, 8, 8] }),
2090
2106
  /* @__PURE__ */ jsx("meshBasicMaterial", { color, depthTest: false })
2091
- ] });
2107
+ ] }) });
2092
2108
  }
2093
2109
  var _force = new Float64Array(3);
2094
2110
  var _torque = new Float64Array(3);
@@ -2100,7 +2116,8 @@ var _raycaster = new THREE11.Raycaster();
2100
2116
  var _mouse = new THREE11.Vector2();
2101
2117
  function DragInteraction({
2102
2118
  stiffness = 250,
2103
- showArrow = true
2119
+ showArrow = true,
2120
+ ...groupProps
2104
2121
  }) {
2105
2122
  const { mjDataRef, mujocoRef, mjModelRef, status } = useMujocoSim();
2106
2123
  const { gl, camera, scene, controls } = useThree();
@@ -2257,7 +2274,7 @@ function DragInteraction({
2257
2274
  }
2258
2275
  });
2259
2276
  if (status !== "ready") return null;
2260
- return /* @__PURE__ */ jsx("group", { ref: groupRef });
2277
+ return /* @__PURE__ */ jsx("group", { ...groupProps, ref: groupRef });
2261
2278
  }
2262
2279
  function useSceneLights(intensity = 1) {
2263
2280
  const { mjModelRef, status } = useMujocoSim();
@@ -2378,7 +2395,8 @@ function Debug({
2378
2395
  showContacts = false,
2379
2396
  showCOM = false,
2380
2397
  showInertia = false,
2381
- showTendons = false
2398
+ showTendons = false,
2399
+ ...groupProps
2382
2400
  }) {
2383
2401
  const { mjModelRef, mjDataRef, status } = useMujocoSim();
2384
2402
  const { scene } = useThree();
@@ -2641,7 +2659,7 @@ function Debug({
2641
2659
  }
2642
2660
  });
2643
2661
  if (status !== "ready") return null;
2644
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2662
+ return /* @__PURE__ */ jsxs("group", { ...groupProps, children: [
2645
2663
  /* @__PURE__ */ jsx("group", { ref: groupRef }),
2646
2664
  showContacts && /* @__PURE__ */ jsx("group", { ref: contactGroupRef })
2647
2665
  ] });
@@ -2649,7 +2667,7 @@ function Debug({
2649
2667
  var DEFAULT_TENDON_COLOR = new THREE11.Color(0.3, 0.3, 0.8);
2650
2668
  var DEFAULT_TENDON_WIDTH = 2e-3;
2651
2669
  new THREE11.Vector3();
2652
- function TendonRenderer() {
2670
+ function TendonRenderer(props) {
2653
2671
  const { mjModelRef, mjDataRef, status } = useMujocoSim();
2654
2672
  const groupRef = useRef(null);
2655
2673
  const meshesRef = useRef([]);
@@ -2751,9 +2769,9 @@ function TendonRenderer() {
2751
2769
  }
2752
2770
  });
2753
2771
  if (status !== "ready") return null;
2754
- return /* @__PURE__ */ jsx("group", { ref: groupRef });
2772
+ return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
2755
2773
  }
2756
- function FlexRenderer() {
2774
+ function FlexRenderer(props) {
2757
2775
  const { mjModelRef, mjDataRef, status } = useMujocoSim();
2758
2776
  const groupRef = useRef(null);
2759
2777
  const meshesRef = useRef([]);
@@ -2817,7 +2835,7 @@ function FlexRenderer() {
2817
2835
  }
2818
2836
  });
2819
2837
  if (status !== "ready") return null;
2820
- return /* @__PURE__ */ jsx("group", { ref: groupRef });
2838
+ return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
2821
2839
  }
2822
2840
  var geomNameCacheByModel = /* @__PURE__ */ new WeakMap();
2823
2841
  function getGeomNameCached(model, geomId) {
@@ -3705,6 +3723,10 @@ function useCameraAnimation() {
3705
3723
  * @license
3706
3724
  * SPDX-License-Identifier: Apache-2.0
3707
3725
  */
3726
+ /**
3727
+ * @license
3728
+ * SPDX-License-Identifier: Apache-2.0
3729
+ */
3708
3730
  /**
3709
3731
  * @license
3710
3732
  * SPDX-License-Identifier: Apache-2.0
@@ -3724,10 +3746,6 @@ function useCameraAnimation() {
3724
3746
  * IkController — composable IK controller plugin.
3725
3747
  * Extracts all IK logic from MujocoSimProvider into an opt-in component.
3726
3748
  */
3727
- /**
3728
- * @license
3729
- * SPDX-License-Identifier: Apache-2.0
3730
- */
3731
3749
  /**
3732
3750
  * @license
3733
3751
  * SPDX-License-Identifier: Apache-2.0
@@ -3889,6 +3907,6 @@ function useCameraAnimation() {
3889
3907
  * useCameraAnimation — composable camera animation hook.
3890
3908
  */
3891
3909
 
3892
- export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoProvider, MujocoSimProvider, SceneLights, SceneRenderer, SelectionHighlight, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoSim, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3910
+ export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SelectionHighlight, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoSim, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3893
3911
  //# sourceMappingURL=index.js.map
3894
3912
  //# sourceMappingURL=index.js.map