mujoco-react 6.0.1 → 7.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,5 +1,5 @@
1
1
  import loadMujoco from 'mujoco-js';
2
- import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo } from 'react';
2
+ import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
  import { Canvas, useThree, useFrame } from '@react-three/fiber';
5
5
  import * as THREE11 from 'three';
@@ -508,7 +508,7 @@ function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
508
508
  });
509
509
  }
510
510
  function SceneRenderer(props) {
511
- const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, status } = useMujoco();
511
+ const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, hiddenBodiesRef, status } = useMujocoContext();
512
512
  const groupRef = useRef(null);
513
513
  const bodyRefs = useRef([]);
514
514
  const prevModelRef = useRef(null);
@@ -530,10 +530,13 @@ function SceneRenderer(props) {
530
530
  for (let i = 0; i < model.nbody; i++) {
531
531
  const bodyGroup = new THREE11.Group();
532
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);
533
+ const bodyName = getName(model, model.name_bodyadr[i]);
534
+ if (!hiddenBodiesRef.current.has(bodyName)) {
535
+ for (let g = 0; g < model.ngeom; g++) {
536
+ if (model.geom_bodyid[g] === i) {
537
+ const mesh = geomBuilder.create(model, g);
538
+ if (mesh) bodyGroup.add(mesh);
539
+ }
537
540
  }
538
541
  }
539
542
  group.add(bodyGroup);
@@ -647,14 +650,51 @@ var _rayGeomId = new Int32Array(1);
647
650
  var _projRaycaster = new THREE11.Raycaster();
648
651
  var _projNdc = new THREE11.Vector2();
649
652
  var MujocoSimContext = createContext(null);
650
- function useMujoco() {
653
+ function useMujocoContext() {
651
654
  const ctx = useContext(MujocoSimContext);
652
655
  if (!ctx)
653
656
  throw new Error("useMujoco must be used inside <MujocoSimProvider>");
654
657
  return ctx;
655
658
  }
659
+ function useMujoco() {
660
+ const ctx = useMujocoContext();
661
+ if (ctx.status === "ready") {
662
+ return {
663
+ status: "ready",
664
+ isPending: false,
665
+ isReady: true,
666
+ isError: false,
667
+ error: null,
668
+ api: ctx.api,
669
+ mjModelRef: ctx.mjModelRef,
670
+ mjDataRef: ctx.mjDataRef
671
+ };
672
+ }
673
+ if (ctx.status === "error") {
674
+ return {
675
+ status: "error",
676
+ isPending: false,
677
+ isReady: false,
678
+ isError: true,
679
+ error: ctx.errorRef.current ?? "Unknown error",
680
+ api: null,
681
+ mjModelRef: null,
682
+ mjDataRef: null
683
+ };
684
+ }
685
+ return {
686
+ status: "loading",
687
+ isPending: true,
688
+ isReady: false,
689
+ isError: false,
690
+ error: null,
691
+ api: null,
692
+ mjModelRef: null,
693
+ mjDataRef: null
694
+ };
695
+ }
656
696
  function useBeforePhysicsStep(callback) {
657
- const { beforeStepCallbacks } = useMujoco();
697
+ const { beforeStepCallbacks } = useMujocoContext();
658
698
  const callbackRef = useRef(callback);
659
699
  callbackRef.current = callback;
660
700
  useEffect(() => {
@@ -666,7 +706,7 @@ function useBeforePhysicsStep(callback) {
666
706
  }, [beforeStepCallbacks]);
667
707
  }
668
708
  function useAfterPhysicsStep(callback) {
669
- const { afterStepCallbacks } = useMujoco();
709
+ const { afterStepCallbacks } = useMujocoContext();
670
710
  const callbackRef = useRef(callback);
671
711
  callbackRef.current = callback;
672
712
  useEffect(() => {
@@ -710,6 +750,10 @@ function MujocoSimProvider({
710
750
  const beforeStepCallbacks = useRef(/* @__PURE__ */ new Set());
711
751
  const afterStepCallbacks = useRef(/* @__PURE__ */ new Set());
712
752
  const resetCallbacks = useRef(/* @__PURE__ */ new Set());
753
+ const errorRef = useRef(null);
754
+ const bodyRegistryRef = useRef(/* @__PURE__ */ new Map());
755
+ const hiddenBodiesRef = useRef(/* @__PURE__ */ new Set());
756
+ const bodyReloadTimerRef = useRef(null);
713
757
  configRef.current = config;
714
758
  useEffect(() => {
715
759
  pausedRef.current = paused ?? false;
@@ -734,11 +778,22 @@ function MujocoSimProvider({
734
778
  if (!model?.opt) return;
735
779
  model.opt.timestep = timestep;
736
780
  }, [timestep]);
781
+ function buildMergedConfig(baseConfig) {
782
+ if (bodyRegistryRef.current.size === 0) return baseConfig;
783
+ const registeredNames = new Set(bodyRegistryRef.current.keys());
784
+ const baseObjects = (baseConfig.sceneObjects ?? []).filter((o) => !registeredNames.has(o.name));
785
+ const registeredBodies = Array.from(bodyRegistryRef.current.values()).map((e) => e.definition);
786
+ hiddenBodiesRef.current.clear();
787
+ for (const [name, entry] of bodyRegistryRef.current) {
788
+ if (entry.hasCustomChildren) hiddenBodiesRef.current.add(name);
789
+ }
790
+ return { ...baseConfig, sceneObjects: [...baseObjects, ...registeredBodies] };
791
+ }
737
792
  useEffect(() => {
738
793
  let disposed = false;
739
794
  (async () => {
740
795
  try {
741
- const result = await loadScene(mujoco, config);
796
+ const result = await loadScene(mujoco, buildMergedConfig(config));
742
797
  if (disposed) {
743
798
  result.mjModel.delete();
744
799
  result.mjData.delete();
@@ -757,8 +812,10 @@ function MujocoSimProvider({
757
812
  setStatus("ready");
758
813
  } catch (e) {
759
814
  if (!disposed) {
815
+ const err = e instanceof Error ? e : new Error(String(e));
816
+ errorRef.current = err.message;
760
817
  setStatus("error");
761
- onError?.(e instanceof Error ? e : new Error(String(e)));
818
+ onError?.(err);
762
819
  }
763
820
  }
764
821
  })();
@@ -1225,10 +1282,18 @@ function MujocoSimProvider({
1225
1282
  setStatus("ready");
1226
1283
  } catch (e) {
1227
1284
  if (gen !== loadGenRef.current) return;
1285
+ errorRef.current = e instanceof Error ? e.message : String(e);
1228
1286
  setStatus("error");
1229
1287
  throw e;
1230
1288
  }
1231
1289
  }, [mujoco]);
1290
+ const requestBodyReload = useCallback(() => {
1291
+ if (bodyReloadTimerRef.current) clearTimeout(bodyReloadTimerRef.current);
1292
+ bodyReloadTimerRef.current = setTimeout(() => {
1293
+ bodyReloadTimerRef.current = null;
1294
+ loadSceneApi(buildMergedConfig(configRef.current));
1295
+ }, 0);
1296
+ }, [loadSceneApi]);
1232
1297
  const getCanvasSnapshot = useCallback(
1233
1298
  (width, height, mimeType = "image/jpeg") => {
1234
1299
  if (width && height) {
@@ -1411,9 +1476,13 @@ function MujocoSimProvider({
1411
1476
  beforeStepCallbacks,
1412
1477
  afterStepCallbacks,
1413
1478
  resetCallbacks,
1479
+ errorRef,
1480
+ bodyRegistryRef,
1481
+ hiddenBodiesRef,
1482
+ requestBodyReload,
1414
1483
  status
1415
1484
  }),
1416
- [api, status]
1485
+ [api, status, requestBodyReload]
1417
1486
  );
1418
1487
  return /* @__PURE__ */ jsxs(MujocoSimContext.Provider, { value: contextValue, children: [
1419
1488
  /* @__PURE__ */ jsx(SceneRenderer, {}),
@@ -1519,13 +1588,24 @@ function createController(options, Impl) {
1519
1588
  Controller.defaultConfig = options.defaultConfig ?? {};
1520
1589
  return Controller;
1521
1590
  }
1522
- var IkContext = createContext(null);
1523
- function useIk(options) {
1524
- const ctx = useContext(IkContext);
1525
- if (!ctx && !options?.optional) {
1526
- throw new Error("useIk() must be used inside an <IkController>");
1527
- }
1528
- return ctx;
1591
+ function createControllerHook(options, useImpl) {
1592
+ const useController = (config) => {
1593
+ const configObj = config;
1594
+ const stableRef = useRef(configObj);
1595
+ if (configObj && stableRef.current) {
1596
+ if (!shallowEqual(stableRef.current, configObj)) {
1597
+ stableRef.current = configObj;
1598
+ }
1599
+ } else {
1600
+ stableRef.current = configObj;
1601
+ }
1602
+ const mergedConfig = useMemo(
1603
+ () => stableRef.current ? { ...options.defaultConfig, ...stableRef.current } : null,
1604
+ [stableRef.current]
1605
+ );
1606
+ return useImpl(mergedConfig);
1607
+ };
1608
+ return useController;
1529
1609
  }
1530
1610
 
1531
1611
  // src/core/GenericIK.ts
@@ -1753,6 +1833,8 @@ function solve6x6(A, b, x) {
1753
1833
  x[row] = sum / a[row * N + row];
1754
1834
  }
1755
1835
  }
1836
+
1837
+ // src/hooks/useIkController.ts
1756
1838
  var _syncMat4 = new THREE11.Matrix4();
1757
1839
  function syncGizmoToSite(data, siteId, target) {
1758
1840
  if (siteId === -1) return;
@@ -1779,206 +1861,272 @@ function syncGizmoToSite(data, siteId, target) {
1779
1861
  );
1780
1862
  target.quaternion.setFromRotationMatrix(_syncMat4);
1781
1863
  }
1782
- function IkControllerImpl({
1783
- config,
1784
- children
1785
- }) {
1786
- const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } = useMujoco();
1787
- const ikEnabledRef = useRef(false);
1788
- const ikCalculatingRef = useRef(false);
1789
- const ikTargetRef = useRef(new THREE11.Group());
1790
- const siteIdRef = useRef(-1);
1791
- const genericIkRef = useRef(new GenericIK(mujocoRef.current));
1792
- const firstIkEnableRef = useRef(true);
1793
- const needsInitialSync = useRef(true);
1794
- const gizmoAnimRef = useRef({
1795
- active: false,
1796
- startPos: new THREE11.Vector3(),
1797
- endPos: new THREE11.Vector3(),
1798
- startRot: new THREE11.Quaternion(),
1799
- endRot: new THREE11.Quaternion(),
1800
- startTime: 0,
1801
- duration: 1e3
1802
- });
1803
- useEffect(() => {
1804
- const model = mjModelRef.current;
1805
- if (!model || status !== "ready") {
1806
- siteIdRef.current = -1;
1807
- return;
1808
- }
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);
1864
+ var useIkController = createControllerHook(
1865
+ { defaultConfig: { damping: 0.01, maxIterations: 50 } },
1866
+ function useIkControllerImpl(config) {
1867
+ const { mjModelRef, mjDataRef, mujocoRef, resetCallbacks, status } = useMujocoContext();
1868
+ const ikEnabledRef = useRef(false);
1869
+ const ikCalculatingRef = useRef(false);
1870
+ const ikTargetRef = useRef(new THREE11.Group());
1871
+ const siteIdRef = useRef(-1);
1872
+ const genericIkRef = useRef(new GenericIK(mujocoRef.current));
1873
+ const firstIkEnableRef = useRef(true);
1874
+ const needsInitialSync = useRef(true);
1875
+ const gizmoAnimRef = useRef({
1876
+ active: false,
1877
+ startPos: new THREE11.Vector3(),
1878
+ endPos: new THREE11.Vector3(),
1879
+ startRot: new THREE11.Quaternion(),
1880
+ endRot: new THREE11.Quaternion(),
1881
+ startTime: 0,
1882
+ duration: 1e3
1883
+ });
1884
+ useEffect(() => {
1885
+ if (!config) {
1886
+ siteIdRef.current = -1;
1887
+ return;
1888
+ }
1818
1889
  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
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;
1890
+ if (!model || status !== "ready") {
1891
+ siteIdRef.current = -1;
1892
+ return;
1845
1893
  }
1846
- }
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];
1872
- }
1873
- });
1874
- useEffect(() => {
1875
- const cb = () => {
1894
+ siteIdRef.current = findSiteByName(model, config.siteName);
1876
1895
  const data = mjDataRef.current;
1877
1896
  if (data && ikTargetRef.current) {
1878
1897
  syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1879
1898
  }
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;
1899
+ }, [config?.siteName, status, mjModelRef, mjDataRef, config]);
1900
+ const ikSolveFn = useCallback(
1901
+ (pos, quat, currentQ) => {
1902
+ if (!config) return null;
1903
+ if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
1904
+ const model = mjModelRef.current;
1905
+ const data = mjDataRef.current;
1906
+ if (!model || !data || siteIdRef.current === -1) return null;
1907
+ return genericIkRef.current.solve(
1908
+ model,
1909
+ data,
1910
+ siteIdRef.current,
1911
+ config.numJoints,
1912
+ pos,
1913
+ quat,
1914
+ currentQ,
1915
+ { damping: config.damping, maxIterations: config.maxIterations }
1916
+ );
1917
+ },
1918
+ [config, mjModelRef, mjDataRef]
1919
+ );
1920
+ const ikSolveFnRef = useRef(ikSolveFn);
1921
+ ikSolveFnRef.current = ikSolveFn;
1922
+ useFrame(() => {
1923
+ if (!config) return;
1924
+ if (needsInitialSync.current && siteIdRef.current !== -1) {
1925
+ const data = mjDataRef.current;
1926
+ if (data && ikTargetRef.current) {
1927
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1928
+ needsInitialSync.current = false;
1929
+ }
1897
1930
  }
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);
1931
+ const ga = gizmoAnimRef.current;
1915
1932
  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);
1933
+ if (!ga.active || !target) return;
1934
+ const now = performance.now();
1935
+ const elapsed = now - ga.startTime;
1936
+ const t = Math.min(elapsed / ga.duration, 1);
1937
+ const ease = 1 - Math.pow(1 - t, 3);
1938
+ target.position.lerpVectors(ga.startPos, ga.endPos, ease);
1939
+ target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
1940
+ if (t >= 1) ga.active = false;
1941
+ });
1942
+ useBeforePhysicsStep((model, data) => {
1943
+ if (!config || !ikEnabledRef.current) {
1944
+ ikCalculatingRef.current = false;
1945
+ return;
1934
1946
  }
1935
- },
1936
- [setIkEnabled]
1937
- );
1938
- const getGizmoStats = useCallback(
1939
- () => {
1940
1947
  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)
1948
+ if (!target) return;
1949
+ ikCalculatingRef.current = true;
1950
+ const numJoints = config.numJoints;
1951
+ const currentQ = [];
1952
+ for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
1953
+ const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
1954
+ if (solution) {
1955
+ for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
1956
+ }
1957
+ });
1958
+ useEffect(() => {
1959
+ if (!config) return;
1960
+ const cb = () => {
1961
+ const data = mjDataRef.current;
1962
+ if (data && ikTargetRef.current) {
1963
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1964
+ }
1965
+ gizmoAnimRef.current.active = false;
1966
+ firstIkEnableRef.current = true;
1967
+ ikEnabledRef.current = false;
1968
+ needsInitialSync.current = true;
1945
1969
  };
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]
1962
- );
1963
- return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
1964
- }
1965
- var IkController = createController(
1966
- {
1967
- name: "IkController",
1968
- defaultConfig: {
1969
- damping: 0.01,
1970
- maxIterations: 50
1971
- }
1972
- },
1973
- IkControllerImpl
1970
+ resetCallbacks.current.add(cb);
1971
+ return () => {
1972
+ resetCallbacks.current.delete(cb);
1973
+ };
1974
+ }, [resetCallbacks, mjDataRef, config]);
1975
+ const setIkEnabled = useCallback(
1976
+ (enabled) => {
1977
+ ikEnabledRef.current = enabled;
1978
+ const data = mjDataRef.current;
1979
+ if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
1980
+ syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1981
+ firstIkEnableRef.current = false;
1982
+ }
1983
+ },
1984
+ [mjDataRef]
1985
+ );
1986
+ const syncTargetToSiteApi = useCallback(() => {
1987
+ const data = mjDataRef.current;
1988
+ const target = ikTargetRef.current;
1989
+ if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
1990
+ }, [mjDataRef]);
1991
+ const solveIK = useCallback(
1992
+ (pos, quat, currentQ) => {
1993
+ return ikSolveFnRef.current(pos, quat, currentQ);
1994
+ },
1995
+ []
1996
+ );
1997
+ const moveTarget = useCallback(
1998
+ (pos, duration = 0) => {
1999
+ if (!ikEnabledRef.current) setIkEnabled(true);
2000
+ const target = ikTargetRef.current;
2001
+ if (!target) return;
2002
+ const targetPos = pos.clone();
2003
+ const targetRot = new THREE11.Quaternion().setFromEuler(
2004
+ new THREE11.Euler(Math.PI, 0, 0)
2005
+ );
2006
+ if (duration > 0) {
2007
+ const ga = gizmoAnimRef.current;
2008
+ ga.active = true;
2009
+ ga.startPos.copy(target.position);
2010
+ ga.endPos.copy(targetPos);
2011
+ ga.startRot.copy(target.quaternion);
2012
+ ga.endRot.copy(targetRot);
2013
+ ga.startTime = performance.now();
2014
+ ga.duration = duration;
2015
+ } else {
2016
+ gizmoAnimRef.current.active = false;
2017
+ target.position.copy(targetPos);
2018
+ target.quaternion.copy(targetRot);
2019
+ }
2020
+ },
2021
+ [setIkEnabled]
2022
+ );
2023
+ const getGizmoStats = useCallback(
2024
+ () => {
2025
+ const target = ikTargetRef.current;
2026
+ if (!ikCalculatingRef.current || !target) return null;
2027
+ return {
2028
+ pos: target.position.clone(),
2029
+ rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
2030
+ };
2031
+ },
2032
+ []
2033
+ );
2034
+ const contextValue = useMemo(
2035
+ () => ({
2036
+ ikEnabledRef,
2037
+ ikCalculatingRef,
2038
+ ikTargetRef,
2039
+ siteIdRef,
2040
+ setIkEnabled,
2041
+ moveTarget,
2042
+ syncTargetToSite: syncTargetToSiteApi,
2043
+ solveIK,
2044
+ getGizmoStats
2045
+ }),
2046
+ [setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats]
2047
+ );
2048
+ if (!config) return null;
2049
+ return contextValue;
2050
+ }
1974
2051
  );
2052
+ function Body({
2053
+ name,
2054
+ type,
2055
+ size,
2056
+ position = [0, 0, 0],
2057
+ rgba = [0.5, 0.5, 0.5, 1],
2058
+ mass,
2059
+ freejoint,
2060
+ friction,
2061
+ solref,
2062
+ solimp,
2063
+ condim,
2064
+ children
2065
+ }) {
2066
+ const { bodyRegistryRef, hiddenBodiesRef, requestBodyReload, mjDataRef, mjModelRef, status } = useMujocoContext();
2067
+ const bodyIdRef = useRef(-1);
2068
+ const groupRef = useRef(null);
2069
+ const initialLoadRef = useRef(true);
2070
+ const hasChildren = children != null;
2071
+ useLayoutEffect(() => {
2072
+ const definition = {
2073
+ name,
2074
+ type,
2075
+ size,
2076
+ position,
2077
+ rgba,
2078
+ mass,
2079
+ freejoint,
2080
+ friction,
2081
+ solref,
2082
+ solimp,
2083
+ condim
2084
+ };
2085
+ bodyRegistryRef.current.set(name, { definition, hasCustomChildren: hasChildren });
2086
+ if (hasChildren) {
2087
+ hiddenBodiesRef.current.add(name);
2088
+ }
2089
+ return () => {
2090
+ bodyRegistryRef.current.delete(name);
2091
+ hiddenBodiesRef.current.delete(name);
2092
+ if (!initialLoadRef.current) {
2093
+ requestBodyReload();
2094
+ }
2095
+ };
2096
+ }, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
2097
+ useEffect(() => {
2098
+ if (status !== "ready") return;
2099
+ const model = mjModelRef.current;
2100
+ if (!model) return;
2101
+ bodyIdRef.current = findBodyByName(model, name);
2102
+ initialLoadRef.current = false;
2103
+ }, [status, name, mjModelRef]);
2104
+ useFrame(() => {
2105
+ if (!hasChildren) return;
2106
+ const data = mjDataRef.current;
2107
+ const id = bodyIdRef.current;
2108
+ const group = groupRef.current;
2109
+ if (!data || id < 0 || !group) return;
2110
+ const i3 = id * 3;
2111
+ const i4 = id * 4;
2112
+ group.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
2113
+ group.quaternion.set(
2114
+ data.xquat[i4 + 1],
2115
+ data.xquat[i4 + 2],
2116
+ data.xquat[i4 + 3],
2117
+ data.xquat[i4]
2118
+ );
2119
+ });
2120
+ if (!hasChildren) return null;
2121
+ return /* @__PURE__ */ jsx("group", { ref: groupRef, children });
2122
+ }
1975
2123
  var _mat4 = new THREE11.Matrix4();
1976
2124
  var _pos = new THREE11.Vector3();
1977
2125
  var _quat = new THREE11.Quaternion();
1978
2126
  var _scale = new THREE11.Vector3(1, 1, 1);
1979
- function IkGizmo({ siteName, scale = 0.18, onDrag }) {
1980
- const { mjModelRef, mjDataRef, status } = useMujoco();
1981
- const { ikTargetRef, siteIdRef, ikEnabledRef, setIkEnabled } = useIk();
2127
+ function IkGizmo({ controller, siteName, scale = 0.18, onDrag }) {
2128
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
2129
+ const { ikTargetRef, siteIdRef, ikEnabledRef, setIkEnabled } = controller;
1982
2130
  const wrapperRef = useRef(null);
1983
2131
  const pivotRef = useRef(null);
1984
2132
  const draggingRef = useRef(false);
@@ -2075,7 +2223,7 @@ function ContactMarkers({
2075
2223
  visible = true,
2076
2224
  ...groupProps
2077
2225
  } = {}) {
2078
- const { mjDataRef, status } = useMujoco();
2226
+ const { mjDataRef, status } = useMujocoContext();
2079
2227
  const meshRef = useRef(null);
2080
2228
  useFrame(() => {
2081
2229
  const mesh = meshRef.current;
@@ -2119,7 +2267,7 @@ function DragInteraction({
2119
2267
  showArrow = true,
2120
2268
  ...groupProps
2121
2269
  }) {
2122
- const { mjDataRef, mujocoRef, mjModelRef, status } = useMujoco();
2270
+ const { mjDataRef, mujocoRef, mjModelRef, status } = useMujocoContext();
2123
2271
  const { gl, camera, scene, controls } = useThree();
2124
2272
  const draggingRef = useRef(false);
2125
2273
  const bodyIdRef = useRef(-1);
@@ -2277,7 +2425,7 @@ function DragInteraction({
2277
2425
  return /* @__PURE__ */ jsx("group", { ...groupProps, ref: groupRef });
2278
2426
  }
2279
2427
  function useSceneLights(intensity = 1) {
2280
- const { mjModelRef, status } = useMujoco();
2428
+ const { mjModelRef, status } = useMujocoContext();
2281
2429
  const { scene } = useThree();
2282
2430
  const lightsRef = useRef([]);
2283
2431
  const targetsRef = useRef([]);
@@ -2398,7 +2546,7 @@ function Debug({
2398
2546
  showTendons = false,
2399
2547
  ...groupProps
2400
2548
  }) {
2401
- const { mjModelRef, mjDataRef, status } = useMujoco();
2549
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
2402
2550
  const { scene } = useThree();
2403
2551
  const groupRef = useRef(null);
2404
2552
  const debugGeometry = useMemo(() => {
@@ -2668,7 +2816,7 @@ var DEFAULT_TENDON_COLOR = new THREE11.Color(0.3, 0.3, 0.8);
2668
2816
  var DEFAULT_TENDON_WIDTH = 2e-3;
2669
2817
  new THREE11.Vector3();
2670
2818
  function TendonRenderer(props) {
2671
- const { mjModelRef, mjDataRef, status } = useMujoco();
2819
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
2672
2820
  const groupRef = useRef(null);
2673
2821
  const meshesRef = useRef([]);
2674
2822
  const curvesRef = useRef([]);
@@ -2772,7 +2920,7 @@ function TendonRenderer(props) {
2772
2920
  return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
2773
2921
  }
2774
2922
  function FlexRenderer(props) {
2775
- const { mjModelRef, mjDataRef, status } = useMujoco();
2923
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
2776
2924
  const groupRef = useRef(null);
2777
2925
  const meshesRef = useRef([]);
2778
2926
  useEffect(() => {
@@ -2852,7 +3000,7 @@ function getGeomNameCached(model, geomId) {
2852
3000
  return name;
2853
3001
  }
2854
3002
  function useContacts(bodyName, callback) {
2855
- const { mjModelRef, status } = useMujoco();
3003
+ const { mjModelRef, status } = useMujocoContext();
2856
3004
  const contactsRef = useRef([]);
2857
3005
  const bodyIdRef = useRef(-1);
2858
3006
  const bodyResolvedRef = useRef(false);
@@ -2951,7 +3099,7 @@ function ContactListener({
2951
3099
  return null;
2952
3100
  }
2953
3101
  function useTrajectoryPlayer(trajectory, options = {}) {
2954
- const { mjModelRef, mjDataRef, mujocoRef, pausedRef } = useMujoco();
3102
+ const { mjModelRef, mjDataRef, mujocoRef, pausedRef } = useMujocoContext();
2955
3103
  const fps = options.fps ?? 30;
2956
3104
  const loop = options.loop ?? false;
2957
3105
  const playingRef = useRef(false);
@@ -3054,7 +3202,7 @@ function TrajectoryPlayer({
3054
3202
  return null;
3055
3203
  }
3056
3204
  function useActuators() {
3057
- const { mjModelRef, status } = useMujoco();
3205
+ const { mjModelRef, status } = useMujocoContext();
3058
3206
  return useMemo(() => {
3059
3207
  if (status !== "ready") return [];
3060
3208
  const model = mjModelRef.current;
@@ -3073,7 +3221,7 @@ function useActuators() {
3073
3221
  }
3074
3222
  var _mat42 = new THREE11.Matrix4();
3075
3223
  function useSitePosition(siteName) {
3076
- const { mjModelRef, mjDataRef, status } = useMujoco();
3224
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
3077
3225
  const siteIdRef = useRef(-1);
3078
3226
  const positionRef = useRef(new THREE11.Vector3());
3079
3227
  const quaternionRef = useRef(new THREE11.Quaternion());
@@ -3130,7 +3278,7 @@ function useGravityCompensation(enabled = true) {
3130
3278
  });
3131
3279
  }
3132
3280
  function useSensor(name) {
3133
- const { mjModelRef, mjDataRef, status } = useMujoco();
3281
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
3134
3282
  const sensorIdRef = useRef(-1);
3135
3283
  const sensorAdrRef = useRef(0);
3136
3284
  const sensorDimRef = useRef(0);
@@ -3160,7 +3308,7 @@ function useSensor(name) {
3160
3308
  return { value: valueRef, size: sensorDimRef.current };
3161
3309
  }
3162
3310
  function useSensors() {
3163
- const { mjModelRef, status } = useMujoco();
3311
+ const { mjModelRef, status } = useMujocoContext();
3164
3312
  return useMemo(() => {
3165
3313
  const model = mjModelRef.current;
3166
3314
  if (!model || status !== "ready") return [];
@@ -3197,7 +3345,7 @@ function useSensors() {
3197
3345
  }, [mjModelRef, status]);
3198
3346
  }
3199
3347
  function useJointState(name) {
3200
- const { mjModelRef, mjDataRef, status } = useMujoco();
3348
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
3201
3349
  const jointIdRef = useRef(-1);
3202
3350
  const qposAdrRef = useRef(0);
3203
3351
  const dofAdrRef = useRef(0);
@@ -3257,7 +3405,7 @@ function useJointState(name) {
3257
3405
  return { position: positionRef, velocity: velocityRef };
3258
3406
  }
3259
3407
  function useBodyState(name) {
3260
- const { mjModelRef, status } = useMujoco();
3408
+ const { mjModelRef, status } = useMujocoContext();
3261
3409
  const bodyIdRef = useRef(-1);
3262
3410
  const position = useRef(new THREE11.Vector3());
3263
3411
  const quaternion = useRef(new THREE11.Quaternion());
@@ -3289,7 +3437,7 @@ function useBodyState(name) {
3289
3437
  return { position, quaternion, linearVelocity, angularVelocity };
3290
3438
  }
3291
3439
  function useCtrl(name) {
3292
- const { mjModelRef, mjDataRef, status } = useMujoco();
3440
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
3293
3441
  const actuatorIdRef = useRef(-1);
3294
3442
  const valueRef = useRef(0);
3295
3443
  useEffect(() => {
@@ -3306,7 +3454,7 @@ function useCtrl(name) {
3306
3454
  return [valueRef, setValue];
3307
3455
  }
3308
3456
  function useKeyboardTeleop(config) {
3309
- const { mjModelRef, mjDataRef, status } = useMujoco();
3457
+ const { mjModelRef, mjDataRef, status } = useMujocoContext();
3310
3458
  const pressedRef = useRef(/* @__PURE__ */ new Set());
3311
3459
  const toggleStateRef = useRef(/* @__PURE__ */ new Map());
3312
3460
  const enabledRef = useRef(config.enabled ?? true);
@@ -3369,7 +3517,7 @@ function useKeyboardTeleop(config) {
3369
3517
  });
3370
3518
  }
3371
3519
  function usePolicy(config) {
3372
- const { mjModelRef } = useMujoco();
3520
+ const { mjModelRef } = useMujocoContext();
3373
3521
  const lastActionTimeRef = useRef(0);
3374
3522
  const lastActionRef = useRef(null);
3375
3523
  const isRunningRef = useRef(true);
@@ -3403,7 +3551,7 @@ function usePolicy(config) {
3403
3551
  };
3404
3552
  }
3405
3553
  function useTrajectoryRecorder(options = {}) {
3406
- const { mjModelRef } = useMujoco();
3554
+ const { mjModelRef } = useMujocoContext();
3407
3555
  const recordingRef = useRef(false);
3408
3556
  const framesRef = useRef([]);
3409
3557
  const fields = options.fields ?? ["qpos"];
@@ -3479,7 +3627,7 @@ function useTrajectoryRecorder(options = {}) {
3479
3627
  };
3480
3628
  }
3481
3629
  function useGamepad(config) {
3482
- const { mjModelRef, status } = useMujoco();
3630
+ const { mjModelRef, status } = useMujocoContext();
3483
3631
  const configRef = useRef(config);
3484
3632
  configRef.current = config;
3485
3633
  const axisCacheRef = useRef(/* @__PURE__ */ new Map());
@@ -3575,7 +3723,7 @@ function useVideoRecorder(options = {}) {
3575
3723
  };
3576
3724
  }
3577
3725
  function useCtrlNoise(config = {}) {
3578
- const { mjModelRef } = useMujoco();
3726
+ const { mjModelRef } = useMujocoContext();
3579
3727
  const configRef = useRef(config);
3580
3728
  configRef.current = config;
3581
3729
  const noiseRef = useRef(null);
@@ -3726,25 +3874,6 @@ function useCameraAnimation() {
3726
3874
  * @license
3727
3875
  * SPDX-License-Identifier: Apache-2.0
3728
3876
  */
3729
- /**
3730
- * @license
3731
- * SPDX-License-Identifier: Apache-2.0
3732
- *
3733
- * createController — typed factory for BYOC (Bring Your Own Controller) plugins.
3734
- */
3735
- /**
3736
- * @license
3737
- * SPDX-License-Identifier: Apache-2.0
3738
- *
3739
- * IkContext — React context for the IK controller plugin.
3740
- */
3741
- /**
3742
- * @license
3743
- * SPDX-License-Identifier: Apache-2.0
3744
- *
3745
- * IkController — composable IK controller plugin.
3746
- * Extracts all IK logic from MujocoSimProvider into an opt-in component.
3747
- */
3748
3877
  /**
3749
3878
  * @license
3750
3879
  * SPDX-License-Identifier: Apache-2.0
@@ -3908,6 +4037,6 @@ function useCameraAnimation() {
3908
4037
  * useCameraAnimation — composable camera animation hook.
3909
4038
  */
3910
4039
 
3911
- export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
4040
+ export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3912
4041
  //# sourceMappingURL=index.js.map
3913
4042
  //# sourceMappingURL=index.js.map