mujoco-react 10.9.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,55 @@
1
- import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, createCaptureCamera, prepareCaptureCamera } from './chunk-5WFYM73E.js';
2
- export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-5WFYM73E.js';
1
+ import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, createCaptureCamera, prepareCaptureCamera } from './chunk-UCQ5MJP4.js';
2
+ export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-UCQ5MJP4.js';
3
3
  import loadMujoco from '@mujoco/mujoco';
4
4
  import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
5
5
  import { createContext, forwardRef, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect, useId } from 'react';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
7
  import { Canvas, useThree, useFrame } from '@react-three/fiber';
8
- import * as THREE11 from 'three';
8
+ import * as THREE12 from 'three';
9
9
  import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
10
10
  import { PivotControls } from '@react-three/drei';
11
11
 
12
+ // src/core/engineState.ts
13
+ var threadCounts = /* @__PURE__ */ new WeakMap();
14
+ function configureModuleThreads(module, count) {
15
+ threadCounts.set(module, count);
16
+ }
17
+ function initializeDataThreads(module, data) {
18
+ const count = threadCounts.get(module) ?? 0;
19
+ if (count > 0) module.mju_threadpool(data, count);
20
+ }
21
+ function captureSimulationState(module, model, data) {
22
+ const signature = module.mjtState.mjSTATE_INTEGRATION.value;
23
+ const buffer = new module.DoubleBuffer(module.mj_stateSize(model, signature));
24
+ try {
25
+ module.mj_getState(model, data, buffer, signature);
26
+ return {
27
+ time: data.time,
28
+ qpos: new Float64Array(data.qpos),
29
+ qvel: new Float64Array(data.qvel),
30
+ ctrl: new Float64Array(data.ctrl),
31
+ act: new Float64Array(data.act),
32
+ qfrc_applied: new Float64Array(data.qfrc_applied),
33
+ integration: { signature, values: new Float64Array(buffer.GetView()), model }
34
+ };
35
+ } finally {
36
+ buffer.delete();
37
+ }
38
+ }
39
+ function restoreSimulationState(module, model, data, snapshot) {
40
+ const integration = snapshot.integration;
41
+ if (integration && (integration.model !== model || integration.values.length !== module.mj_stateSize(model, integration.signature))) {
42
+ throw new Error("Snapshot belongs to a different model; capture a new snapshot after loading a scene");
43
+ }
44
+ for (const key of ["qpos", "qvel", "ctrl", "act", "qfrc_applied"]) {
45
+ if (snapshot[key].length !== data[key].length)
46
+ throw new Error(`Snapshot ${key} size does not match model`);
47
+ }
48
+ if (integration) module.mj_setState(model, data, Array.from(integration.values), integration.signature);
49
+ data.time = snapshot.time;
50
+ for (const key of ["qpos", "qvel", "ctrl", "act", "qfrc_applied"]) data[key].set(snapshot[key]);
51
+ module.mj_forward(model, data);
52
+ }
12
53
  var MujocoContext = createContext({
13
54
  mujoco: null,
14
55
  status: "loading",
@@ -36,6 +77,7 @@ function MujocoProvider({
36
77
  mtWasmUrl,
37
78
  threadedLoader,
38
79
  wasmVariant = "single",
80
+ threadCount = 0,
39
81
  timeout = 3e4,
40
82
  children,
41
83
  onError
@@ -43,12 +85,21 @@ function MujocoProvider({
43
85
  const [status, setStatus] = useState("loading");
44
86
  const [error, setError] = useState(null);
45
87
  const moduleRef = useRef(null);
46
- const isMounted = useRef(true);
47
88
  const onErrorRef = useRef(onError);
48
89
  onErrorRef.current = onError;
49
90
  useEffect(() => {
50
- isMounted.current = true;
91
+ let disposed = false;
92
+ moduleRef.current = null;
93
+ setStatus("loading");
94
+ setError(null);
51
95
  const variant = resolveWasmVariant(wasmVariant, threadedLoader, mtWasmUrl);
96
+ if (!Number.isInteger(threadCount) || threadCount < 0 || threadCount > 0 && variant !== "threaded" || variant === "threaded" && !canUseThreadedWasm()) {
97
+ const err = new Error("threadCount must be a nonnegative integer; threaded physics requires the MT loader and cross-origin isolation");
98
+ setError(err.message);
99
+ setStatus("error");
100
+ onErrorRef.current?.(err);
101
+ return;
102
+ }
52
103
  if (variant === "threaded" && !threadedLoader) {
53
104
  const err = new Error('MujocoProvider wasmVariant="threaded" requires a threadedLoader from @mujoco/mujoco/mt');
54
105
  setError(err.message);
@@ -71,35 +122,38 @@ function MujocoProvider({
71
122
  const wasmPromise = load({
72
123
  locateFile: (path) => path.endsWith(".wasm") ? selectedWasmUrl : path,
73
124
  printErr: (text) => {
74
- if (text.includes("Aborted") && isMounted.current) {
125
+ if (text.includes("Aborted") && !disposed) {
75
126
  setError("Simulation crashed. Reload page.");
76
127
  setStatus("error");
77
128
  }
78
129
  }
79
130
  });
131
+ let timeoutId;
80
132
  const timeoutPromise = new Promise(
81
- (_, reject) => setTimeout(() => reject(new Error(`WASM module load timed out after ${timeout}ms`)), timeout)
133
+ (_, reject) => timeoutId = setTimeout(() => reject(new Error(`WASM module load timed out after ${timeout}ms`)), timeout)
82
134
  );
83
135
  Promise.race([wasmPromise, timeoutPromise]).then((inst) => {
84
- if (isMounted.current) {
136
+ if (!disposed) {
85
137
  if (!isMujocoModule(inst)) {
86
138
  throw new Error("MuJoCo WASM module initialized with an unexpected shape");
87
139
  }
140
+ configureModuleThreads(inst, threadCount);
88
141
  moduleRef.current = inst;
89
142
  setStatus("ready");
90
143
  }
91
144
  }).catch((err) => {
92
- if (isMounted.current) {
145
+ if (!disposed) {
93
146
  const msg = err.message || "Failed to init spatial simulation";
94
147
  setError(msg);
95
148
  setStatus("error");
96
149
  onErrorRef.current?.(new Error(msg));
97
150
  }
98
- });
151
+ }).finally(() => clearTimeout(timeoutId));
99
152
  return () => {
100
- isMounted.current = false;
153
+ disposed = true;
154
+ clearTimeout(timeoutId);
101
155
  };
102
- }, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, timeout]);
156
+ }, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, threadCount, timeout]);
103
157
  return /* @__PURE__ */ jsx(
104
158
  MujocoContext.Provider,
105
159
  {
@@ -108,23 +162,6 @@ function MujocoProvider({
108
162
  }
109
163
  );
110
164
  }
111
- var CapsuleGeometry = class extends THREE11.BufferGeometry {
112
- parameters;
113
- constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
114
- super();
115
- this.type = "CapsuleGeometry";
116
- this.parameters = { radius, length, capSegments, radialSegments };
117
- const path = new THREE11.Path();
118
- path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
119
- path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
120
- const latheGeometry = new THREE11.LatheGeometry(path.getPoints(capSegments), radialSegments);
121
- const self = this;
122
- self.setIndex(latheGeometry.getIndex());
123
- self.setAttribute("position", latheGeometry.getAttribute("position"));
124
- self.setAttribute("normal", latheGeometry.getAttribute("normal"));
125
- self.setAttribute("uv", latheGeometry.getAttribute("uv"));
126
- }
127
- };
128
165
 
129
166
  // src/core/SceneLoader.ts
130
167
  var JOINT_TYPE_NAMES = {
@@ -150,7 +187,7 @@ function findSiteByName(mjModel, name) {
150
187
  return -1;
151
188
  }
152
189
  function findActuatorByName(mjModel, name) {
153
- for (let i = 0; i < mjModel.nu; i++) {
190
+ for (let i = 0; i < mjModel.nactuator; i++) {
154
191
  if (getName(mjModel, mjModel.name_actuatoradr[i]).includes(name)) return i;
155
192
  }
156
193
  return -1;
@@ -201,7 +238,7 @@ function findTendonByName(mjModel, name) {
201
238
  return -1;
202
239
  }
203
240
  function getActuatedScalarQposAdr(mjModel, actuatorId) {
204
- if (actuatorId < 0 || actuatorId >= mjModel.nu) return -1;
241
+ if (actuatorId < 0 || actuatorId >= mjModel.nactuator) return -1;
205
242
  const trnType = mjModel.actuator_trntype?.[actuatorId];
206
243
  if (trnType !== void 0 && trnType !== 0 && trnType !== 1) return -1;
207
244
  const jointId = mjModel.actuator_trnid[2 * actuatorId];
@@ -220,7 +257,7 @@ function isScalarJoint(mjModel, jointId) {
220
257
  return jointId >= 0 && jointId < mjModel.njnt && getScalarJointDim(mjModel.jnt_type[jointId]) === 1;
221
258
  }
222
259
  function getActuatorJointId(mjModel, actuatorId) {
223
- if (actuatorId < 0 || actuatorId >= mjModel.nu) return -1;
260
+ if (actuatorId < 0 || actuatorId >= mjModel.nactuator) return -1;
224
261
  const trnType = mjModel.actuator_trntype?.[actuatorId];
225
262
  if (trnType !== void 0 && trnType !== 0 && trnType !== 1) return -1;
226
263
  const jointId = mjModel.actuator_trnid[2 * actuatorId];
@@ -242,13 +279,38 @@ function getJointInfo(mjModel, jointId) {
242
279
  };
243
280
  }
244
281
  function getActuatorInfo(mjModel, actuatorId) {
245
- const hasRange = mjModel.actuator_ctrlrange[2 * actuatorId] < mjModel.actuator_ctrlrange[2 * actuatorId + 1];
282
+ const ctrlAdr = mjModel.actuator_ctrladr[actuatorId];
283
+ const ctrlCount = mjModel.actuator_ctrlnum[actuatorId];
284
+ const ranges = Array.from({ length: ctrlCount }, (_, input) => {
285
+ const adr = ctrlAdr + input;
286
+ const lo = mjModel.actuator_ctrlrange[2 * adr], hi = mjModel.actuator_ctrlrange[2 * adr + 1];
287
+ return lo < hi ? [lo, hi] : [-Infinity, Infinity];
288
+ });
246
289
  return {
247
290
  id: actuatorId,
248
291
  name: getName(mjModel, mjModel.name_actuatoradr[actuatorId]),
249
- range: hasRange ? [mjModel.actuator_ctrlrange[2 * actuatorId], mjModel.actuator_ctrlrange[2 * actuatorId + 1]] : unlimitedRange()
292
+ ctrlAdr,
293
+ ctrlCount,
294
+ ranges,
295
+ range: ranges[0] ?? [-Infinity, Infinity]
250
296
  };
251
297
  }
298
+ function getActuatorControlAddress(model, id, input) {
299
+ if (id < 0) return -1;
300
+ const count = model.actuator_ctrlnum[id];
301
+ if (input === void 0 && count !== 1) throw new Error("This actuator requires an explicit input index or a complete control vector");
302
+ const index = input ?? 0;
303
+ if (!Number.isInteger(index) || index < 0 || index >= count) throw new Error("Actuator input index out of range");
304
+ return model.actuator_ctrladr[id] + index;
305
+ }
306
+ function writeActuatorControl(model, data, id, value) {
307
+ if (id < 0) return;
308
+ const values = typeof value === "number" ? [value] : value;
309
+ if (values.length !== model.actuator_ctrlnum[id] || !values.every(Number.isFinite)) {
310
+ throw new Error("Actuator control must contain one finite value per input");
311
+ }
312
+ data.ctrl.set(values, model.actuator_ctrladr[id]);
313
+ }
252
314
  function includesResourceName(names, name) {
253
315
  return names.includes(name);
254
316
  }
@@ -284,7 +346,7 @@ function orderedActuatorIdsFromSelector(mjModel, selector) {
284
346
  return selector.map((name) => findActuatorByName(mjModel, name)).filter((id) => id >= 0 && getActuatorJointId(mjModel, id) >= 0);
285
347
  }
286
348
  const ids = [];
287
- for (let i = 0; i < mjModel.nu; i++) {
349
+ for (let i = 0; i < mjModel.nactuator; i++) {
288
350
  if (getActuatorJointId(mjModel, i) < 0) continue;
289
351
  const info = getActuatorInfo(mjModel, i);
290
352
  if (matchesSelector(info, selector)) ids.push(i);
@@ -323,9 +385,9 @@ function unique(values) {
323
385
  return result;
324
386
  }
325
387
  function findActuatorForJoint(mjModel, jointId, preferredActuatorIds) {
326
- const search = preferredActuatorIds ?? Array.from({ length: mjModel.nu }, (_, i) => i);
388
+ const search = preferredActuatorIds ?? Array.from({ length: mjModel.nactuator }, (_, i) => i);
327
389
  for (const actuatorId of search) {
328
- if (getActuatorJointId(mjModel, actuatorId) === jointId) return actuatorId;
390
+ if (mjModel.actuator_ctrlnum[actuatorId] === 1 && getActuatorJointId(mjModel, actuatorId) === jointId) return actuatorId;
329
391
  }
330
392
  return -1;
331
393
  }
@@ -345,12 +407,12 @@ function buildControlGroup(mjModel, jointIds, preferredActuatorIds) {
345
407
  if (actuatorId >= 0) {
346
408
  const actuator = getActuatorInfo(mjModel, actuatorId);
347
409
  actuators.push(actuator);
348
- ctrlAdr.push(actuatorId);
410
+ ctrlAdr.push(getActuatorControlAddress(mjModel, actuatorId));
349
411
  joints.push({
350
412
  ...joint,
351
413
  actuatorId,
352
414
  actuatorName: actuator.name,
353
- ctrlAdr: actuatorId,
415
+ ctrlAdr: getActuatorControlAddress(mjModel, actuatorId),
354
416
  ctrlRange: actuator.range
355
417
  });
356
418
  } else {
@@ -390,28 +452,29 @@ function buildControlGroup(mjModel, jointIds, preferredActuatorIds) {
390
452
  }
391
453
  function getActuatedJoints(mjModel) {
392
454
  const result = [];
393
- for (let actuatorId = 0; actuatorId < mjModel.nu; actuatorId++) {
455
+ for (let actuatorId = 0; actuatorId < mjModel.nactuator; actuatorId++) {
394
456
  const jointId = getActuatorJointId(mjModel, actuatorId);
395
- if (jointId < 0) continue;
457
+ if (jointId < 0 || mjModel.actuator_ctrlnum[actuatorId] !== 1) continue;
396
458
  const actuator = getActuatorInfo(mjModel, actuatorId);
397
459
  result.push({
398
460
  ...getJointInfo(mjModel, jointId),
399
461
  actuatorId,
400
462
  actuatorName: actuator.name,
401
- ctrlAdr: actuatorId,
463
+ ctrlAdr: getActuatorControlAddress(mjModel, actuatorId),
402
464
  ctrlRange: actuator.range
403
465
  });
404
466
  }
405
467
  return result;
406
468
  }
407
469
  function getControlMap(mjModel) {
408
- const actuatorIds = Array.from({ length: mjModel.nu }, (_, i) => i).filter((id) => getActuatorJointId(mjModel, id) >= 0);
470
+ const actuatorIds = Array.from({ length: mjModel.nactuator }, (_, i) => i).filter((id) => mjModel.actuator_ctrlnum[id] === 1 && getActuatorJointId(mjModel, id) >= 0);
409
471
  const jointIds = actuatorIds.map((id) => getActuatorJointId(mjModel, id));
410
472
  return buildControlGroup(mjModel, jointIds, actuatorIds) ?? createContiguousControlGroup(mjModel, 0);
411
473
  }
412
474
  function resolveControlGroup(mjModel, selector) {
413
475
  if (selector.actuators) {
414
476
  const actuatorIds = orderedActuatorIdsFromSelector(mjModel, selector.actuators);
477
+ for (const id of actuatorIds) getActuatorControlAddress(mjModel, id);
415
478
  const jointIds = actuatorIds.map((id) => getActuatorJointId(mjModel, id));
416
479
  return buildControlGroup(mjModel, jointIds, actuatorIds);
417
480
  }
@@ -429,6 +492,9 @@ function resolveControlGroup(mjModel, selector) {
429
492
  return getControlMap(mjModel);
430
493
  }
431
494
  function createContiguousControlGroup(mjModel, count) {
495
+ if (count > 0 && (mjModel.nactuator !== mjModel.nu || Array.from(mjModel.actuator_ctrlnum).some((n2) => n2 !== 1))) {
496
+ throw new Error("Legacy numJoints control groups require scalar-input actuators; use named controls");
497
+ }
432
498
  const n = Math.max(0, Math.min(count, mjModel.nq, mjModel.nu));
433
499
  const joints = [];
434
500
  const actuators = [];
@@ -778,12 +844,20 @@ async function loadSceneFromFiles(mujoco, config, onProgress) {
778
844
  onProgress?.("Loading model...");
779
845
  const mjModel = loadModelFromPath(mujoco, `/working/${config.sceneFile}`);
780
846
  const mjData = new mujoco.MjData(mjModel);
781
- applyInitialPose(mjModel, mjData, config);
782
- mujoco.mj_forward(mjModel, mjData);
783
- return { mjModel, mjData };
847
+ try {
848
+ initializeDataThreads(mujoco, mjData);
849
+ applyInitialPose(mjModel, mjData, config);
850
+ mujoco.mj_forward(mjModel, mjData);
851
+ return { mjModel, mjData };
852
+ } catch (error) {
853
+ mjData.delete();
854
+ mjModel.delete();
855
+ throw error;
856
+ }
784
857
  }
785
858
  function applyInitialPose(mjModel, mjData, config) {
786
859
  if (!config.homeJoints) return;
860
+ if (config.homeJoints.length && (mjModel.nactuator !== mjModel.nu || Array.from(mjModel.actuator_ctrlnum).some((n) => n !== 1))) throw new Error("homeJoints requires scalar-input actuators; use a keyframe for multi-input models");
787
861
  const homeCount = Math.min(config.homeJoints.length, Math.max(mjModel.nu, mjModel.nq));
788
862
  for (let i = 0; i < homeCount; i++) {
789
863
  if (i < mjModel.nu) mjData.ctrl[i] = config.homeJoints[i];
@@ -862,9 +936,16 @@ async function loadScene(mujoco, config, onProgress) {
862
936
  onProgress?.("Loading model...");
863
937
  const mjModel = loadModelFromPath(mujoco, `/working/${config.sceneFile}`);
864
938
  const mjData = new mujoco.MjData(mjModel);
865
- applyInitialPose(mjModel, mjData, config);
866
- mujoco.mj_forward(mjModel, mjData);
867
- return { mjModel, mjData };
939
+ try {
940
+ initializeDataThreads(mujoco, mjData);
941
+ applyInitialPose(mjModel, mjData, config);
942
+ mujoco.mj_forward(mjModel, mjData);
943
+ return { mjModel, mjData };
944
+ } catch (error) {
945
+ mjData.delete();
946
+ mjModel.delete();
947
+ throw error;
948
+ }
868
949
  }
869
950
  function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
870
951
  for (const fullPath of collectDependencyPaths(xmlString, currentFile, parser)) {
@@ -894,6 +975,23 @@ function collectDependencyPaths(xmlString, currentFile, parser) {
894
975
  });
895
976
  return paths;
896
977
  }
978
+ var CapsuleGeometry = class extends THREE12.BufferGeometry {
979
+ parameters;
980
+ constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
981
+ super();
982
+ this.type = "CapsuleGeometry";
983
+ this.parameters = { radius, length, capSegments, radialSegments };
984
+ const path = new THREE12.Path();
985
+ path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
986
+ path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
987
+ const latheGeometry = new THREE12.LatheGeometry(path.getPoints(capSegments), radialSegments);
988
+ const self = this;
989
+ self.setIndex(latheGeometry.getIndex());
990
+ self.setAttribute("position", latheGeometry.getAttribute("position"));
991
+ self.setAttribute("normal", latheGeometry.getAttribute("normal"));
992
+ self.setAttribute("uv", latheGeometry.getAttribute("uv"));
993
+ }
994
+ };
897
995
 
898
996
  // src/rendering/GeomBuilder.ts
899
997
  var DEFAULT_MESH_NORMAL_SMOOTHING_TOLERANCE = 1e-4;
@@ -944,10 +1042,10 @@ var GeomBuilder = class {
944
1042
  rgba[out + 2] = b;
945
1043
  rgba[out + 3] = a;
946
1044
  }
947
- const texture = new THREE11.DataTexture(rgba, width, height, THREE11.RGBAFormat);
948
- texture.colorSpace = THREE11.LinearSRGBColorSpace;
949
- texture.wrapS = THREE11.RepeatWrapping;
950
- texture.wrapT = THREE11.RepeatWrapping;
1045
+ const texture = new THREE12.DataTexture(rgba, width, height, THREE12.RGBAFormat);
1046
+ texture.colorSpace = THREE12.LinearSRGBColorSpace;
1047
+ texture.wrapS = THREE12.RepeatWrapping;
1048
+ texture.wrapT = THREE12.RepeatWrapping;
951
1049
  texture.flipY = true;
952
1050
  const repeatOffset = matId * 2;
953
1051
  const repeatS = mjModel.mat_texrepeat?.[repeatOffset] ?? 1;
@@ -968,7 +1066,7 @@ var GeomBuilder = class {
968
1066
  const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
969
1067
  const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
970
1068
  const matId = mjModel.geom_matid[g];
971
- const color = new THREE11.Color(16777215);
1069
+ const color = new THREE12.Color(16777215);
972
1070
  const map = this.getMaterialTexture(mjModel, matId);
973
1071
  let opacity = 1;
974
1072
  if (matId >= 0) {
@@ -984,16 +1082,16 @@ var GeomBuilder = class {
984
1082
  let geo = null;
985
1083
  const getVal = (v) => v?.value ?? v;
986
1084
  if (type === getVal(MG.mjGEOM_PLANE)) {
987
- geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
1085
+ geo = new THREE12.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
988
1086
  } else if (type === getVal(MG.mjGEOM_SPHERE)) {
989
- geo = new THREE11.SphereGeometry(size[0], 24, 24);
1087
+ geo = new THREE12.SphereGeometry(size[0], 24, 24);
990
1088
  } else if (type === getVal(MG.mjGEOM_CAPSULE)) {
991
1089
  geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
992
1090
  geo.rotateX(Math.PI / 2);
993
1091
  } else if (type === getVal(MG.mjGEOM_BOX)) {
994
- geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
1092
+ geo = new THREE12.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
995
1093
  } else if (type === getVal(MG.mjGEOM_CYLINDER)) {
996
- geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
1094
+ geo = new THREE12.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
997
1095
  geo.rotateX(Math.PI / 2);
998
1096
  } else if (type === getVal(MG.mjGEOM_MESH)) {
999
1097
  const mId = mjModel.geom_dataid[g];
@@ -1015,13 +1113,13 @@ var GeomBuilder = class {
1015
1113
  uvs[c * 2] = mjModel.mesh_texcoord[t * 2];
1016
1114
  uvs[c * 2 + 1] = 1 - mjModel.mesh_texcoord[t * 2 + 1];
1017
1115
  }
1018
- geo = new THREE11.BufferGeometry();
1019
- geo.setAttribute("position", new THREE11.Float32BufferAttribute(positions, 3));
1020
- geo.setAttribute("uv", new THREE11.Float32BufferAttribute(uvs, 2));
1116
+ geo = new THREE12.BufferGeometry();
1117
+ geo.setAttribute("position", new THREE12.Float32BufferAttribute(positions, 3));
1118
+ geo.setAttribute("uv", new THREE12.Float32BufferAttribute(uvs, 2));
1021
1119
  geo.computeVertexNormals();
1022
1120
  } else {
1023
- geo = new THREE11.BufferGeometry();
1024
- geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
1121
+ geo = new THREE12.BufferGeometry();
1122
+ geo.setAttribute("position", new THREE12.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
1025
1123
  geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
1026
1124
  const smoothingTolerance = this.getMeshNormalSmoothingTolerance();
1027
1125
  if (smoothingTolerance !== null) {
@@ -1037,7 +1135,7 @@ var GeomBuilder = class {
1037
1135
  materialMap.repeat.multiplyScalar(2.5);
1038
1136
  materialMap.needsUpdate = true;
1039
1137
  }
1040
- const mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
1138
+ const mesh = new THREE12.Mesh(geo, new THREE12.MeshStandardMaterial({
1041
1139
  color,
1042
1140
  map: materialMap,
1043
1141
  transparent: opacity < 1,
@@ -1102,7 +1200,7 @@ function SceneRenderer({ renderOptions, ...props }) {
1102
1200
  }
1103
1201
  const refs = [];
1104
1202
  for (let i = 0; i < model.nbody; i++) {
1105
- const bodyGroup = new THREE11.Group();
1203
+ const bodyGroup = new THREE12.Group();
1106
1204
  bodyGroup.userData.bodyID = i;
1107
1205
  const bodyName = getName(model, model.name_bodyadr[i]);
1108
1206
  if (!hiddenBodiesRef.current.has(bodyName)) {
@@ -1131,9 +1229,9 @@ function SceneRenderer({ renderOptions, ...props }) {
1131
1229
  const alpha = interpolation.alpha;
1132
1230
  const i3 = i * 3;
1133
1231
  ref.position.set(
1134
- THREE11.MathUtils.lerp(interpolation.previousXpos[i3], interpolation.currentXpos[i3], alpha),
1135
- THREE11.MathUtils.lerp(interpolation.previousXpos[i3 + 1], interpolation.currentXpos[i3 + 1], alpha),
1136
- THREE11.MathUtils.lerp(interpolation.previousXpos[i3 + 2], interpolation.currentXpos[i3 + 2], alpha)
1232
+ THREE12.MathUtils.lerp(interpolation.previousXpos[i3], interpolation.currentXpos[i3], alpha),
1233
+ THREE12.MathUtils.lerp(interpolation.previousXpos[i3 + 1], interpolation.currentXpos[i3 + 1], alpha),
1234
+ THREE12.MathUtils.lerp(interpolation.previousXpos[i3 + 2], interpolation.currentXpos[i3 + 2], alpha)
1137
1235
  );
1138
1236
  const i4 = i * 4;
1139
1237
  _previousQuat.set(
@@ -1193,8 +1291,8 @@ function SceneRenderer({ renderOptions, ...props }) {
1193
1291
  }
1194
1292
  );
1195
1293
  }
1196
- var _previousQuat = new THREE11.Quaternion();
1197
- var _currentQuat = new THREE11.Quaternion();
1294
+ var _previousQuat = new THREE12.Quaternion();
1295
+ var _currentQuat = new THREE12.Quaternion();
1198
1296
  var CameraViewportRegistryContext = createContext(null);
1199
1297
  var nextViewportId = 0;
1200
1298
  var VIEWPORT_RENDER_PRIORITY = 1;
@@ -1812,17 +1910,17 @@ async function recordMountedCameraFrameSequence(api, options) {
1812
1910
  readiness
1813
1911
  };
1814
1912
  }
1815
- var _raycaster = new THREE11.Raycaster();
1816
- var _ndc = new THREE11.Vector2();
1913
+ var _raycaster = new THREE12.Raycaster();
1914
+ var _ndc = new THREE12.Vector2();
1817
1915
  function toVector3(value, fallback) {
1818
1916
  if (!value) return fallback.clone();
1819
- return value instanceof THREE11.Vector3 ? value.clone() : new THREE11.Vector3(value[0], value[1], value[2]);
1917
+ return value instanceof THREE12.Vector3 ? value.clone() : new THREE12.Vector3(value[0], value[1], value[2]);
1820
1918
  }
1821
1919
  function applyCameraPose(camera, options, fallbackCamera) {
1822
1920
  camera.position.copy(toVector3(options.position, fallbackCamera.position));
1823
1921
  camera.up.copy(toVector3(options.up, fallbackCamera.up));
1824
1922
  if (options.quaternion) {
1825
- if (options.quaternion instanceof THREE11.Quaternion) {
1923
+ if (options.quaternion instanceof THREE12.Quaternion) {
1826
1924
  camera.quaternion.copy(options.quaternion);
1827
1925
  } else {
1828
1926
  camera.quaternion.set(
@@ -1833,15 +1931,15 @@ function applyCameraPose(camera, options, fallbackCamera) {
1833
1931
  );
1834
1932
  }
1835
1933
  } else if (options.lookAt) {
1836
- camera.lookAt(toVector3(options.lookAt, new THREE11.Vector3()));
1934
+ camera.lookAt(toVector3(options.lookAt, new THREE12.Vector3()));
1837
1935
  } else {
1838
1936
  camera.quaternion.copy(fallbackCamera.quaternion);
1839
1937
  }
1840
1938
  camera.updateMatrixWorld();
1841
1939
  }
1842
1940
  function createProjectionCamera(fallbackCamera, options, width, height) {
1843
- const camera = options.camera ? options.camera.clone() : fallbackCamera instanceof THREE11.PerspectiveCamera ? fallbackCamera.clone() : new THREE11.PerspectiveCamera(45, width / height, 0.01, 100);
1844
- if (camera instanceof THREE11.PerspectiveCamera) {
1941
+ const camera = options.camera ? options.camera.clone() : fallbackCamera instanceof THREE12.PerspectiveCamera ? fallbackCamera.clone() : new THREE12.PerspectiveCamera(45, width / height, 0.01, 100);
1942
+ if (camera instanceof THREE12.PerspectiveCamera) {
1845
1943
  camera.aspect = width / height;
1846
1944
  camera.fov = options.fov ?? camera.fov;
1847
1945
  camera.near = options.near ?? camera.near;
@@ -2006,8 +2104,8 @@ var _applyPoint = new Float64Array(3);
2006
2104
  var _rayPnt = new Float64Array(3);
2007
2105
  var _rayVec = new Float64Array(3);
2008
2106
  var _rayGeomId = new Int32Array(1);
2009
- var _projRaycaster = new THREE11.Raycaster();
2010
- var _projNdc = new THREE11.Vector2();
2107
+ var _projRaycaster = new THREE12.Raycaster();
2108
+ var _projNdc = new THREE12.Vector2();
2011
2109
  function waitForNextAnimationFrame2() {
2012
2110
  return new Promise((resolve) => {
2013
2111
  requestAnimationFrame(() => resolve());
@@ -2032,7 +2130,7 @@ function quaternionFromMujocoQuat(values, offset) {
2032
2130
  ];
2033
2131
  }
2034
2132
  function quaternionFromXmat(values, offset) {
2035
- const matrix = new THREE11.Matrix4();
2133
+ const matrix = new THREE12.Matrix4();
2036
2134
  matrix.set(
2037
2135
  values[offset],
2038
2136
  values[offset + 1],
@@ -2051,7 +2149,7 @@ function quaternionFromXmat(values, offset) {
2051
2149
  0,
2052
2150
  1
2053
2151
  );
2054
- const quaternion = new THREE11.Quaternion().setFromRotationMatrix(matrix);
2152
+ const quaternion = new THREE12.Quaternion().setFromRotationMatrix(matrix);
2055
2153
  return [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
2056
2154
  }
2057
2155
  function omitResolvedCameraSelectors(options) {
@@ -2059,14 +2157,14 @@ function omitResolvedCameraSelectors(options) {
2059
2157
  return rest;
2060
2158
  }
2061
2159
  function vector3FromCaptureValue(value) {
2062
- return value instanceof THREE11.Vector3 ? value.clone() : new THREE11.Vector3(value[0], value[1], value[2]);
2160
+ return value instanceof THREE12.Vector3 ? value.clone() : new THREE12.Vector3(value[0], value[1], value[2]);
2063
2161
  }
2064
2162
  function quaternionFromCaptureValue(value) {
2065
- return value instanceof THREE11.Quaternion ? value.clone() : new THREE11.Quaternion(value[0], value[1], value[2], value[3]);
2163
+ return value instanceof THREE12.Quaternion ? value.clone() : new THREE12.Quaternion(value[0], value[1], value[2], value[3]);
2066
2164
  }
2067
2165
  function applyMountedCameraPoseOffsets(options, position, quaternion) {
2068
- const resolvedPosition = new THREE11.Vector3(position[0], position[1], position[2]);
2069
- const resolvedQuaternion = new THREE11.Quaternion(
2166
+ const resolvedPosition = new THREE12.Vector3(position[0], position[1], position[2]);
2167
+ const resolvedQuaternion = new THREE12.Quaternion(
2070
2168
  quaternion[0],
2071
2169
  quaternion[1],
2072
2170
  quaternion[2],
@@ -2151,7 +2249,7 @@ function mujocoCameraProjectionMatrix(model, cameraId, width, height, near, far)
2151
2249
  const cy = height * (0.5 + principalY / sensorHeight);
2152
2250
  const znear = near ?? 0.01;
2153
2251
  const zfar = far ?? 100;
2154
- return new THREE11.Matrix4().set(
2252
+ return new THREE12.Matrix4().set(
2155
2253
  2 * fx / width,
2156
2254
  0,
2157
2255
  1 - 2 * cx / width,
@@ -2293,6 +2391,7 @@ function MujocoSimProvider({
2293
2391
  onSelection,
2294
2392
  gravity,
2295
2393
  timestep,
2394
+ integrator,
2296
2395
  substeps,
2297
2396
  paused,
2298
2397
  speed,
@@ -2349,6 +2448,15 @@ function MujocoSimProvider({
2349
2448
  model.opt.gravity[1] = gravity[1];
2350
2449
  model.opt.gravity[2] = gravity[2];
2351
2450
  }, [gravity]);
2451
+ const integratorRef = useRef(integrator);
2452
+ integratorRef.current = integrator;
2453
+ const applyIntegrator = (model) => {
2454
+ const value = integratorRef.current;
2455
+ if (value !== void 0) model.opt.integrator = { Euler: 0, RK4: 1, implicit: 2, implicitfast: 3, discrete: 4 }[value];
2456
+ };
2457
+ useEffect(() => {
2458
+ if (mjModelRef.current) applyIntegrator(mjModelRef.current);
2459
+ }, [integrator]);
2352
2460
  useEffect(() => {
2353
2461
  if (timestep === void 0) return;
2354
2462
  const model = mjModelRef.current;
@@ -2394,11 +2502,12 @@ function MujocoSimProvider({
2394
2502
  try {
2395
2503
  const result = await loadScene(mujoco, buildMergedConfig(config));
2396
2504
  if (disposed) {
2397
- result.mjModel.delete();
2398
2505
  result.mjData.delete();
2506
+ result.mjModel.delete();
2399
2507
  return;
2400
2508
  }
2401
2509
  mujocoRef.current = mujoco;
2510
+ applyIntegrator(result.mjModel);
2402
2511
  mjModelRef.current = result.mjModel;
2403
2512
  mjDataRef.current = result.mjData;
2404
2513
  physicsAccumulatorRef.current = 0;
@@ -2423,8 +2532,8 @@ function MujocoSimProvider({
2423
2532
  })();
2424
2533
  return () => {
2425
2534
  disposed = true;
2426
- mjModelRef.current?.delete();
2427
2535
  mjDataRef.current?.delete();
2536
+ mjModelRef.current?.delete();
2428
2537
  mjModelRef.current = null;
2429
2538
  mjDataRef.current = null;
2430
2539
  physicsAccumulatorRef.current = 0;
@@ -2596,26 +2705,15 @@ function MujocoSimProvider({
2596
2705
  const saveState = useCallback(() => {
2597
2706
  const data = mjDataRef.current;
2598
2707
  if (!data) return { time: 0, qpos: new Float64Array(0), qvel: new Float64Array(0), ctrl: new Float64Array(0), act: new Float64Array(0), qfrc_applied: new Float64Array(0) };
2599
- return {
2600
- time: data.time,
2601
- qpos: new Float64Array(data.qpos),
2602
- qvel: new Float64Array(data.qvel),
2603
- ctrl: new Float64Array(data.ctrl),
2604
- act: new Float64Array(data.act),
2605
- qfrc_applied: new Float64Array(data.qfrc_applied)
2606
- };
2708
+ return captureSimulationState(mujocoRef.current, mjModelRef.current, data);
2607
2709
  }, []);
2608
2710
  const restoreState = useCallback((snapshot) => {
2609
2711
  const model = mjModelRef.current;
2610
2712
  const data = mjDataRef.current;
2611
2713
  if (!model || !data) return;
2612
- data.time = snapshot.time;
2613
- data.qpos.set(snapshot.qpos);
2614
- data.qvel.set(snapshot.qvel);
2615
- data.ctrl.set(snapshot.ctrl);
2616
- if (snapshot.act.length > 0) data.act.set(snapshot.act);
2617
- data.qfrc_applied.set(snapshot.qfrc_applied);
2618
- mujocoRef.current.mj_forward(model, data);
2714
+ restoreSimulationState(mujocoRef.current, model, data, snapshot);
2715
+ physicsAccumulatorRef.current = 0;
2716
+ interpolationStateRef.current.valid = false;
2619
2717
  }, [mujoco]);
2620
2718
  const setQpos = useCallback((values) => {
2621
2719
  const model = mjModelRef.current;
@@ -2643,11 +2741,11 @@ function MujocoSimProvider({
2643
2741
  if (!model || !data) return;
2644
2742
  if (typeof nameOrValues === "string") {
2645
2743
  const id = findActuatorByName(model, nameOrValues);
2646
- if (id >= 0 && value !== void 0) data.ctrl[id] = value;
2744
+ if (value !== void 0) writeActuatorControl(model, data, id, value);
2647
2745
  } else {
2648
2746
  for (const [name, val] of Object.entries(nameOrValues)) {
2649
2747
  const id = findActuatorByName(model, name);
2650
- if (id >= 0) data.ctrl[id] = val;
2748
+ writeActuatorControl(model, data, id, val);
2651
2749
  }
2652
2750
  }
2653
2751
  }, []);
@@ -2818,15 +2916,7 @@ function MujocoSimProvider({
2818
2916
  const getActuatorsApi = useCallback(() => {
2819
2917
  const model = mjModelRef.current;
2820
2918
  if (!model) return [];
2821
- const result = [];
2822
- for (let i = 0; i < model.nu; i++) {
2823
- const hasRange = model.actuator_ctrlrange[2 * i] < model.actuator_ctrlrange[2 * i + 1];
2824
- result.push({
2825
- id: i,
2826
- name: getName(model, model.name_actuatoradr[i]),
2827
- range: hasRange ? [model.actuator_ctrlrange[2 * i], model.actuator_ctrlrange[2 * i + 1]] : [-Infinity, Infinity]
2828
- });
2829
- }
2919
+ const result = Array.from({ length: model.nactuator }, (_, id) => getActuatorInfo(model, id));
2830
2920
  return result;
2831
2921
  }, []);
2832
2922
  const getControlMapApi = useCallback(() => {
@@ -3015,7 +3105,7 @@ function MujocoSimProvider({
3015
3105
  const geomId = _rayGeomId[0];
3016
3106
  const bodyId = geomId >= 0 ? model.geom_bodyid[geomId] : -1;
3017
3107
  return {
3018
- point: new THREE11.Vector3(
3108
+ point: new THREE12.Vector3(
3019
3109
  origin.x + dir.x * dist,
3020
3110
  origin.y + dir.y * dist,
3021
3111
  origin.z + dir.z * dist
@@ -3072,17 +3162,18 @@ function MujocoSimProvider({
3072
3162
  const loadSceneApi = useCallback(async (newConfig) => {
3073
3163
  const gen = ++loadGenRef.current;
3074
3164
  try {
3075
- mjModelRef.current?.delete();
3076
3165
  mjDataRef.current?.delete();
3166
+ mjModelRef.current?.delete();
3077
3167
  mjModelRef.current = null;
3078
3168
  mjDataRef.current = null;
3079
3169
  setStatus("loading");
3080
3170
  const result = await loadScene(mujoco, buildMergedConfig(newConfig));
3081
3171
  if (gen !== loadGenRef.current) {
3082
- result.mjModel.delete();
3083
3172
  result.mjData.delete();
3173
+ result.mjModel.delete();
3084
3174
  return;
3085
3175
  }
3176
+ applyIntegrator(result.mjModel);
3086
3177
  mjModelRef.current = result.mjModel;
3087
3178
  mjDataRef.current = result.mjData;
3088
3179
  physicsAccumulatorRef.current = 0;
@@ -3624,6 +3715,7 @@ var MujocoCanvas = forwardRef(
3624
3715
  // Declarative physics config
3625
3716
  gravity,
3626
3717
  timestep,
3718
+ integrator,
3627
3719
  substeps,
3628
3720
  paused,
3629
3721
  speed,
@@ -3660,6 +3752,7 @@ var MujocoCanvas = forwardRef(
3660
3752
  onSelection,
3661
3753
  gravity,
3662
3754
  timestep,
3755
+ integrator,
3663
3756
  substeps,
3664
3757
  paused,
3665
3758
  speed,
@@ -3821,8 +3914,6 @@ function createControllerHook(options, useImpl) {
3821
3914
  };
3822
3915
  return useController;
3823
3916
  }
3824
-
3825
- // src/core/GenericIK.ts
3826
3917
  var DEFAULTS = {
3827
3918
  maxIterations: 50,
3828
3919
  damping: 0.01,
@@ -3845,130 +3936,168 @@ function resolveOptions(opts) {
3845
3936
  };
3846
3937
  }
3847
3938
  var GenericIK = class {
3848
- mujoco;
3849
3939
  constructor(mujoco) {
3850
3940
  this.mujoco = mujoco;
3851
3941
  }
3942
+ /** Scalar hinge/slide interface. Returns values in qposAdr order. */
3943
+ solve(model, data, siteId, qposAdr, targetPos, targetQuat, currentQ, opts) {
3944
+ if (currentQ.length !== qposAdr.length) throw new Error("IK currentQ must match qposAdr length");
3945
+ const jointIds = Array.from(qposAdr, (adr) => {
3946
+ const id = Array.from({ length: model.njnt }, (_, i) => i).find(
3947
+ (i) => model.jnt_qposadr[i] === adr
3948
+ );
3949
+ if (id === void 0 || model.jnt_type[id] < 2) {
3950
+ throw new Error(
3951
+ "Scalar IK requires hinge/slide qpos addresses; use solveJoints for ball/free joints"
3952
+ );
3953
+ }
3954
+ return id;
3955
+ });
3956
+ const initial = new Float64Array(data.qpos);
3957
+ for (let i = 0; i < qposAdr.length; i++) initial[qposAdr[i]] = currentQ[i];
3958
+ const result = this.solveJoints(model, data, siteId, jointIds, targetPos, targetQuat, initial, opts);
3959
+ return result ? Array.from(qposAdr, (adr) => result[adr]) : null;
3960
+ }
3852
3961
  /**
3853
- * Solve IK for a target 6-DOF pose.
3854
- * @param model MuJoCo model
3855
- * @param data MuJoCo data (qpos will be temporarily modified, then restored)
3856
- * @param siteId Index of the end-effector site to control
3857
- * @param qposAdr qpos addresses for scalar joints in solve order
3858
- * @param targetPos Target position in world frame
3859
- * @param targetQuat Target orientation in world frame
3860
- * @param currentQ Current joint angles matching qposAdr order
3861
- * @param opts Optional solver parameters
3862
- * @returns Joint angles array, or null if solver diverged
3962
+ * Solve selected joint IDs, including ball/free joints. Initial and returned
3963
+ * configurations have model.nq entries, with MuJoCo wxyz quaternions.
3964
+ * Jacobian columns use nv/dof addresses; mj_integratePos updates quaternions.
3965
+ * The caller's qpos is restored even if the solve throws.
3863
3966
  */
3864
- solve(model, data, siteId, qposAdr, targetPos, targetQuat, currentQ, opts) {
3967
+ solveJoints(model, data, siteId, jointIds, targetPos, targetQuat, currentQpos = data.qpos, opts) {
3865
3968
  const o = resolveOptions(opts);
3866
- const n = qposAdr.length;
3867
- const savedQpos = new Float64Array(data.qpos.length);
3868
- savedQpos.set(data.qpos);
3869
- const R_target = quatToMat3(targetQuat);
3870
- const q = new Float64Array(n);
3871
- for (let i = 0; i < n; i++) q[i] = currentQ[i];
3872
- const J = new Float64Array(6 * n);
3873
- const JJt = new Float64Array(36);
3874
- const rhs = new Float64Array(6);
3875
- const x = new Float64Array(6);
3876
- const dq = new Float64Array(n);
3877
- const baseSitePos = new Float64Array(3);
3878
- const baseSiteMat = new Float64Array(9);
3879
- const pertSitePos = new Float64Array(3);
3880
- const pertSiteMat = new Float64Array(9);
3881
- let bestQ = null;
3882
- let bestErr = Infinity;
3883
- const patience = 4;
3884
- let noImprove = 0;
3885
- if (n === 0) return null;
3886
- for (let iter = 0; iter < o.maxIterations; iter++) {
3887
- for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
3888
- this.mujoco.mj_forward(model, data);
3889
- const sp = data.site_xpos;
3890
- const sm = data.site_xmat;
3891
- const off3 = siteId * 3;
3892
- const off9 = siteId * 9;
3893
- for (let i = 0; i < 3; i++) baseSitePos[i] = sp[off3 + i];
3894
- for (let i = 0; i < 9; i++) baseSiteMat[i] = sm[off9 + i];
3895
- const posErr0 = targetPos.x - baseSitePos[0];
3896
- const posErr1 = targetPos.y - baseSitePos[1];
3897
- const posErr2 = targetPos.z - baseSitePos[2];
3898
- const rotErr = orientationError(baseSiteMat, R_target);
3899
- const error = [
3900
- posErr0 * o.posWeight,
3901
- posErr1 * o.posWeight,
3902
- posErr2 * o.posWeight,
3903
- rotErr[0] * o.rotWeight,
3904
- rotErr[1] * o.rotWeight,
3905
- rotErr[2] * o.rotWeight
3906
- ];
3907
- const errNorm = Math.sqrt(
3908
- error[0] * error[0] + error[1] * error[1] + error[2] * error[2] + error[3] * error[3] + error[4] * error[4] + error[5] * error[5]
3909
- );
3910
- if (errNorm < bestErr - 1e-9) {
3911
- bestErr = errNorm;
3912
- bestQ = Array.from(q);
3913
- noImprove = 0;
3914
- } else {
3915
- noImprove++;
3916
- }
3917
- if (errNorm < o.tolerance) break;
3918
- if (noImprove >= patience) break;
3919
- for (let j = 0; j < n; j++) {
3920
- const adr = qposAdr[j];
3921
- const saved = data.qpos[adr];
3922
- data.qpos[adr] = q[j] + o.epsilon;
3969
+ const targetRotation = new THREE12.Quaternion(targetQuat.x, targetQuat.y, targetQuat.z, targetQuat.w);
3970
+ if (!Number.isInteger(siteId) || siteId < 0 || siteId >= model.nsite)
3971
+ throw new Error("Invalid IK site ID");
3972
+ if (currentQpos.length !== model.nq || !Array.from(currentQpos).every(Number.isFinite))
3973
+ throw new Error("IK configuration must contain nq finite values");
3974
+ if (![
3975
+ targetPos.x,
3976
+ targetPos.y,
3977
+ targetPos.z,
3978
+ targetQuat.x,
3979
+ targetQuat.y,
3980
+ targetQuat.z,
3981
+ targetQuat.w
3982
+ ].every(Number.isFinite) || targetRotation.lengthSq() === 0)
3983
+ throw new Error("IK target must be finite with a nonzero quaternion");
3984
+ if (!(o.damping > 0) || !(o.maxStepRad > 0) || !Number.isFinite(o.maxStepRad) || !Number.isInteger(o.maxIterations) || o.maxIterations < 1)
3985
+ throw new Error("Invalid IK solver options");
3986
+ const ids = Array.from(jointIds);
3987
+ if (new Set(ids).size !== ids.length || ids.some((id) => !Number.isInteger(id) || id < 0 || id >= model.njnt))
3988
+ throw new Error("IK joint IDs must be unique and valid");
3989
+ const dofs = ids.flatMap(
3990
+ (id) => Array.from(
3991
+ { length: model.jnt_type[id] === 0 ? 6 : model.jnt_type[id] === 1 ? 3 : 1 },
3992
+ (_, i) => model.jnt_dofadr[id] + i
3993
+ )
3994
+ );
3995
+ const n = dofs.length;
3996
+ if (!n) return null;
3997
+ const savedQpos = new Float64Array(data.qpos);
3998
+ const buffers = [];
3999
+ try {
4000
+ const alloc = (size) => {
4001
+ const b = new this.mujoco.DoubleBuffer(size);
4002
+ buffers.push(b);
4003
+ return b;
4004
+ };
4005
+ const jacp = alloc(3 * model.nv), jacr = alloc(3 * model.nv), qbuffer = alloc(model.nq);
4006
+ qbuffer.GetView().set(currentQpos);
4007
+ const target = quatToMat3(targetRotation.normalize());
4008
+ const J = new Float64Array(6 * n), JJt = new Float64Array(36), rhs = new Float64Array(6), x = new Float64Array(6);
4009
+ const velocity = new Array(model.nv).fill(0);
4010
+ let best = null, bestErr = Infinity, noImprove = 0;
4011
+ const clamp = () => {
4012
+ const q = qbuffer.GetView();
4013
+ ids.forEach((id, i) => {
4014
+ const adr = model.jnt_qposadr[id], type = model.jnt_type[id];
4015
+ if (type >= 2) {
4016
+ const limits = o.jointLimits?.[i] ?? (model.jnt_limited[id] ? [model.jnt_range[2 * id], model.jnt_range[2 * id + 1]] : null);
4017
+ if (limits)
4018
+ q[adr] = Math.max(Math.min(...limits), Math.min(Math.max(...limits), q[adr]));
4019
+ } else {
4020
+ const offset = adr + (type === 0 ? 3 : 0);
4021
+ const quat = new THREE12.Quaternion(
4022
+ q[offset + 1],
4023
+ q[offset + 2],
4024
+ q[offset + 3],
4025
+ q[offset]
4026
+ ).normalize();
4027
+ if (type === 1 && model.jnt_limited[id]) {
4028
+ if (quat.w < 0) {
4029
+ quat.x *= -1;
4030
+ quat.y *= -1;
4031
+ quat.z *= -1;
4032
+ quat.w *= -1;
4033
+ }
4034
+ const angle = 2 * Math.acos(Math.min(1, Math.max(-1, quat.w)));
4035
+ const limit = model.jnt_range[2 * id + 1];
4036
+ if (angle > limit)
4037
+ quat.identity().slerp(
4038
+ new THREE12.Quaternion(
4039
+ q[offset + 1],
4040
+ q[offset + 2],
4041
+ q[offset + 3],
4042
+ q[offset]
4043
+ ).normalize(),
4044
+ limit / angle
4045
+ );
4046
+ }
4047
+ q.set([quat.w, quat.x, quat.y, quat.z], offset);
4048
+ }
4049
+ });
4050
+ };
4051
+ clamp();
4052
+ for (let iter = 0; iter < o.maxIterations; iter++) {
4053
+ data.qpos.set(qbuffer.GetView());
3923
4054
  this.mujoco.mj_forward(model, data);
3924
- for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
3925
- for (let i = 0; i < 9; i++) pertSiteMat[i] = sm[off9 + i];
3926
- J[0 * n + j] = (pertSitePos[0] - baseSitePos[0]) / o.epsilon * o.posWeight;
3927
- J[1 * n + j] = (pertSitePos[1] - baseSitePos[1]) / o.epsilon * o.posWeight;
3928
- J[2 * n + j] = (pertSitePos[2] - baseSitePos[2]) / o.epsilon * o.posWeight;
3929
- const dRot = angularDelta(baseSiteMat, pertSiteMat);
3930
- J[3 * n + j] = dRot[0] / o.epsilon * o.rotWeight;
3931
- J[4 * n + j] = dRot[1] / o.epsilon * o.rotWeight;
3932
- J[5 * n + j] = dRot[2] / o.epsilon * o.rotWeight;
3933
- data.qpos[adr] = saved;
3934
- }
3935
- for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
3936
- for (let r = 0; r < 6; r++) {
3937
- for (let c = 0; c < 6; c++) {
3938
- let sum = 0;
3939
- for (let k = 0; k < n; k++) {
3940
- sum += J[r * n + k] * J[c * n + k];
4055
+ const pos = data.site_xpos.subarray(3 * siteId, 3 * siteId + 3);
4056
+ const rot = orientationError(data.site_xmat.subarray(9 * siteId, 9 * siteId + 9), target);
4057
+ const error = [
4058
+ (targetPos.x - pos[0]) * o.posWeight,
4059
+ (targetPos.y - pos[1]) * o.posWeight,
4060
+ (targetPos.z - pos[2]) * o.posWeight,
4061
+ ...rot.map((v) => v * o.rotWeight)
4062
+ ];
4063
+ const norm = Math.hypot(...error);
4064
+ if (!Number.isFinite(norm)) break;
4065
+ if (norm < bestErr - 1e-9) {
4066
+ bestErr = norm;
4067
+ best = Array.from(qbuffer.GetView());
4068
+ noImprove = 0;
4069
+ } else noImprove++;
4070
+ if (norm < o.tolerance || noImprove >= 4) break;
4071
+ this.mujoco.mj_jacSite(model, data, jacp, jacr, siteId);
4072
+ const jp = jacp.GetView(), jr = jacr.GetView();
4073
+ for (let r = 0; r < 3; r++)
4074
+ for (let j = 0; j < n; j++) {
4075
+ J[r * n + j] = jp[r * model.nv + dofs[j]] * o.posWeight;
4076
+ J[(r + 3) * n + j] = jr[r * model.nv + dofs[j]] * o.rotWeight;
3941
4077
  }
3942
- JJt[r * 6 + c] = sum + (r === c ? o.damping : 0);
3943
- }
3944
- }
3945
- for (let i = 0; i < 6; i++) rhs[i] = error[i];
3946
- solve6x6(JJt, rhs, x);
3947
- for (let j = 0; j < n; j++) {
3948
- let sum = 0;
3949
- for (let r = 0; r < 6; r++) {
3950
- sum += J[r * n + j] * x[r];
3951
- }
3952
- dq[j] = sum;
3953
- }
3954
- for (let i = 0; i < n; i++) {
3955
- let step = dq[i];
3956
- if (step > o.maxStepRad) step = o.maxStepRad;
3957
- else if (step < -o.maxStepRad) step = -o.maxStepRad;
3958
- let next = q[i] + step;
3959
- const limits = o.jointLimits?.[i];
3960
- if (limits) {
3961
- const low = Math.min(limits[0], limits[1]);
3962
- const high = Math.max(limits[0], limits[1]);
3963
- if (next < low) next = low;
3964
- else if (next > high) next = high;
4078
+ for (let r = 0; r < 6; r++)
4079
+ for (let c = 0; c < 6; c++) {
4080
+ let sum = 0;
4081
+ for (let k = 0; k < n; k++) sum += J[r * n + k] * J[c * n + k];
4082
+ JJt[r * 6 + c] = sum + (r === c ? o.damping : 0);
4083
+ }
4084
+ rhs.set(error);
4085
+ solve6x6(JJt, rhs, x);
4086
+ velocity.fill(0);
4087
+ for (let j = 0; j < n; j++) {
4088
+ let step = 0;
4089
+ for (let r = 0; r < 6; r++) step += J[r * n + j] * x[r];
4090
+ velocity[dofs[j]] = Math.max(-o.maxStepRad, Math.min(o.maxStepRad, step));
3965
4091
  }
3966
- q[i] = next;
4092
+ this.mujoco.mj_integratePos(model, qbuffer, velocity, 1);
4093
+ clamp();
3967
4094
  }
4095
+ return best;
4096
+ } finally {
4097
+ for (const buffer of buffers) buffer.delete();
4098
+ data.qpos.set(savedQpos);
4099
+ this.mujoco.mj_forward(model, data);
3968
4100
  }
3969
- data.qpos.set(savedQpos);
3970
- this.mujoco.mj_forward(model, data);
3971
- return bestQ;
3972
4101
  }
3973
4102
  };
3974
4103
  function quatToMat3(q) {
@@ -4005,36 +4134,31 @@ function orientationError(R_cur, R_tgt) {
4005
4134
  if (angle < 1e-6) {
4006
4135
  return [0, 0, 0];
4007
4136
  }
4008
- if (angle > Math.PI - 1e-6) {
4009
- return [
4010
- 0.5 * (Re[7] - Re[5]),
4011
- 0.5 * (Re[2] - Re[6]),
4012
- 0.5 * (Re[3] - Re[1])
4013
- ];
4137
+ if (angle > Math.PI - 1e-4) {
4138
+ const matrix = new THREE12.Matrix4().set(
4139
+ Re[0],
4140
+ Re[1],
4141
+ Re[2],
4142
+ 0,
4143
+ Re[3],
4144
+ Re[4],
4145
+ Re[5],
4146
+ 0,
4147
+ Re[6],
4148
+ Re[7],
4149
+ Re[8],
4150
+ 0,
4151
+ 0,
4152
+ 0,
4153
+ 0,
4154
+ 1
4155
+ );
4156
+ const q = new THREE12.Quaternion().setFromRotationMatrix(matrix).normalize();
4157
+ const scale = angle / Math.hypot(q.x, q.y, q.z) * (q.w < 0 ? -1 : 1);
4158
+ return [q.x * scale, q.y * scale, q.z * scale];
4014
4159
  }
4015
4160
  const s = angle / (2 * Math.sin(angle));
4016
- return [
4017
- s * (Re[7] - Re[5]),
4018
- s * (Re[2] - Re[6]),
4019
- s * (Re[3] - Re[1])
4020
- ];
4021
- }
4022
- function angularDelta(R_base, R_pert) {
4023
- const dR = new Float64Array(9);
4024
- for (let i = 0; i < 3; i++) {
4025
- for (let j = 0; j < 3; j++) {
4026
- let s = 0;
4027
- for (let k = 0; k < 3; k++) {
4028
- s += R_pert[i * 3 + k] * R_base[j * 3 + k];
4029
- }
4030
- dR[i * 3 + j] = s;
4031
- }
4032
- }
4033
- return [
4034
- 0.5 * (dR[7] - dR[5]),
4035
- 0.5 * (dR[2] - dR[6]),
4036
- 0.5 * (dR[3] - dR[1])
4037
- ];
4161
+ return [s * (Re[7] - Re[5]), s * (Re[2] - Re[6]), s * (Re[3] - Re[1])];
4038
4162
  }
4039
4163
  function solve6x6(A, b, x) {
4040
4164
  const N = 6;
@@ -4083,12 +4207,12 @@ function solve6x6(A, b, x) {
4083
4207
  }
4084
4208
 
4085
4209
  // src/hooks/useIkController.ts
4086
- var _syncMat4 = new THREE11.Matrix4();
4210
+ var _syncMat4 = new THREE12.Matrix4();
4087
4211
  function toIkVector3(value) {
4088
- return "x" in value ? new THREE11.Vector3(value.x, value.y, value.z) : new THREE11.Vector3(value[0], value[1], value[2]);
4212
+ return "x" in value ? new THREE12.Vector3(value.x, value.y, value.z) : new THREE12.Vector3(value[0], value[1], value[2]);
4089
4213
  }
4090
4214
  function toIkQuaternion(value) {
4091
- return "x" in value ? new THREE11.Quaternion(value.x, value.y, value.z, value.w) : new THREE11.Quaternion(value[0], value[1], value[2], value[3]);
4215
+ return "x" in value ? new THREE12.Quaternion(value.x, value.y, value.z, value.w) : new THREE12.Quaternion(value[0], value[1], value[2], value[3]);
4092
4216
  }
4093
4217
  function syncGizmoToSite(data, siteId, target) {
4094
4218
  if (siteId === -1) return;
@@ -4121,7 +4245,7 @@ var useIkController = createControllerHook(
4121
4245
  const { mjModelRef, mjDataRef, mujocoRef, resetCallbacks, status } = useMujocoContext();
4122
4246
  const ikEnabledRef = useRef(false);
4123
4247
  const ikCalculatingRef = useRef(false);
4124
- const ikTargetRef = useRef(new THREE11.Group());
4248
+ const ikTargetRef = useRef(new THREE12.Group());
4125
4249
  const siteIdRef = useRef(-1);
4126
4250
  const controlGroupRef = useRef(null);
4127
4251
  const genericIkRef = useRef(null);
@@ -4138,10 +4262,10 @@ var useIkController = createControllerHook(
4138
4262
  const needsInitialSync = useRef(true);
4139
4263
  const gizmoAnimRef = useRef({
4140
4264
  active: false,
4141
- startPos: new THREE11.Vector3(),
4142
- endPos: new THREE11.Vector3(),
4143
- startRot: new THREE11.Quaternion(),
4144
- endRot: new THREE11.Quaternion(),
4265
+ startPos: new THREE12.Vector3(),
4266
+ endPos: new THREE12.Vector3(),
4267
+ startRot: new THREE12.Quaternion(),
4268
+ endRot: new THREE12.Quaternion(),
4145
4269
  startTime: 0,
4146
4270
  duration: 1e3
4147
4271
  });
@@ -4302,8 +4426,8 @@ var useIkController = createControllerHook(
4302
4426
  const target = ikTargetRef.current;
4303
4427
  if (!target) return;
4304
4428
  const targetPos = toIkVector3(pos);
4305
- const targetRot = new THREE11.Quaternion().setFromEuler(
4306
- new THREE11.Euler(Math.PI, 0, 0)
4429
+ const targetRot = new THREE12.Quaternion().setFromEuler(
4430
+ new THREE12.Euler(Math.PI, 0, 0)
4307
4431
  );
4308
4432
  if (duration > 0) {
4309
4433
  const ga = gizmoAnimRef.current;
@@ -4328,7 +4452,7 @@ var useIkController = createControllerHook(
4328
4452
  if (!ikCalculatingRef.current || !target) return null;
4329
4453
  return {
4330
4454
  pos: target.position.clone(),
4331
- rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
4455
+ rot: new THREE12.Euler().setFromQuaternion(target.quaternion)
4332
4456
  };
4333
4457
  },
4334
4458
  []
@@ -4424,10 +4548,10 @@ function Body({
4424
4548
  if (!hasChildren) return null;
4425
4549
  return /* @__PURE__ */ jsx("group", { ref: groupRef, children });
4426
4550
  }
4427
- var _mat4 = new THREE11.Matrix4();
4428
- var _pos = new THREE11.Vector3();
4429
- var _quat = new THREE11.Quaternion();
4430
- var _scale = new THREE11.Vector3(1, 1, 1);
4551
+ var _mat4 = new THREE12.Matrix4();
4552
+ var _pos = new THREE12.Vector3();
4553
+ var _quat = new THREE12.Quaternion();
4554
+ var _scale = new THREE12.Vector3(1, 1, 1);
4431
4555
  function IkGizmo({ controller, siteName, scale = 0.18, onDrag }) {
4432
4556
  const { mjModelRef, mjDataRef, status } = useMujocoContext();
4433
4557
  const { ikTargetRef, siteIdRef, ikEnabledRef, setIkEnabled } = controller;
@@ -4526,7 +4650,7 @@ function IkGizmo({ controller, siteName, scale = 0.18, onDrag }) {
4526
4650
  }
4527
4651
  );
4528
4652
  }
4529
- var _dummy = new THREE11.Object3D();
4653
+ var _dummy = new THREE12.Object3D();
4530
4654
  function ContactMarkers({
4531
4655
  maxContacts = 100,
4532
4656
  radius = 8e-3,
@@ -4580,11 +4704,11 @@ function ContactMarkers({
4580
4704
  var _force = new Float64Array(3);
4581
4705
  var _torque = new Float64Array(3);
4582
4706
  var _point = new Float64Array(3);
4583
- var _bodyPos = new THREE11.Vector3();
4584
- var _bodyQuat = new THREE11.Quaternion();
4585
- var _worldHit = new THREE11.Vector3();
4586
- var _raycaster2 = new THREE11.Raycaster();
4587
- var _mouse = new THREE11.Vector2();
4707
+ var _bodyPos = new THREE12.Vector3();
4708
+ var _bodyQuat = new THREE12.Quaternion();
4709
+ var _worldHit = new THREE12.Vector3();
4710
+ var _raycaster2 = new THREE12.Raycaster();
4711
+ var _mouse = new THREE12.Vector2();
4588
4712
  function DragInteraction({
4589
4713
  stiffness = 250,
4590
4714
  showArrow = true,
@@ -4595,16 +4719,16 @@ function DragInteraction({
4595
4719
  const draggingRef = useRef(false);
4596
4720
  const bodyIdRef = useRef(-1);
4597
4721
  const grabDistanceRef = useRef(0);
4598
- const localHitRef = useRef(new THREE11.Vector3());
4599
- const grabWorldRef = useRef(new THREE11.Vector3());
4600
- const mouseWorldRef = useRef(new THREE11.Vector3());
4722
+ const localHitRef = useRef(new THREE12.Vector3());
4723
+ const grabWorldRef = useRef(new THREE12.Vector3());
4724
+ const mouseWorldRef = useRef(new THREE12.Vector3());
4601
4725
  const arrowRef = useRef(null);
4602
4726
  const groupRef = useRef(null);
4603
4727
  useEffect(() => {
4604
4728
  if (!showArrow || !groupRef.current) return;
4605
- const arrow = new THREE11.ArrowHelper(
4606
- new THREE11.Vector3(0, 1, 0),
4607
- new THREE11.Vector3(),
4729
+ const arrow = new THREE12.ArrowHelper(
4730
+ new THREE12.Vector3(0, 1, 0),
4731
+ new THREE12.Vector3(),
4608
4732
  0.1,
4609
4733
  16729156
4610
4734
  );
@@ -4787,7 +4911,7 @@ function useSceneLights(intensity = 1) {
4787
4911
  const dr = lightDiffuse ? lightDiffuse[3 * i] : 1;
4788
4912
  const dg = lightDiffuse ? lightDiffuse[3 * i + 1] : 1;
4789
4913
  const db = lightDiffuse ? lightDiffuse[3 * i + 2] : 1;
4790
- const color = new THREE11.Color(dr, dg, db);
4914
+ const color = new THREE12.Color(dr, dg, db);
4791
4915
  const px = lightPos[3 * i];
4792
4916
  const py = lightPos[3 * i + 1];
4793
4917
  const pz = lightPos[3 * i + 2];
@@ -4795,7 +4919,7 @@ function useSceneLights(intensity = 1) {
4795
4919
  const dy = lightDir[3 * i + 1];
4796
4920
  const dz = lightDir[3 * i + 2];
4797
4921
  if (isDirectional) {
4798
- const light = new THREE11.DirectionalLight(color, finalIntensity);
4922
+ const light = new THREE12.DirectionalLight(color, finalIntensity);
4799
4923
  light.position.set(px, py, pz);
4800
4924
  light.target.position.set(px + dx, py + dy, pz + dz);
4801
4925
  light.castShadow = castShadow;
@@ -4818,7 +4942,7 @@ function useSceneLights(intensity = 1) {
4818
4942
  const cutoff = lightCutoff ? lightCutoff[i] : 45;
4819
4943
  const exponent = lightExponent ? lightExponent[i] : 10;
4820
4944
  const angle = cutoff * Math.PI / 180;
4821
- const light = new THREE11.SpotLight(color, finalIntensity, 0, angle, exponent / 128);
4945
+ const light = new THREE12.SpotLight(color, finalIntensity, 0, angle, exponent / 128);
4822
4946
  light.position.set(px, py, pz);
4823
4947
  light.target.position.set(px + dx, py + dy, pz + dz);
4824
4948
  light.castShadow = castShadow;
@@ -5164,31 +5288,31 @@ var JOINT_COLORS = {
5164
5288
  3: 16776960
5165
5289
  // hinge - yellow
5166
5290
  };
5167
- var _v3a = new THREE11.Vector3();
5168
- new THREE11.Vector3();
5169
- var _quat2 = new THREE11.Quaternion();
5170
- var _cameraMatrix = new THREE11.Matrix4();
5171
- var _contactPos = new THREE11.Vector3();
5172
- var _contactNormal = new THREE11.Vector3();
5291
+ var _v3a = new THREE12.Vector3();
5292
+ new THREE12.Vector3();
5293
+ var _quat2 = new THREE12.Quaternion();
5294
+ var _cameraMatrix = new THREE12.Matrix4();
5295
+ var _contactPos = new THREE12.Vector3();
5296
+ var _contactNormal = new THREE12.Vector3();
5173
5297
  var MAX_CONTACT_ARROWS = 50;
5174
5298
  var CAMERA_DEBUG_LENGTH = 0.12;
5175
5299
  var CAMERA_DEBUG_FRUSTUM_DEPTH = 0.08;
5176
5300
  function toVector32(value, fallback) {
5177
5301
  if (!value) return fallback.clone();
5178
- return value instanceof THREE11.Vector3 ? value.clone() : new THREE11.Vector3(value[0], value[1], value[2]);
5302
+ return value instanceof THREE12.Vector3 ? value.clone() : new THREE12.Vector3(value[0], value[1], value[2]);
5179
5303
  }
5180
5304
  function createCameraLabel(text, color) {
5181
5305
  const canvas = document.createElement("canvas");
5182
5306
  canvas.width = 256;
5183
5307
  canvas.height = 64;
5184
5308
  const ctx = canvas.getContext("2d");
5185
- ctx.fillStyle = new THREE11.Color(color).getStyle();
5309
+ ctx.fillStyle = new THREE12.Color(color).getStyle();
5186
5310
  ctx.font = "bold 32px monospace";
5187
5311
  ctx.textAlign = "center";
5188
5312
  ctx.fillText(text, 128, 42);
5189
- const texture = new THREE11.CanvasTexture(canvas);
5190
- const sprite = new THREE11.Sprite(
5191
- new THREE11.SpriteMaterial({
5313
+ const texture = new THREE12.CanvasTexture(canvas);
5314
+ const sprite = new THREE12.Sprite(
5315
+ new THREE12.SpriteMaterial({
5192
5316
  map: texture,
5193
5317
  depthTest: false,
5194
5318
  transparent: true
@@ -5203,12 +5327,12 @@ function createVirtualCameraDebugObject(camera, index) {
5203
5327
  const color = camera.color ?? "#ff3d71";
5204
5328
  const aimColor = camera.aimColor ?? "#38bdf8";
5205
5329
  const markerScale = camera.markerScale ?? 1;
5206
- const cameraPosition = toVector32(camera.position, new THREE11.Vector3());
5207
- const configuredUp = toVector32(camera.up, new THREE11.Vector3(0, 0, 1)).normalize();
5208
- const cameraQuaternion = new THREE11.Quaternion();
5209
- const forward = new THREE11.Vector3();
5330
+ const cameraPosition = toVector32(camera.position, new THREE12.Vector3());
5331
+ const configuredUp = toVector32(camera.up, new THREE12.Vector3(0, 0, 1)).normalize();
5332
+ const cameraQuaternion = new THREE12.Quaternion();
5333
+ const forward = new THREE12.Vector3();
5210
5334
  if (camera.quaternion) {
5211
- if (camera.quaternion instanceof THREE11.Quaternion) {
5335
+ if (camera.quaternion instanceof THREE12.Quaternion) {
5212
5336
  cameraQuaternion.copy(camera.quaternion);
5213
5337
  } else {
5214
5338
  cameraQuaternion.set(
@@ -5222,13 +5346,13 @@ function createVirtualCameraDebugObject(camera, index) {
5222
5346
  } else {
5223
5347
  const target2 = toVector32(
5224
5348
  camera.lookAt,
5225
- cameraPosition.clone().add(new THREE11.Vector3(0, 0, -1))
5349
+ cameraPosition.clone().add(new THREE12.Vector3(0, 0, -1))
5226
5350
  );
5227
5351
  forward.copy(target2).sub(cameraPosition);
5228
5352
  if (forward.lengthSq() < 1e-8) forward.set(0, 0, -1);
5229
5353
  forward.normalize();
5230
5354
  cameraQuaternion.setFromRotationMatrix(
5231
- new THREE11.Matrix4().lookAt(cameraPosition, target2, configuredUp)
5355
+ new THREE12.Matrix4().lookAt(cameraPosition, target2, configuredUp)
5232
5356
  );
5233
5357
  }
5234
5358
  const target = camera.lookAt ? toVector32(camera.lookAt, cameraPosition.clone().add(forward)) : cameraPosition.clone().addScaledVector(forward, 0.4);
@@ -5240,7 +5364,7 @@ function createVirtualCameraDebugObject(camera, index) {
5240
5364
  if (right.lengthSq() < 1e-8) right.set(1, 0, 0);
5241
5365
  right.normalize();
5242
5366
  const orthogonalUp = right.clone().cross(forward).normalize();
5243
- const frustumHeight = 2 * Math.tan(THREE11.MathUtils.degToRad(fov) / 2) * depth;
5367
+ const frustumHeight = 2 * Math.tan(THREE12.MathUtils.degToRad(fov) / 2) * depth;
5244
5368
  const frustumWidth = frustumHeight * aspect;
5245
5369
  const center = cameraPosition.clone().addScaledVector(forward, depth);
5246
5370
  const halfRight = right.clone().multiplyScalar(frustumWidth / 2);
@@ -5267,13 +5391,13 @@ function createVirtualCameraDebugObject(camera, index) {
5267
5391
  bottomLeft,
5268
5392
  topLeft
5269
5393
  ];
5270
- const group = new THREE11.Group();
5394
+ const group = new THREE12.Group();
5271
5395
  group.name = camera.name ?? `virtual-camera-${index}`;
5272
5396
  group.renderOrder = 999;
5273
5397
  group.frustumCulled = false;
5274
- const frustum = new THREE11.LineSegments(
5275
- new THREE11.BufferGeometry().setFromPoints(frustumPoints),
5276
- new THREE11.LineBasicMaterial({
5398
+ const frustum = new THREE12.LineSegments(
5399
+ new THREE12.BufferGeometry().setFromPoints(frustumPoints),
5400
+ new THREE12.LineBasicMaterial({
5277
5401
  color,
5278
5402
  transparent: true,
5279
5403
  opacity: 0.9,
@@ -5283,9 +5407,9 @@ function createVirtualCameraDebugObject(camera, index) {
5283
5407
  frustum.renderOrder = 999;
5284
5408
  frustum.frustumCulled = false;
5285
5409
  group.add(frustum);
5286
- const aim = new THREE11.LineSegments(
5287
- new THREE11.BufferGeometry().setFromPoints([cameraPosition, target]),
5288
- new THREE11.LineBasicMaterial({
5410
+ const aim = new THREE12.LineSegments(
5411
+ new THREE12.BufferGeometry().setFromPoints([cameraPosition, target]),
5412
+ new THREE12.LineBasicMaterial({
5289
5413
  color: aimColor,
5290
5414
  transparent: true,
5291
5415
  opacity: 0.95,
@@ -5295,26 +5419,26 @@ function createVirtualCameraDebugObject(camera, index) {
5295
5419
  aim.renderOrder = 999;
5296
5420
  aim.frustumCulled = false;
5297
5421
  group.add(aim);
5298
- const markerGroup = new THREE11.Group();
5422
+ const markerGroup = new THREE12.Group();
5299
5423
  markerGroup.position.copy(cameraPosition);
5300
5424
  markerGroup.quaternion.copy(cameraQuaternion);
5301
5425
  markerGroup.renderOrder = 999;
5302
5426
  markerGroup.frustumCulled = false;
5303
- markerGroup.add(new THREE11.Mesh(
5304
- new THREE11.BoxGeometry(0.045 * markerScale, 0.028 * markerScale, 0.022 * markerScale),
5305
- new THREE11.MeshBasicMaterial({ color, depthTest: false })
5427
+ markerGroup.add(new THREE12.Mesh(
5428
+ new THREE12.BoxGeometry(0.045 * markerScale, 0.028 * markerScale, 0.022 * markerScale),
5429
+ new THREE12.MeshBasicMaterial({ color, depthTest: false })
5306
5430
  ));
5307
- const lens = new THREE11.Mesh(
5308
- new THREE11.BoxGeometry(0.025 * markerScale, 0.018 * markerScale, 0.014 * markerScale),
5309
- new THREE11.MeshBasicMaterial({ color: aimColor, depthTest: false })
5431
+ const lens = new THREE12.Mesh(
5432
+ new THREE12.BoxGeometry(0.025 * markerScale, 0.018 * markerScale, 0.014 * markerScale),
5433
+ new THREE12.MeshBasicMaterial({ color: aimColor, depthTest: false })
5310
5434
  );
5311
5435
  lens.position.set(0, 0, -0.021 * markerScale);
5312
5436
  markerGroup.add(lens);
5313
5437
  if (camera.name) markerGroup.add(createCameraLabel(camera.name, color));
5314
5438
  group.add(markerGroup);
5315
- const targetMarker = new THREE11.Mesh(
5316
- new THREE11.SphereGeometry(0.018 * markerScale, 16, 10),
5317
- new THREE11.MeshBasicMaterial({ color: aimColor, depthTest: false })
5439
+ const targetMarker = new THREE12.Mesh(
5440
+ new THREE12.SphereGeometry(0.018 * markerScale, 16, 10),
5441
+ new THREE12.MeshBasicMaterial({ color: aimColor, depthTest: false })
5318
5442
  );
5319
5443
  targetMarker.position.copy(target);
5320
5444
  targetMarker.renderOrder = 999;
@@ -5353,21 +5477,21 @@ function Debug({
5353
5477
  let geometry = null;
5354
5478
  switch (type) {
5355
5479
  case 2:
5356
- geometry = new THREE11.SphereGeometry(s[3 * i], 12, 8);
5480
+ geometry = new THREE12.SphereGeometry(s[3 * i], 12, 8);
5357
5481
  break;
5358
5482
  case 3:
5359
- geometry = new THREE11.CapsuleGeometry(s[3 * i], s[3 * i + 1] * 2, 6, 8);
5483
+ geometry = new THREE12.CapsuleGeometry(s[3 * i], s[3 * i + 1] * 2, 6, 8);
5360
5484
  break;
5361
5485
  case 5:
5362
- geometry = new THREE11.CylinderGeometry(s[3 * i], s[3 * i], s[3 * i + 1] * 2, 12);
5486
+ geometry = new THREE12.CylinderGeometry(s[3 * i], s[3 * i], s[3 * i + 1] * 2, 12);
5363
5487
  break;
5364
5488
  case 6:
5365
- geometry = new THREE11.BoxGeometry(s[3 * i] * 2, s[3 * i + 1] * 2, s[3 * i + 2] * 2);
5489
+ geometry = new THREE12.BoxGeometry(s[3 * i] * 2, s[3 * i + 1] * 2, s[3 * i + 2] * 2);
5366
5490
  break;
5367
5491
  }
5368
5492
  if (geometry) {
5369
- const mat = new THREE11.MeshBasicMaterial({ color: 65280, wireframe: true, transparent: true, opacity: 0.3 });
5370
- const mesh = new THREE11.Mesh(geometry, mat);
5493
+ const mat = new THREE12.MeshBasicMaterial({ color: 65280, wireframe: true, transparent: true, opacity: 0.3 });
5494
+ const mesh = new THREE12.Mesh(geometry, mat);
5371
5495
  mesh.userData.geomId = i;
5372
5496
  mesh.userData.bodyId = model.geom_bodyid[i];
5373
5497
  geoms.push(mesh);
@@ -5390,9 +5514,9 @@ function Debug({
5390
5514
  }
5391
5515
  if (maxGeomSize > 0) radius = maxGeomSize * 0.15;
5392
5516
  }
5393
- const geometry = new THREE11.OctahedronGeometry(radius);
5394
- const mat = new THREE11.MeshBasicMaterial({ color: 16711935, depthTest: false });
5395
- const mesh = new THREE11.Mesh(geometry, mat);
5517
+ const geometry = new THREE12.OctahedronGeometry(radius);
5518
+ const mat = new THREE12.MeshBasicMaterial({ color: 16711935, depthTest: false });
5519
+ const mesh = new THREE12.Mesh(geometry, mat);
5396
5520
  mesh.renderOrder = 999;
5397
5521
  mesh.frustumCulled = false;
5398
5522
  mesh.userData.siteId = i;
@@ -5404,9 +5528,9 @@ function Debug({
5404
5528
  ctx.font = "bold 36px monospace";
5405
5529
  ctx.textAlign = "center";
5406
5530
  ctx.fillText(getName(model, model.name_siteadr[i]), 128, 42);
5407
- const tex = new THREE11.CanvasTexture(canvas);
5408
- const spriteMat = new THREE11.SpriteMaterial({ map: tex, depthTest: false, transparent: true });
5409
- const sprite = new THREE11.Sprite(spriteMat);
5531
+ const tex = new THREE12.CanvasTexture(canvas);
5532
+ const spriteMat = new THREE12.SpriteMaterial({ map: tex, depthTest: false, transparent: true });
5533
+ const sprite = new THREE12.Sprite(spriteMat);
5410
5534
  const labelScale = radius * 15;
5411
5535
  sprite.scale.set(labelScale, labelScale * 0.25, 1);
5412
5536
  sprite.position.y = radius * 2;
@@ -5429,9 +5553,9 @@ function Debug({
5429
5553
  }
5430
5554
  }
5431
5555
  const arrowLen = Math.max(maxGeomSize * 0.8, 0.05);
5432
- const arrow = new THREE11.ArrowHelper(
5433
- new THREE11.Vector3(0, 0, 1),
5434
- new THREE11.Vector3(),
5556
+ const arrow = new THREE12.ArrowHelper(
5557
+ new THREE12.Vector3(0, 0, 1),
5558
+ new THREE12.Vector3(),
5435
5559
  arrowLen,
5436
5560
  color,
5437
5561
  arrowLen * 0.25,
@@ -5439,7 +5563,7 @@ function Debug({
5439
5563
  );
5440
5564
  arrow.renderOrder = 999;
5441
5565
  arrow.frustumCulled = false;
5442
- arrow.line.material = new THREE11.LineBasicMaterial({ color, depthTest: false });
5566
+ arrow.line.material = new THREE12.LineBasicMaterial({ color, depthTest: false });
5443
5567
  arrow.cone.material.depthTest = false;
5444
5568
  arrow.line.renderOrder = 999;
5445
5569
  arrow.line.frustumCulled = false;
@@ -5454,20 +5578,20 @@ function Debug({
5454
5578
  }
5455
5579
  if (showCameras && model.ncam && model.name_camadr) {
5456
5580
  for (let i = 0; i < model.ncam; i++) {
5457
- const group = new THREE11.Group();
5581
+ const group = new THREE12.Group();
5458
5582
  group.userData.cameraId = i;
5459
5583
  group.renderOrder = 999;
5460
5584
  group.frustumCulled = false;
5461
- const marker = new THREE11.Mesh(
5462
- new THREE11.BoxGeometry(0.014, 9e-3, 6e-3),
5463
- new THREE11.MeshBasicMaterial({ color: 3718648, depthTest: false })
5585
+ const marker = new THREE12.Mesh(
5586
+ new THREE12.BoxGeometry(0.014, 9e-3, 6e-3),
5587
+ new THREE12.MeshBasicMaterial({ color: 3718648, depthTest: false })
5464
5588
  );
5465
5589
  marker.renderOrder = 999;
5466
5590
  marker.frustumCulled = false;
5467
5591
  group.add(marker);
5468
- const forward = new THREE11.ArrowHelper(
5469
- new THREE11.Vector3(0, 0, -1),
5470
- new THREE11.Vector3(),
5592
+ const forward = new THREE12.ArrowHelper(
5593
+ new THREE12.Vector3(0, 0, -1),
5594
+ new THREE12.Vector3(),
5471
5595
  CAMERA_DEBUG_LENGTH,
5472
5596
  3718648,
5473
5597
  CAMERA_DEBUG_LENGTH * 0.24,
@@ -5475,20 +5599,20 @@ function Debug({
5475
5599
  );
5476
5600
  forward.renderOrder = 999;
5477
5601
  forward.frustumCulled = false;
5478
- forward.line.material = new THREE11.LineBasicMaterial({
5602
+ forward.line.material = new THREE12.LineBasicMaterial({
5479
5603
  color: 3718648,
5480
5604
  depthTest: false
5481
5605
  });
5482
5606
  forward.cone.material.depthTest = false;
5483
5607
  group.add(forward);
5484
- const frustumGeometry = new THREE11.BufferGeometry();
5608
+ const frustumGeometry = new THREE12.BufferGeometry();
5485
5609
  frustumGeometry.setAttribute(
5486
5610
  "position",
5487
- new THREE11.Float32BufferAttribute(new Float32Array(8 * 2 * 3), 3)
5611
+ new THREE12.Float32BufferAttribute(new Float32Array(8 * 2 * 3), 3)
5488
5612
  );
5489
- const frustum = new THREE11.LineSegments(
5613
+ const frustum = new THREE12.LineSegments(
5490
5614
  frustumGeometry,
5491
- new THREE11.LineBasicMaterial({
5615
+ new THREE12.LineBasicMaterial({
5492
5616
  color: 3718648,
5493
5617
  transparent: true,
5494
5618
  opacity: 0.8,
@@ -5507,9 +5631,9 @@ function Debug({
5507
5631
  ctx.font = "bold 32px monospace";
5508
5632
  ctx.textAlign = "center";
5509
5633
  ctx.fillText(getName(model, model.name_camadr[i]), 128, 42);
5510
- const texture = new THREE11.CanvasTexture(canvas);
5511
- const sprite = new THREE11.Sprite(
5512
- new THREE11.SpriteMaterial({
5634
+ const texture = new THREE12.CanvasTexture(canvas);
5635
+ const sprite = new THREE12.Sprite(
5636
+ new THREE12.SpriteMaterial({
5513
5637
  map: texture,
5514
5638
  depthTest: false,
5515
5639
  transparent: true
@@ -5528,9 +5652,9 @@ function Debug({
5528
5652
  }
5529
5653
  if (showCOM) {
5530
5654
  for (let i = 1; i < model.nbody; i++) {
5531
- const geometry = new THREE11.SphereGeometry(5e-3, 6, 6);
5532
- const mat = new THREE11.MeshBasicMaterial({ color: 16711680 });
5533
- const mesh = new THREE11.Mesh(geometry, mat);
5655
+ const geometry = new THREE12.SphereGeometry(5e-3, 6, 6);
5656
+ const mat = new THREE12.MeshBasicMaterial({ color: 16711680 });
5657
+ const mesh = new THREE12.Mesh(geometry, mat);
5534
5658
  mesh.userData.bodyId = i;
5535
5659
  comMarkers.push(mesh);
5536
5660
  }
@@ -5640,7 +5764,7 @@ function Debug({
5640
5764
  );
5641
5765
  group.quaternion.setFromRotationMatrix(_cameraMatrix);
5642
5766
  const fovy = model.cam_fovy?.[cameraId] ?? 45;
5643
- const halfHeight = Math.tan(THREE11.MathUtils.degToRad(fovy) / 2) * CAMERA_DEBUG_FRUSTUM_DEPTH;
5767
+ const halfHeight = Math.tan(THREE12.MathUtils.degToRad(fovy) / 2) * CAMERA_DEBUG_FRUSTUM_DEPTH;
5644
5768
  const halfWidth = halfHeight * 4 / 3;
5645
5769
  const positions = group.userData.frustum.geometry.attributes.position;
5646
5770
  const array = positions.array;
@@ -5685,9 +5809,9 @@ function Debug({
5685
5809
  contactPoolInitRef.current = true;
5686
5810
  const pool = [];
5687
5811
  for (let i = 0; i < MAX_CONTACT_ARROWS; i++) {
5688
- const arrow = new THREE11.ArrowHelper(
5689
- new THREE11.Vector3(0, 1, 0),
5690
- new THREE11.Vector3(),
5812
+ const arrow = new THREE12.ArrowHelper(
5813
+ new THREE12.Vector3(0, 1, 0),
5814
+ new THREE12.Vector3(),
5691
5815
  0.1,
5692
5816
  16729156,
5693
5817
  0.03,
@@ -5752,9 +5876,9 @@ function Debug({
5752
5876
  }
5753
5877
  );
5754
5878
  }
5755
- var DEFAULT_TENDON_COLOR = new THREE11.Color(0.3, 0.3, 0.8);
5879
+ var DEFAULT_TENDON_COLOR = new THREE12.Color(0.3, 0.3, 0.8);
5756
5880
  var DEFAULT_TENDON_WIDTH = 2e-3;
5757
- new THREE11.Vector3();
5881
+ new THREE12.Vector3();
5758
5882
  function TendonRenderer(props) {
5759
5883
  const { mjModelRef, mjDataRef, status } = useMujocoContext();
5760
5884
  const groupRef = useRef(null);
@@ -5768,7 +5892,7 @@ function TendonRenderer(props) {
5768
5892
  if (!model || !data || !group) return;
5769
5893
  const ntendon = model.ntendon ?? 0;
5770
5894
  if (ntendon === 0) return;
5771
- const material = new THREE11.MeshStandardMaterial({
5895
+ const material = new THREE12.MeshStandardMaterial({
5772
5896
  color: DEFAULT_TENDON_COLOR,
5773
5897
  roughness: 0.6,
5774
5898
  metalness: 0.1
@@ -5783,11 +5907,11 @@ function TendonRenderer(props) {
5783
5907
  curves.push(null);
5784
5908
  continue;
5785
5909
  }
5786
- const points = Array.from({ length: wrapNum }, () => new THREE11.Vector3());
5787
- const curve = new THREE11.CatmullRomCurve3(points, false);
5910
+ const points = Array.from({ length: wrapNum }, () => new THREE12.Vector3());
5911
+ const curve = new THREE12.CatmullRomCurve3(points, false);
5788
5912
  const segments = Math.max(wrapNum * 2, 4);
5789
- const geometry = new THREE11.TubeGeometry(curve, segments, DEFAULT_TENDON_WIDTH, 6, false);
5790
- const mesh = new THREE11.Mesh(geometry, material);
5913
+ const geometry = new THREE12.TubeGeometry(curve, segments, DEFAULT_TENDON_WIDTH, 6, false);
5914
+ const mesh = new THREE12.Mesh(geometry, material);
5791
5915
  mesh.frustumCulled = false;
5792
5916
  group.add(mesh);
5793
5917
  meshes.push(mesh);
@@ -5842,11 +5966,11 @@ function TendonRenderer(props) {
5842
5966
  if (curve.points.length !== validCount) {
5843
5967
  curve.points.length = validCount;
5844
5968
  while (curve.points.length < validCount) {
5845
- curve.points.push(new THREE11.Vector3());
5969
+ curve.points.push(new THREE12.Vector3());
5846
5970
  }
5847
5971
  }
5848
5972
  mesh.geometry.dispose();
5849
- mesh.geometry = new THREE11.TubeGeometry(
5973
+ mesh.geometry = new THREE12.TubeGeometry(
5850
5974
  curve,
5851
5975
  Math.max(validCount * 2, 4),
5852
5976
  DEFAULT_TENDON_WIDTH,
@@ -5859,6 +5983,26 @@ function TendonRenderer(props) {
5859
5983
  if (status !== "ready") return null;
5860
5984
  return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
5861
5985
  }
5986
+
5987
+ // src/rendering/flexTopology.ts
5988
+ function getFlexTopology(model, id) {
5989
+ const dim = model.flex_dim[id];
5990
+ if (dim === 3) {
5991
+ const start = model.flex_shelldataadr[id];
5992
+ return {
5993
+ kind: "mesh",
5994
+ indices: Array.from(model.flex_shell.subarray(start, start + 3 * model.flex_shellnum[id]))
5995
+ };
5996
+ }
5997
+ if (dim === 1 || dim === 2) {
5998
+ const start = model.flex_elemdataadr[id];
5999
+ return {
6000
+ kind: dim === 2 ? "mesh" : "lines",
6001
+ indices: Array.from(model.flex_elem.subarray(start, start + (dim + 1) * model.flex_elemnum[id]))
6002
+ };
6003
+ }
6004
+ return { kind: "points", indices: [] };
6005
+ }
5862
6006
  function FlexRenderer(props) {
5863
6007
  const { mjModelRef, mjDataRef, status } = useMujocoContext();
5864
6008
  const groupRef = useRef(null);
@@ -5873,24 +6017,23 @@ function FlexRenderer(props) {
5873
6017
  const vertAdr = model.flex_vertadr[f];
5874
6018
  const vertNum = model.flex_vertnum[f];
5875
6019
  if (vertNum === 0) continue;
5876
- const geometry = new THREE11.BufferGeometry();
6020
+ const geometry = new THREE12.BufferGeometry();
5877
6021
  const positions = new Float32Array(vertNum * 3);
5878
- geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
5879
- geometry.computeVertexNormals();
5880
- let color = new THREE11.Color(0.5, 0.5, 0.5);
6022
+ geometry.setAttribute("position", new THREE12.BufferAttribute(positions, 3));
6023
+ const topology = getFlexTopology(model, f);
6024
+ if (topology.indices.length) geometry.setIndex(topology.indices);
6025
+ let color = new THREE12.Color(0.5, 0.5, 0.5);
5881
6026
  if (model.flex_rgba) {
5882
- color = new THREE11.Color(
6027
+ color = new THREE12.Color(
5883
6028
  model.flex_rgba[4 * f],
5884
6029
  model.flex_rgba[4 * f + 1],
5885
6030
  model.flex_rgba[4 * f + 2]
5886
6031
  );
5887
6032
  }
5888
- const material = new THREE11.MeshStandardMaterial({
5889
- color,
5890
- roughness: 0.7,
5891
- side: THREE11.DoubleSide
5892
- });
5893
- const mesh = new THREE11.Mesh(geometry, material);
6033
+ const opacity = model.flex_rgba[4 * f + 3];
6034
+ const materialOptions = { color, opacity, transparent: opacity < 1 };
6035
+ const mesh = topology.kind === "mesh" ? new THREE12.Mesh(geometry, new THREE12.MeshStandardMaterial({ ...materialOptions, roughness: 0.7, side: THREE12.DoubleSide })) : topology.kind === "lines" ? new THREE12.LineSegments(geometry, new THREE12.LineBasicMaterial(materialOptions)) : new THREE12.Points(geometry, new THREE12.PointsMaterial({ ...materialOptions, size: 0.01 }));
6036
+ mesh.frustumCulled = false;
5894
6037
  mesh.userData.flexId = f;
5895
6038
  mesh.userData.vertAdr = vertAdr;
5896
6039
  mesh.userData.vertNum = vertNum;
@@ -5919,14 +6062,14 @@ function FlexRenderer(props) {
5919
6062
  posAttr.setXYZ(v, data.flexvert_xpos[srcIdx], data.flexvert_xpos[srcIdx + 1], data.flexvert_xpos[srcIdx + 2]);
5920
6063
  }
5921
6064
  posAttr.needsUpdate = true;
5922
- mesh.geometry.computeVertexNormals();
6065
+ if (mesh instanceof THREE12.Mesh) mesh.geometry.computeVertexNormals();
5923
6066
  }
5924
6067
  });
5925
6068
  if (status !== "ready") return null;
5926
6069
  return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
5927
6070
  }
5928
6071
  var GEOM_TYPE_NAMES2 = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
5929
- var _matrix = new THREE11.Matrix4();
6072
+ var _matrix = new THREE12.Matrix4();
5930
6073
  function getGeomInfo(model, geomId) {
5931
6074
  const size = model.geom_size.subarray(geomId * 3, geomId * 3 + 3);
5932
6075
  const type = model.geom_type[geomId];
@@ -5948,10 +6091,10 @@ function geomSignature(model, geomId) {
5948
6091
  return [type, size, mat, data, rgba].join("|");
5949
6092
  }
5950
6093
  function firstMesh(object) {
5951
- if (object instanceof THREE11.Mesh) return object;
6094
+ if (object instanceof THREE12.Mesh) return object;
5952
6095
  let mesh = null;
5953
6096
  object.traverse((child) => {
5954
- if (!mesh && child instanceof THREE11.Mesh) mesh = child;
6097
+ if (!mesh && child instanceof THREE12.Mesh) mesh = child;
5955
6098
  });
5956
6099
  return mesh;
5957
6100
  }
@@ -6368,24 +6511,16 @@ function useActuators() {
6368
6511
  if (status !== "ready") return [];
6369
6512
  const model = mjModelRef.current;
6370
6513
  if (!model) return [];
6371
- const actuators = [];
6372
- for (let i = 0; i < model.nu; i++) {
6373
- const name = getName(model, model.name_actuatoradr[i]);
6374
- const lo = model.actuator_ctrlrange[i * 2];
6375
- const hi = model.actuator_ctrlrange[i * 2 + 1];
6376
- const hasRange = lo < hi;
6377
- const range = hasRange ? [lo, hi] : [-Infinity, Infinity];
6378
- actuators.push({ id: i, name, range });
6379
- }
6514
+ const actuators = Array.from({ length: model.nactuator }, (_, id) => getActuatorInfo(model, id));
6380
6515
  return actuators;
6381
6516
  }, [status, mjModelRef]);
6382
6517
  }
6383
- var _mat42 = new THREE11.Matrix4();
6518
+ var _mat42 = new THREE12.Matrix4();
6384
6519
  function useSitePosition(siteName) {
6385
6520
  const { mjModelRef, mjDataRef, status } = useMujocoContext();
6386
6521
  const siteIdRef = useRef(-1);
6387
- const positionRef = useRef(new THREE11.Vector3());
6388
- const quaternionRef = useRef(new THREE11.Quaternion());
6522
+ const positionRef = useRef(new THREE12.Vector3());
6523
+ const quaternionRef = useRef(new THREE12.Quaternion());
6389
6524
  useEffect(() => {
6390
6525
  const model = mjModelRef.current;
6391
6526
  if (!model || status !== "ready") {
@@ -6578,10 +6713,10 @@ function useJointState(name, options = {}) {
6578
6713
  function useBodyState(name) {
6579
6714
  const { mjModelRef, status } = useMujocoContext();
6580
6715
  const bodyIdRef = useRef(-1);
6581
- const position = useRef(new THREE11.Vector3());
6582
- const quaternion = useRef(new THREE11.Quaternion());
6583
- const linearVelocity = useRef(new THREE11.Vector3());
6584
- const angularVelocity = useRef(new THREE11.Vector3());
6716
+ const position = useRef(new THREE12.Vector3());
6717
+ const quaternion = useRef(new THREE12.Quaternion());
6718
+ const linearVelocity = useRef(new THREE12.Vector3());
6719
+ const angularVelocity = useRef(new THREE12.Vector3());
6585
6720
  useEffect(() => {
6586
6721
  const model = mjModelRef.current;
6587
6722
  if (!model || status !== "ready") return;
@@ -6607,7 +6742,7 @@ function useBodyState(name) {
6607
6742
  });
6608
6743
  return { position, quaternion, linearVelocity, angularVelocity };
6609
6744
  }
6610
- var _matrix2 = new THREE11.Matrix4();
6745
+ var _matrix2 = new THREE12.Matrix4();
6611
6746
  function quaternionFromMatrixArray(target, values, offset) {
6612
6747
  _matrix2.set(
6613
6748
  values[offset],
@@ -6641,8 +6776,8 @@ function useNamedPose(kind, name) {
6641
6776
  const { mjModelRef, status } = useMujocoContext();
6642
6777
  const idRef = useRef(-1);
6643
6778
  const foundRef = useRef(false);
6644
- const positionRef = useRef(new THREE11.Vector3());
6645
- const quaternionRef = useRef(new THREE11.Quaternion());
6779
+ const positionRef = useRef(new THREE12.Vector3());
6780
+ const quaternionRef = useRef(new THREE12.Quaternion());
6646
6781
  useEffect(() => {
6647
6782
  const model = mjModelRef.current;
6648
6783
  if (!model || status !== "ready") {
@@ -6694,7 +6829,7 @@ function useGeomPose(name) {
6694
6829
  function useSitePose(name) {
6695
6830
  return useNamedPose("site", name);
6696
6831
  }
6697
- function useCtrl(name) {
6832
+ function useCtrl(name, input) {
6698
6833
  const { mjModelRef, mjDataRef, status } = useMujocoContext();
6699
6834
  const actuatorIdRef = useRef(-1);
6700
6835
  const rangeRef = useRef([0, 0]);
@@ -6702,14 +6837,15 @@ function useCtrl(name) {
6702
6837
  const model = mjModelRef.current;
6703
6838
  if (!model || status !== "ready") return;
6704
6839
  const id = findActuatorByName(model, name);
6705
- actuatorIdRef.current = id;
6840
+ const adr = getActuatorControlAddress(model, id, input);
6841
+ actuatorIdRef.current = adr;
6706
6842
  if (id >= 0) {
6707
6843
  rangeRef.current = [
6708
- model.actuator_ctrlrange[id * 2],
6709
- model.actuator_ctrlrange[id * 2 + 1]
6844
+ model.actuator_ctrlrange[adr * 2],
6845
+ model.actuator_ctrlrange[adr * 2 + 1]
6710
6846
  ];
6711
6847
  }
6712
- }, [name, status, mjModelRef]);
6848
+ }, [name, input, status, mjModelRef]);
6713
6849
  return useMemo(() => ({
6714
6850
  read() {
6715
6851
  const data = mjDataRef.current;
@@ -6879,7 +7015,7 @@ function useControlGroup(group, options = {}) {
6879
7015
  if (id < 0) {
6880
7016
  console.warn(`[mujoco-react] useControlGroup: actuator "${name}" was not found.`);
6881
7017
  }
6882
- return id;
7018
+ return getActuatorControlAddress(model, id);
6883
7019
  });
6884
7020
  }, [mjModelRef, status, namesKey]);
6885
7021
  const patch = useCallback(
@@ -7085,7 +7221,7 @@ function useKeyboardTeleop(config) {
7085
7221
  const cache = /* @__PURE__ */ new Map();
7086
7222
  for (const binding of Object.values(config.bindings)) {
7087
7223
  if (!cache.has(binding.actuator)) {
7088
- cache.set(binding.actuator, findActuatorByName(model, binding.actuator));
7224
+ cache.set(binding.actuator, getActuatorControlAddress(model, findActuatorByName(model, binding.actuator)));
7089
7225
  }
7090
7226
  }
7091
7227
  actuatorCacheRef.current = cache;
@@ -7135,9 +7271,9 @@ function useKeyboardTeleop(config) {
7135
7271
  }
7136
7272
  var DEFAULT_TRANSLATE_SPEED = 0.25;
7137
7273
  var DEFAULT_ROTATE_SPEED = 1;
7138
- var _translation = new THREE11.Vector3();
7139
- var _axis = new THREE11.Vector3();
7140
- var _quat3 = new THREE11.Quaternion();
7274
+ var _translation = new THREE12.Vector3();
7275
+ var _axis = new THREE12.Vector3();
7276
+ var _quat3 = new THREE12.Quaternion();
7141
7277
  function actionSign(action) {
7142
7278
  return action.endsWith("+") ? 1 : -1;
7143
7279
  }
@@ -7914,10 +8050,10 @@ function useGamepad(config) {
7914
8050
  axisCacheRef.current.clear();
7915
8051
  buttonCacheRef.current.clear();
7916
8052
  for (const [idx, name] of Object.entries(config.axes ?? {})) {
7917
- axisCacheRef.current.set(Number(idx), findActuatorByName(model, name));
8053
+ axisCacheRef.current.set(Number(idx), getActuatorControlAddress(model, findActuatorByName(model, name)));
7918
8054
  }
7919
8055
  for (const [idx, name] of Object.entries(config.buttons ?? {})) {
7920
- buttonCacheRef.current.set(Number(idx), findActuatorByName(model, name));
8056
+ buttonCacheRef.current.set(Number(idx), getActuatorControlAddress(model, findActuatorByName(model, name)));
7921
8057
  }
7922
8058
  }, [config.axes, config.buttons, status, mjModelRef]);
7923
8059
  useBeforePhysicsStep(({ data }) => {
@@ -8605,7 +8741,7 @@ function useSelectionHighlight(bodyId, options = {}) {
8605
8741
  }
8606
8742
  }
8607
8743
  prevRef.current = [];
8608
- const highlightColor = new THREE11.Color(color);
8744
+ const highlightColor = new THREE12.Color(color);
8609
8745
  for (const mesh of meshes) {
8610
8746
  const mat = mesh.material;
8611
8747
  if (mat.emissive) {
@@ -8632,15 +8768,15 @@ function useSelectionHighlight(bodyId, options = {}) {
8632
8768
  }
8633
8769
  function useCameraAnimation() {
8634
8770
  const { camera } = useThree();
8635
- const orbitTargetRef = useRef(new THREE11.Vector3(0, 0, 0));
8771
+ const orbitTargetRef = useRef(new THREE12.Vector3(0, 0, 0));
8636
8772
  const cameraAnimRef = useRef({
8637
8773
  active: false,
8638
- startPos: new THREE11.Vector3(),
8639
- endPos: new THREE11.Vector3(),
8640
- startRot: new THREE11.Quaternion(),
8641
- endRot: new THREE11.Quaternion(),
8642
- startTarget: new THREE11.Vector3(),
8643
- endTarget: new THREE11.Vector3(),
8774
+ startPos: new THREE12.Vector3(),
8775
+ endPos: new THREE12.Vector3(),
8776
+ startRot: new THREE12.Quaternion(),
8777
+ endRot: new THREE12.Quaternion(),
8778
+ startTarget: new THREE12.Vector3(),
8779
+ endTarget: new THREE12.Vector3(),
8644
8780
  startTime: 0,
8645
8781
  duration: 0,
8646
8782
  resolve: null