mujoco-react 1.0.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
@@ -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,20 +507,98 @@ 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",
281
- 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",
282
602
  11: "tendonpos",
283
603
  12: "tendonvel",
284
604
  13: "actuatorpos",
@@ -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({
@@ -1448,527 +1771,207 @@ function syncGizmoToSite(data, siteId, target) {
1448
1771
  siteMat[6],
1449
1772
  siteMat[7],
1450
1773
  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]);
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
+ });
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();
@@ -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, useMujocoSim, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3908
3911
  //# sourceMappingURL=index.js.map
3909
3912
  //# sourceMappingURL=index.js.map