mujoco-react 1.0.0 → 3.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
@@ -11,7 +11,7 @@ var MujocoContext = createContext({
11
11
  status: "loading",
12
12
  error: null
13
13
  });
14
- function useMujoco() {
14
+ function useMujocoWasm() {
15
15
  return useContext(MujocoContext);
16
16
  }
17
17
  function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
@@ -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) {
@@ -265,19 +507,97 @@ function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
265
507
  if (!downloaded.has(fullPath)) queue.push(fullPath);
266
508
  });
267
509
  }
268
- var JOINT_TYPE_NAMES = ["free", "ball", "slide", "hinge"];
269
- var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
270
- var SENSOR_TYPE_NAMES = {
271
- 0: "touch",
272
- 1: "accelerometer",
273
- 2: "velocimeter",
274
- 3: "gyro",
275
- 4: "force",
276
- 5: "torque",
277
- 6: "magnetometer",
278
- 7: "rangefinder",
279
- 8: "camprojection",
280
- 9: "jointpos",
510
+ function SceneRenderer(props) {
511
+ const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, status } = useMujoco();
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",
281
601
  10: "jointvel",
282
602
  11: "tendonpos",
283
603
  12: "tendonvel",
@@ -327,14 +647,14 @@ var _rayGeomId = new Int32Array(1);
327
647
  var _projRaycaster = new THREE11.Raycaster();
328
648
  var _projNdc = new THREE11.Vector2();
329
649
  var MujocoSimContext = createContext(null);
330
- function useMujocoSim() {
650
+ function useMujoco() {
331
651
  const ctx = useContext(MujocoSimContext);
332
652
  if (!ctx)
333
- throw new Error("useMujocoSim must be used inside <MujocoSimProvider>");
653
+ throw new Error("useMujoco must be used inside <MujocoSimProvider>");
334
654
  return ctx;
335
655
  }
336
656
  function useBeforePhysicsStep(callback) {
337
- const { beforeStepCallbacks } = useMujocoSim();
657
+ const { beforeStepCallbacks } = useMujoco();
338
658
  const callbackRef = useRef(callback);
339
659
  callbackRef.current = callback;
340
660
  useEffect(() => {
@@ -346,7 +666,7 @@ function useBeforePhysicsStep(callback) {
346
666
  }, [beforeStepCallbacks]);
347
667
  }
348
668
  function useAfterPhysicsStep(callback) {
349
- const { afterStepCallbacks } = useMujocoSim();
669
+ const { afterStepCallbacks } = useMujoco();
350
670
  const callbackRef = useRef(callback);
351
671
  callbackRef.current = callback;
352
672
  useEffect(() => {
@@ -1095,7 +1415,10 @@ function MujocoSimProvider({
1095
1415
  }),
1096
1416
  [api, status]
1097
1417
  );
1098
- return /* @__PURE__ */ jsx(MujocoSimContext.Provider, { value: contextValue, children });
1418
+ return /* @__PURE__ */ jsxs(MujocoSimContext.Provider, { value: contextValue, children: [
1419
+ /* @__PURE__ */ jsx(SceneRenderer, {}),
1420
+ children
1421
+ ] });
1099
1422
  }
1100
1423
  var MujocoCanvas = forwardRef(
1101
1424
  function MujocoCanvas2({
@@ -1113,7 +1436,7 @@ var MujocoCanvas = forwardRef(
1113
1436
  children,
1114
1437
  ...canvasProps
1115
1438
  }, ref) {
1116
- const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
1439
+ const { mujoco, status: wasmStatus, error: wasmError } = useMujocoWasm();
1117
1440
  useEffect(() => {
1118
1441
  if (wasmStatus === "error" && onError) {
1119
1442
  onError(new Error(wasmError ?? "WASM load failed"));
@@ -1144,7 +1467,7 @@ var MujocoCanvas = forwardRef(
1144
1467
  );
1145
1468
  var MujocoPhysics = forwardRef(
1146
1469
  function MujocoPhysics2({ onError, children, ...props }, ref) {
1147
- const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
1470
+ const { mujoco, status: wasmStatus, error: wasmError } = useMujocoWasm();
1148
1471
  useEffect(() => {
1149
1472
  if (wasmStatus === "error" && onError) {
1150
1473
  onError(new Error(wasmError ?? "WASM load failed"));
@@ -1447,534 +1770,214 @@ function syncGizmoToSite(data, siteId, target) {
1447
1770
  0,
1448
1771
  siteMat[6],
1449
1772
  siteMat[7],
1450
- siteMat[8],
1451
- 0,
1452
- 0,
1453
- 0,
1454
- 0,
1455
- 1
1456
- );
1457
- target.quaternion.setFromRotationMatrix(_syncMat4);
1458
- }
1459
- function IkControllerImpl({
1460
- config,
1461
- children
1462
- }) {
1463
- const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } = useMujocoSim();
1464
- const ikEnabledRef = useRef(false);
1465
- const ikCalculatingRef = useRef(false);
1466
- const ikTargetRef = useRef(new THREE11.Group());
1467
- const siteIdRef = useRef(-1);
1468
- const genericIkRef = useRef(new GenericIK(mujocoRef.current));
1469
- const firstIkEnableRef = useRef(true);
1470
- const needsInitialSync = useRef(true);
1471
- const gizmoAnimRef = useRef({
1472
- active: false,
1473
- startPos: new THREE11.Vector3(),
1474
- endPos: new THREE11.Vector3(),
1475
- startRot: new THREE11.Quaternion(),
1476
- endRot: new THREE11.Quaternion(),
1477
- startTime: 0,
1478
- duration: 1e3
1479
- });
1480
- useEffect(() => {
1481
- const model = mjModelRef.current;
1482
- if (!model || status !== "ready") {
1483
- siteIdRef.current = -1;
1484
- return;
1485
- }
1486
- siteIdRef.current = findSiteByName(model, config.siteName);
1487
- const data = mjDataRef.current;
1488
- if (data && ikTargetRef.current) {
1489
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1490
- }
1491
- }, [config.siteName, status, mjModelRef, mjDataRef]);
1492
- const ikSolveFn = useCallback(
1493
- (pos, quat, currentQ) => {
1494
- if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
1495
- const model = mjModelRef.current;
1496
- const data = mjDataRef.current;
1497
- if (!model || !data || siteIdRef.current === -1) return null;
1498
- return genericIkRef.current.solve(
1499
- model,
1500
- data,
1501
- siteIdRef.current,
1502
- config.numJoints,
1503
- pos,
1504
- quat,
1505
- currentQ,
1506
- {
1507
- damping: config.damping,
1508
- maxIterations: config.maxIterations
1509
- }
1510
- );
1511
- },
1512
- [config.ikSolveFn, config.numJoints, config.damping, config.maxIterations, mjModelRef, mjDataRef]
1513
- );
1514
- const ikSolveFnRef = useRef(ikSolveFn);
1515
- ikSolveFnRef.current = ikSolveFn;
1516
- useFrame(() => {
1517
- if (needsInitialSync.current && siteIdRef.current !== -1) {
1518
- const data = mjDataRef.current;
1519
- if (data && ikTargetRef.current) {
1520
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1521
- needsInitialSync.current = false;
1522
- }
1523
- }
1524
- const ga = gizmoAnimRef.current;
1525
- const target = ikTargetRef.current;
1526
- if (!ga.active || !target) return;
1527
- const now = performance.now();
1528
- const elapsed = now - ga.startTime;
1529
- const t = Math.min(elapsed / ga.duration, 1);
1530
- const ease = 1 - Math.pow(1 - t, 3);
1531
- target.position.lerpVectors(ga.startPos, ga.endPos, ease);
1532
- target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
1533
- if (t >= 1) ga.active = false;
1534
- });
1535
- useBeforePhysicsStep((model, data) => {
1536
- if (!ikEnabledRef.current) {
1537
- ikCalculatingRef.current = false;
1538
- return;
1539
- }
1540
- const target = ikTargetRef.current;
1541
- if (!target) return;
1542
- ikCalculatingRef.current = true;
1543
- const numJoints = config.numJoints;
1544
- const currentQ = [];
1545
- for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
1546
- const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
1547
- if (solution) {
1548
- for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
1549
- }
1550
- });
1551
- useEffect(() => {
1552
- const cb = () => {
1553
- const data = mjDataRef.current;
1554
- if (data && ikTargetRef.current) {
1555
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1556
- }
1557
- gizmoAnimRef.current.active = false;
1558
- firstIkEnableRef.current = true;
1559
- ikEnabledRef.current = false;
1560
- needsInitialSync.current = true;
1561
- };
1562
- resetCallbacks.current.add(cb);
1563
- return () => {
1564
- resetCallbacks.current.delete(cb);
1565
- };
1566
- }, [resetCallbacks, mjDataRef]);
1567
- const setIkEnabled = useCallback(
1568
- (enabled) => {
1569
- ikEnabledRef.current = enabled;
1570
- const data = mjDataRef.current;
1571
- if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
1572
- syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1573
- firstIkEnableRef.current = false;
1574
- }
1575
- },
1576
- [mjDataRef]
1577
- );
1578
- const syncTargetToSiteApi = useCallback(() => {
1579
- const data = mjDataRef.current;
1580
- const target = ikTargetRef.current;
1581
- if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
1582
- }, [mjDataRef]);
1583
- const solveIK = useCallback(
1584
- (pos, quat, currentQ) => {
1585
- return ikSolveFnRef.current(pos, quat, currentQ);
1586
- },
1587
- []
1588
- );
1589
- const moveTarget = useCallback(
1590
- (pos, duration = 0) => {
1591
- if (!ikEnabledRef.current) setIkEnabled(true);
1592
- const target = ikTargetRef.current;
1593
- if (!target) return;
1594
- const targetPos = pos.clone();
1595
- const targetRot = new THREE11.Quaternion().setFromEuler(
1596
- new THREE11.Euler(Math.PI, 0, 0)
1597
- );
1598
- if (duration > 0) {
1599
- const ga = gizmoAnimRef.current;
1600
- ga.active = true;
1601
- ga.startPos.copy(target.position);
1602
- ga.endPos.copy(targetPos);
1603
- ga.startRot.copy(target.quaternion);
1604
- ga.endRot.copy(targetRot);
1605
- ga.startTime = performance.now();
1606
- ga.duration = duration;
1607
- } else {
1608
- gizmoAnimRef.current.active = false;
1609
- target.position.copy(targetPos);
1610
- target.quaternion.copy(targetRot);
1611
- }
1612
- },
1613
- [setIkEnabled]
1614
- );
1615
- const getGizmoStats = useCallback(
1616
- () => {
1617
- const target = ikTargetRef.current;
1618
- if (!ikCalculatingRef.current || !target) return null;
1619
- return {
1620
- pos: target.position.clone(),
1621
- rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
1622
- };
1623
- },
1624
- []
1625
- );
1626
- const contextValue = useMemo(
1627
- () => ({
1628
- ikEnabledRef,
1629
- ikCalculatingRef,
1630
- ikTargetRef,
1631
- siteIdRef,
1632
- setIkEnabled,
1633
- moveTarget,
1634
- syncTargetToSite: syncTargetToSiteApi,
1635
- solveIK,
1636
- getGizmoStats
1637
- }),
1638
- [setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats]
1639
- );
1640
- return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
1641
- }
1642
- var IkController = createController(
1643
- {
1644
- name: "IkController",
1645
- defaultConfig: {
1646
- damping: 0.01,
1647
- maxIterations: 50
1648
- }
1649
- },
1650
- IkControllerImpl
1651
- );
1652
- var CapsuleGeometry = class extends THREE11.BufferGeometry {
1653
- parameters;
1654
- constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
1655
- super();
1656
- this.type = "CapsuleGeometry";
1657
- this.parameters = { radius, length, capSegments, radialSegments };
1658
- const path = new THREE11.Path();
1659
- path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
1660
- path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
1661
- const latheGeometry = new THREE11.LatheGeometry(path.getPoints(capSegments), radialSegments);
1662
- const self = this;
1663
- self.setIndex(latheGeometry.getIndex());
1664
- self.setAttribute("position", latheGeometry.getAttribute("position"));
1665
- self.setAttribute("normal", latheGeometry.getAttribute("normal"));
1666
- self.setAttribute("uv", latheGeometry.getAttribute("uv"));
1667
- }
1668
- };
1669
- var Reflector = class extends THREE11.Mesh {
1670
- isReflector = true;
1671
- camera;
1672
- reflectorPlane = new THREE11.Plane();
1673
- normal = new THREE11.Vector3();
1674
- reflectorWorldPosition = new THREE11.Vector3();
1675
- cameraWorldPosition = new THREE11.Vector3();
1676
- rotationMatrix = new THREE11.Matrix4();
1677
- lookAtPosition = new THREE11.Vector3(0, 0, -1);
1678
- clipPlane = new THREE11.Vector4();
1679
- view = new THREE11.Vector3();
1680
- target = new THREE11.Vector3();
1681
- q = new THREE11.Vector4();
1682
- textureMatrix = new THREE11.Matrix4();
1683
- virtualCamera;
1684
- renderTarget;
1685
- constructor(geometry, options = {}) {
1686
- super(geometry);
1687
- this.type = "Reflector";
1688
- this.camera = new THREE11.PerspectiveCamera();
1689
- const color = options.color !== void 0 ? new THREE11.Color(options.color) : new THREE11.Color(8355711);
1690
- const textureWidth = options.textureWidth || 512;
1691
- const textureHeight = options.textureHeight || 512;
1692
- const clipBias = options.clipBias || 0;
1693
- const multisample = options.multisample !== void 0 ? options.multisample : 4;
1694
- const blendTexture = options.texture || void 0;
1695
- const mixStrength = options.mixStrength !== void 0 ? options.mixStrength : 0.25;
1696
- this.virtualCamera = this.camera;
1697
- this.renderTarget = new THREE11.WebGLRenderTarget(textureWidth, textureHeight, {
1698
- samples: multisample,
1699
- type: THREE11.HalfFloatType
1700
- });
1701
- this.material = new THREE11.MeshPhysicalMaterial({
1702
- map: blendTexture,
1703
- color,
1704
- roughness: 0.5,
1705
- metalness: 0.1
1706
- });
1707
- this.material.onBeforeCompile = (shader) => {
1708
- shader.uniforms.tDiffuse = { value: this.renderTarget.texture };
1709
- shader.uniforms.textureMatrix = { value: this.textureMatrix };
1710
- shader.uniforms.mixStrength = { value: mixStrength };
1711
- const bodyStart = shader.vertexShader.indexOf("void main() {");
1712
- 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}";
1713
- const fragmentBodyStart = shader.fragmentShader.indexOf("void main() {");
1714
- 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}";
1715
- };
1716
- this.receiveShadow = true;
1717
- this.onBeforeRender = (renderer, scene, camera) => {
1718
- this.reflectorWorldPosition.setFromMatrixPosition(this.matrixWorld);
1719
- this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);
1720
- this.rotationMatrix.extractRotation(this.matrixWorld);
1721
- this.normal.set(0, 0, 1);
1722
- this.normal.applyMatrix4(this.rotationMatrix);
1723
- this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition);
1724
- if (this.view.dot(this.normal) > 0) return;
1725
- this.view.reflect(this.normal).negate();
1726
- this.view.add(this.reflectorWorldPosition);
1727
- this.rotationMatrix.extractRotation(camera.matrixWorld);
1728
- this.lookAtPosition.set(0, 0, -1);
1729
- this.lookAtPosition.applyMatrix4(this.rotationMatrix);
1730
- this.lookAtPosition.add(this.cameraWorldPosition);
1731
- this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition);
1732
- this.target.reflect(this.normal).negate();
1733
- this.target.add(this.reflectorWorldPosition);
1734
- this.virtualCamera.position.copy(this.view);
1735
- this.virtualCamera.up.set(0, 1, 0);
1736
- this.virtualCamera.up.applyMatrix4(this.rotationMatrix);
1737
- this.virtualCamera.up.reflect(this.normal);
1738
- this.virtualCamera.lookAt(this.target);
1739
- this.virtualCamera.far = camera.far;
1740
- this.virtualCamera.updateMatrixWorld();
1741
- this.virtualCamera.projectionMatrix.copy(camera.projectionMatrix);
1742
- this.textureMatrix.set(
1743
- 0.5,
1744
- 0,
1745
- 0,
1746
- 0.5,
1747
- 0,
1748
- 0.5,
1749
- 0,
1750
- 0.5,
1751
- 0,
1752
- 0,
1753
- 0.5,
1754
- 0.5,
1755
- 0,
1756
- 0,
1757
- 0,
1758
- 1
1759
- );
1760
- this.textureMatrix.multiply(this.virtualCamera.projectionMatrix);
1761
- this.textureMatrix.multiply(this.virtualCamera.matrixWorldInverse);
1762
- this.textureMatrix.multiply(this.matrixWorld);
1763
- this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition);
1764
- this.reflectorPlane.applyMatrix4(this.virtualCamera.matrixWorldInverse);
1765
- this.clipPlane.set(this.reflectorPlane.normal.x, this.reflectorPlane.normal.y, this.reflectorPlane.normal.z, this.reflectorPlane.constant);
1766
- const projectionMatrix = this.virtualCamera.projectionMatrix;
1767
- this.q.x = (Math.sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
1768
- this.q.y = (Math.sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
1769
- this.q.z = -1;
1770
- this.q.w = (1 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];
1771
- this.clipPlane.multiplyScalar(2 / this.clipPlane.dot(this.q));
1772
- projectionMatrix.elements[2] = this.clipPlane.x;
1773
- projectionMatrix.elements[6] = this.clipPlane.y;
1774
- projectionMatrix.elements[10] = this.clipPlane.z + 1 - clipBias;
1775
- projectionMatrix.elements[14] = this.clipPlane.w;
1776
- this.visible = false;
1777
- const currentRenderTarget = renderer.getRenderTarget();
1778
- const currentXrEnabled = renderer.xr.enabled;
1779
- const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
1780
- renderer.xr.enabled = false;
1781
- renderer.shadowMap.autoUpdate = false;
1782
- renderer.setRenderTarget(this.renderTarget);
1783
- renderer.state.buffers.depth.setMask(true);
1784
- if (renderer.autoClear === false) renderer.clear();
1785
- renderer.render(scene, this.virtualCamera);
1786
- renderer.xr.enabled = currentXrEnabled;
1787
- renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
1788
- renderer.setRenderTarget(currentRenderTarget);
1789
- const viewport = camera.viewport;
1790
- if (viewport !== void 0) {
1791
- renderer.state.viewport(viewport);
1792
- }
1793
- this.visible = true;
1794
- };
1795
- }
1796
- getRenderTarget() {
1797
- return this.renderTarget;
1798
- }
1799
- dispose() {
1800
- this.renderTarget.dispose();
1801
- const mesh = this;
1802
- if (Array.isArray(mesh.material)) {
1803
- mesh.material.forEach((m) => m.dispose());
1804
- } else {
1805
- mesh.material.dispose();
1806
- }
1807
- }
1808
- };
1809
-
1810
- // src/rendering/GeomBuilder.ts
1811
- var GeomBuilder = class {
1812
- mujoco;
1813
- constructor(mujoco) {
1814
- this.mujoco = mujoco;
1815
- }
1816
- /**
1817
- * Creates a Three.js Object3D (usually a Mesh) for a specific geometry in the MuJoCo model.
1818
- * Returns null if the geometry shouldn't be rendered (e.g., invisible collision triggers).
1819
- */
1820
- create(mjModel, g) {
1821
- if (mjModel.geom_group[g] === 3) return null;
1822
- const type = mjModel.geom_type[g];
1823
- const size = mjModel.geom_size.subarray(g * 3, g * 3 + 3);
1824
- const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
1825
- const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
1826
- const matId = mjModel.geom_matid[g];
1827
- const color = new THREE11.Color(16777215);
1828
- let opacity = 1;
1829
- if (matId >= 0) {
1830
- const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
1831
- color.setRGB(rgba[0], rgba[1], rgba[2]);
1832
- opacity = rgba[3];
1833
- } else {
1834
- const rgba = mjModel.geom_rgba.subarray(g * 4, g * 4 + 4);
1835
- color.setRGB(rgba[0], rgba[1], rgba[2]);
1836
- opacity = rgba[3];
1837
- }
1838
- const MG = this.mujoco.mjtGeom;
1839
- let geo = null;
1840
- const getVal = (v) => v?.value ?? v;
1841
- if (type === getVal(MG.mjGEOM_PLANE)) {
1842
- geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
1843
- } else if (type === getVal(MG.mjGEOM_SPHERE)) {
1844
- geo = new THREE11.SphereGeometry(size[0], 24, 24);
1845
- } else if (type === getVal(MG.mjGEOM_CAPSULE)) {
1846
- geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
1847
- geo.rotateX(Math.PI / 2);
1848
- } else if (type === getVal(MG.mjGEOM_BOX)) {
1849
- geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
1850
- } else if (type === getVal(MG.mjGEOM_CYLINDER)) {
1851
- geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
1852
- geo.rotateX(Math.PI / 2);
1853
- } else if (type === getVal(MG.mjGEOM_MESH)) {
1854
- const mId = mjModel.geom_dataid[g];
1855
- const vAdr = mjModel.mesh_vertadr[mId];
1856
- const vNum = mjModel.mesh_vertnum[mId];
1857
- const fAdr = mjModel.mesh_faceadr[mId];
1858
- const fNum = mjModel.mesh_facenum[mId];
1859
- geo = new THREE11.BufferGeometry();
1860
- geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
1861
- geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
1862
- geo.computeVertexNormals();
1863
- }
1864
- if (geo) {
1865
- let mesh;
1866
- if (type === getVal(MG.mjGEOM_PLANE)) {
1867
- mesh = new Reflector(geo, {
1868
- clipBias: 3e-3,
1869
- textureWidth: 1024,
1870
- textureHeight: 1024,
1871
- color,
1872
- mixStrength: 0.25
1873
- });
1874
- } else {
1875
- mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
1876
- color,
1877
- transparent: opacity < 1,
1878
- opacity,
1879
- roughness: 0.6,
1880
- metalness: 0.2
1881
- }));
1882
- mesh.castShadow = true;
1883
- mesh.receiveShadow = true;
1884
- }
1885
- mesh.position.set(pos[0], pos[1], pos[2]);
1886
- mesh.quaternion.set(quat[1], quat[2], quat[3], quat[0]);
1887
- mesh.userData.bodyID = mjModel.geom_bodyid[g];
1888
- mesh.userData.geomID = g;
1889
- return mesh;
1890
- }
1891
- return null;
1892
- }
1893
- };
1894
- function SceneRenderer(props) {
1895
- const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, status } = useMujocoSim();
1896
- const groupRef = useRef(null);
1897
- const bodyRefs = useRef([]);
1898
- const prevModelRef = useRef(null);
1899
- const geomBuilder = useMemo(() => {
1900
- if (status !== "ready") return null;
1901
- return new GeomBuilder(mujocoRef.current);
1902
- }, [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 } = useMujoco();
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
+ });
1903
1803
  useEffect(() => {
1904
- if (status !== "ready" || !geomBuilder) return;
1905
1804
  const model = mjModelRef.current;
1906
- const group = groupRef.current;
1907
- if (!model || !group) return;
1908
- if (prevModelRef.current === model) return;
1909
- prevModelRef.current = model;
1910
- while (group.children.length > 0) {
1911
- group.remove(group.children[0]);
1805
+ if (!model || status !== "ready") {
1806
+ siteIdRef.current = -1;
1807
+ return;
1912
1808
  }
1913
- const refs = [];
1914
- for (let i = 0; i < model.nbody; i++) {
1915
- const bodyGroup = new THREE11.Group();
1916
- bodyGroup.userData.bodyID = i;
1917
- for (let g = 0; g < model.ngeom; g++) {
1918
- if (model.geom_bodyid[g] === i) {
1919
- const mesh = geomBuilder.create(model, g);
1920
- 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
1921
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;
1922
1845
  }
1923
- group.add(bodyGroup);
1924
- refs.push(bodyGroup);
1925
1846
  }
1926
- bodyRefs.current = refs;
1927
- }, [status, geomBuilder, mjModelRef]);
1928
- useFrame(() => {
1929
- const data = mjDataRef.current;
1930
- if (!data) return;
1931
- const bodies = bodyRefs.current;
1932
- for (let i = 0; i < bodies.length; i++) {
1933
- const ref = bodies[i];
1934
- if (!ref) continue;
1935
- ref.position.set(
1936
- data.xpos[i * 3],
1937
- data.xpos[i * 3 + 1],
1938
- data.xpos[i * 3 + 2]
1939
- );
1940
- ref.quaternion.set(
1941
- data.xquat[i * 4 + 1],
1942
- data.xquat[i * 4 + 2],
1943
- data.xquat[i * 4 + 3],
1944
- data.xquat[i * 4]
1945
- );
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];
1946
1872
  }
1947
1873
  });
1948
- return /* @__PURE__ */ jsx(
1949
- "group",
1950
- {
1951
- ...props,
1952
- ref: groupRef,
1953
- onDoubleClick: (e) => {
1954
- if (typeof props.onDoubleClick === "function") props.onDoubleClick(e);
1955
- e.stopPropagation();
1956
- let obj = e.object;
1957
- while (obj && obj.userData.bodyID === void 0 && obj.parent) {
1958
- obj = obj.parent;
1959
- }
1960
- const bodyID = obj?.userData.bodyID;
1961
- if (typeof bodyID === "number" && bodyID > 0) {
1962
- const model = mjModelRef.current;
1963
- if (model && bodyID < model.nbody && onSelectionRef.current) {
1964
- const name = getName(model, model.name_bodyadr[bodyID]);
1965
- onSelectionRef.current(bodyID, name);
1966
- }
1967
- }
1874
+ useEffect(() => {
1875
+ const cb = () => {
1876
+ const data = mjDataRef.current;
1877
+ if (data && ikTargetRef.current) {
1878
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1968
1879
  }
1969
- }
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]
1970
1962
  );
1963
+ return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
1971
1964
  }
1965
+ var IkController = createController(
1966
+ {
1967
+ name: "IkController",
1968
+ defaultConfig: {
1969
+ damping: 0.01,
1970
+ maxIterations: 50
1971
+ }
1972
+ },
1973
+ IkControllerImpl
1974
+ );
1972
1975
  var _mat4 = new THREE11.Matrix4();
1973
1976
  var _pos = new THREE11.Vector3();
1974
1977
  var _quat = new THREE11.Quaternion();
1975
1978
  var _scale = new THREE11.Vector3(1, 1, 1);
1976
1979
  function IkGizmo({ siteName, scale = 0.18, onDrag }) {
1977
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
1980
+ const { mjModelRef, mjDataRef, status } = useMujoco();
1978
1981
  const { ikTargetRef, siteIdRef, ikEnabledRef, setIkEnabled } = useIk();
1979
1982
  const wrapperRef = useRef(null);
1980
1983
  const pivotRef = useRef(null);
@@ -2072,7 +2075,7 @@ function ContactMarkers({
2072
2075
  visible = true,
2073
2076
  ...groupProps
2074
2077
  } = {}) {
2075
- const { mjDataRef, status } = useMujocoSim();
2078
+ const { mjDataRef, status } = useMujoco();
2076
2079
  const meshRef = useRef(null);
2077
2080
  useFrame(() => {
2078
2081
  const mesh = meshRef.current;
@@ -2116,7 +2119,7 @@ function DragInteraction({
2116
2119
  showArrow = true,
2117
2120
  ...groupProps
2118
2121
  }) {
2119
- const { mjDataRef, mujocoRef, mjModelRef, status } = useMujocoSim();
2122
+ const { mjDataRef, mujocoRef, mjModelRef, status } = useMujoco();
2120
2123
  const { gl, camera, scene, controls } = useThree();
2121
2124
  const draggingRef = useRef(false);
2122
2125
  const bodyIdRef = useRef(-1);
@@ -2274,7 +2277,7 @@ function DragInteraction({
2274
2277
  return /* @__PURE__ */ jsx("group", { ...groupProps, ref: groupRef });
2275
2278
  }
2276
2279
  function useSceneLights(intensity = 1) {
2277
- const { mjModelRef, status } = useMujocoSim();
2280
+ const { mjModelRef, status } = useMujoco();
2278
2281
  const { scene } = useThree();
2279
2282
  const lightsRef = useRef([]);
2280
2283
  const targetsRef = useRef([]);
@@ -2395,7 +2398,7 @@ function Debug({
2395
2398
  showTendons = false,
2396
2399
  ...groupProps
2397
2400
  }) {
2398
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
2401
+ const { mjModelRef, mjDataRef, status } = useMujoco();
2399
2402
  const { scene } = useThree();
2400
2403
  const groupRef = useRef(null);
2401
2404
  const debugGeometry = useMemo(() => {
@@ -2665,7 +2668,7 @@ var DEFAULT_TENDON_COLOR = new THREE11.Color(0.3, 0.3, 0.8);
2665
2668
  var DEFAULT_TENDON_WIDTH = 2e-3;
2666
2669
  new THREE11.Vector3();
2667
2670
  function TendonRenderer(props) {
2668
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
2671
+ const { mjModelRef, mjDataRef, status } = useMujoco();
2669
2672
  const groupRef = useRef(null);
2670
2673
  const meshesRef = useRef([]);
2671
2674
  const curvesRef = useRef([]);
@@ -2769,7 +2772,7 @@ function TendonRenderer(props) {
2769
2772
  return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
2770
2773
  }
2771
2774
  function FlexRenderer(props) {
2772
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
2775
+ const { mjModelRef, mjDataRef, status } = useMujoco();
2773
2776
  const groupRef = useRef(null);
2774
2777
  const meshesRef = useRef([]);
2775
2778
  useEffect(() => {
@@ -2849,7 +2852,7 @@ function getGeomNameCached(model, geomId) {
2849
2852
  return name;
2850
2853
  }
2851
2854
  function useContacts(bodyName, callback) {
2852
- const { mjModelRef, status } = useMujocoSim();
2855
+ const { mjModelRef, status } = useMujoco();
2853
2856
  const contactsRef = useRef([]);
2854
2857
  const bodyIdRef = useRef(-1);
2855
2858
  const bodyResolvedRef = useRef(false);
@@ -2948,7 +2951,7 @@ function ContactListener({
2948
2951
  return null;
2949
2952
  }
2950
2953
  function useTrajectoryPlayer(trajectory, options = {}) {
2951
- const { mjModelRef, mjDataRef, mujocoRef, pausedRef } = useMujocoSim();
2954
+ const { mjModelRef, mjDataRef, mujocoRef, pausedRef } = useMujoco();
2952
2955
  const fps = options.fps ?? 30;
2953
2956
  const loop = options.loop ?? false;
2954
2957
  const playingRef = useRef(false);
@@ -3103,7 +3106,7 @@ function SelectionHighlight({
3103
3106
  return null;
3104
3107
  }
3105
3108
  function useActuators() {
3106
- const { mjModelRef, status } = useMujocoSim();
3109
+ const { mjModelRef, status } = useMujoco();
3107
3110
  return useMemo(() => {
3108
3111
  if (status !== "ready") return [];
3109
3112
  const model = mjModelRef.current;
@@ -3122,7 +3125,7 @@ function useActuators() {
3122
3125
  }
3123
3126
  var _mat42 = new THREE11.Matrix4();
3124
3127
  function useSitePosition(siteName) {
3125
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
3128
+ const { mjModelRef, mjDataRef, status } = useMujoco();
3126
3129
  const siteIdRef = useRef(-1);
3127
3130
  const positionRef = useRef(new THREE11.Vector3());
3128
3131
  const quaternionRef = useRef(new THREE11.Quaternion());
@@ -3179,7 +3182,7 @@ function useGravityCompensation(enabled = true) {
3179
3182
  });
3180
3183
  }
3181
3184
  function useSensor(name) {
3182
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
3185
+ const { mjModelRef, mjDataRef, status } = useMujoco();
3183
3186
  const sensorIdRef = useRef(-1);
3184
3187
  const sensorAdrRef = useRef(0);
3185
3188
  const sensorDimRef = useRef(0);
@@ -3209,7 +3212,7 @@ function useSensor(name) {
3209
3212
  return { value: valueRef, size: sensorDimRef.current };
3210
3213
  }
3211
3214
  function useSensors() {
3212
- const { mjModelRef, status } = useMujocoSim();
3215
+ const { mjModelRef, status } = useMujoco();
3213
3216
  return useMemo(() => {
3214
3217
  const model = mjModelRef.current;
3215
3218
  if (!model || status !== "ready") return [];
@@ -3246,7 +3249,7 @@ function useSensors() {
3246
3249
  }, [mjModelRef, status]);
3247
3250
  }
3248
3251
  function useJointState(name) {
3249
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
3252
+ const { mjModelRef, mjDataRef, status } = useMujoco();
3250
3253
  const jointIdRef = useRef(-1);
3251
3254
  const qposAdrRef = useRef(0);
3252
3255
  const dofAdrRef = useRef(0);
@@ -3306,7 +3309,7 @@ function useJointState(name) {
3306
3309
  return { position: positionRef, velocity: velocityRef };
3307
3310
  }
3308
3311
  function useBodyState(name) {
3309
- const { mjModelRef, status } = useMujocoSim();
3312
+ const { mjModelRef, status } = useMujoco();
3310
3313
  const bodyIdRef = useRef(-1);
3311
3314
  const position = useRef(new THREE11.Vector3());
3312
3315
  const quaternion = useRef(new THREE11.Quaternion());
@@ -3338,7 +3341,7 @@ function useBodyState(name) {
3338
3341
  return { position, quaternion, linearVelocity, angularVelocity };
3339
3342
  }
3340
3343
  function useCtrl(name) {
3341
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
3344
+ const { mjModelRef, mjDataRef, status } = useMujoco();
3342
3345
  const actuatorIdRef = useRef(-1);
3343
3346
  const valueRef = useRef(0);
3344
3347
  useEffect(() => {
@@ -3355,7 +3358,7 @@ function useCtrl(name) {
3355
3358
  return [valueRef, setValue];
3356
3359
  }
3357
3360
  function useKeyboardTeleop(config) {
3358
- const { mjModelRef, mjDataRef, status } = useMujocoSim();
3361
+ const { mjModelRef, mjDataRef, status } = useMujoco();
3359
3362
  const pressedRef = useRef(/* @__PURE__ */ new Set());
3360
3363
  const toggleStateRef = useRef(/* @__PURE__ */ new Map());
3361
3364
  const enabledRef = useRef(config.enabled ?? true);
@@ -3418,7 +3421,7 @@ function useKeyboardTeleop(config) {
3418
3421
  });
3419
3422
  }
3420
3423
  function usePolicy(config) {
3421
- const { mjModelRef } = useMujocoSim();
3424
+ const { mjModelRef } = useMujoco();
3422
3425
  const lastActionTimeRef = useRef(0);
3423
3426
  const lastActionRef = useRef(null);
3424
3427
  const isRunningRef = useRef(true);
@@ -3452,7 +3455,7 @@ function usePolicy(config) {
3452
3455
  };
3453
3456
  }
3454
3457
  function useTrajectoryRecorder(options = {}) {
3455
- const { mjModelRef } = useMujocoSim();
3458
+ const { mjModelRef } = useMujoco();
3456
3459
  const recordingRef = useRef(false);
3457
3460
  const framesRef = useRef([]);
3458
3461
  const fields = options.fields ?? ["qpos"];
@@ -3528,7 +3531,7 @@ function useTrajectoryRecorder(options = {}) {
3528
3531
  };
3529
3532
  }
3530
3533
  function useGamepad(config) {
3531
- const { mjModelRef, status } = useMujocoSim();
3534
+ const { mjModelRef, status } = useMujoco();
3532
3535
  const configRef = useRef(config);
3533
3536
  configRef.current = config;
3534
3537
  const axisCacheRef = useRef(/* @__PURE__ */ new Map());
@@ -3624,7 +3627,7 @@ function useVideoRecorder(options = {}) {
3624
3627
  };
3625
3628
  }
3626
3629
  function useCtrlNoise(config = {}) {
3627
- const { mjModelRef } = useMujocoSim();
3630
+ const { mjModelRef } = useMujoco();
3628
3631
  const configRef = useRef(config);
3629
3632
  configRef.current = config;
3630
3633
  const noiseRef = useRef(null);
@@ -3720,6 +3723,10 @@ function useCameraAnimation() {
3720
3723
  * @license
3721
3724
  * SPDX-License-Identifier: Apache-2.0
3722
3725
  */
3726
+ /**
3727
+ * @license
3728
+ * SPDX-License-Identifier: Apache-2.0
3729
+ */
3723
3730
  /**
3724
3731
  * @license
3725
3732
  * SPDX-License-Identifier: Apache-2.0
@@ -3739,10 +3746,6 @@ function useCameraAnimation() {
3739
3746
  * IkController — composable IK controller plugin.
3740
3747
  * Extracts all IK logic from MujocoSimProvider into an opt-in component.
3741
3748
  */
3742
- /**
3743
- * @license
3744
- * SPDX-License-Identifier: Apache-2.0
3745
- */
3746
3749
  /**
3747
3750
  * @license
3748
3751
  * SPDX-License-Identifier: Apache-2.0
@@ -3904,6 +3907,6 @@ function useCameraAnimation() {
3904
3907
  * useCameraAnimation — composable camera animation hook.
3905
3908
  */
3906
3909
 
3907
- export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoPhysics, 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, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3908
3911
  //# sourceMappingURL=index.js.map
3909
3912
  //# sourceMappingURL=index.js.map