mujoco-react 10.6.0 → 10.8.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/README.md +43 -11
- package/dist/{chunk-EN55TTGH.js → chunk-5WFYM73E.js} +2 -2
- package/dist/chunk-5WFYM73E.js.map +1 -0
- package/dist/index.d.ts +43 -6
- package/dist/index.js +172 -29
- package/dist/index.js.map +1 -1
- package/dist/onnx.d.ts +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-Dvtm4I0o.d.ts → types-Bu8Aki_j.d.ts} +10 -0
- package/package.json +1 -1
- package/src/components/SceneRenderer.tsx +7 -3
- package/src/core/MujocoCanvas.tsx +2 -0
- package/src/core/MujocoSimProvider.tsx +39 -5
- package/src/hooks/useControlGroup.ts +0 -0
- package/src/hooks/useIkController.ts +16 -3
- package/src/index.ts +3 -1
- package/src/rendering/GeomBuilder.ts +33 -9
- package/src/types.ts +10 -0
- package/dist/chunk-EN55TTGH.js.map +0 -1
package/dist/onnx.d.ts
CHANGED
package/dist/spark.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as _sparkjsdev_spark from '@sparkjsdev/spark';
|
|
3
|
-
import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-
|
|
3
|
+
import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-Bu8Aki_j.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import '@react-three/fiber';
|
|
6
6
|
import 'three';
|
package/dist/spark.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-
|
|
1
|
+
import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-5WFYM73E.js';
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
3
|
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
@@ -310,6 +310,9 @@ interface MujocoModel {
|
|
|
310
310
|
mesh_vert: Float32Array;
|
|
311
311
|
mesh_face: Int32Array;
|
|
312
312
|
mesh_normal: Float32Array;
|
|
313
|
+
mesh_texcoordadr?: Int32Array;
|
|
314
|
+
mesh_texcoord?: Float32Array;
|
|
315
|
+
mesh_facetexcoord?: Int32Array;
|
|
313
316
|
site_bodyid: Int32Array;
|
|
314
317
|
actuator_trnid: Int32Array;
|
|
315
318
|
actuator_ctrlrange: Float64Array;
|
|
@@ -1586,6 +1589,13 @@ type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
|
1586
1589
|
speed?: number;
|
|
1587
1590
|
interpolate?: boolean;
|
|
1588
1591
|
renderOptions?: MujocoRenderOptions;
|
|
1592
|
+
/**
|
|
1593
|
+
* Names of model bodies whose geometry should not be rendered. The bodies stay
|
|
1594
|
+
* in the compiled model and continue to simulate — only their meshes are
|
|
1595
|
+
* skipped at scene-build time, so body/joint/actuator indices are unchanged.
|
|
1596
|
+
* Applied on every (re)build, so toggling names cannot be lost to a rebuild.
|
|
1597
|
+
*/
|
|
1598
|
+
hiddenBodies?: readonly string[];
|
|
1589
1599
|
};
|
|
1590
1600
|
interface SitePositionResult {
|
|
1591
1601
|
position: React__default.RefObject<THREE.Vector3>;
|
package/package.json
CHANGED
|
@@ -35,6 +35,7 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
|
|
|
35
35
|
mujocoRef,
|
|
36
36
|
onSelectionRef,
|
|
37
37
|
hiddenBodiesRef,
|
|
38
|
+
hiddenBodiesVersion,
|
|
38
39
|
interpolateRef,
|
|
39
40
|
interpolationStateRef,
|
|
40
41
|
status,
|
|
@@ -43,6 +44,7 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
|
|
|
43
44
|
const bodyRefs = useRef<(THREE.Group | null)[]>([]);
|
|
44
45
|
const prevModelRef = useRef<MujocoModel | null>(null);
|
|
45
46
|
const prevRenderOptionsKeyRef = useRef<string | null>(null);
|
|
47
|
+
const prevHiddenVersionRef = useRef<number | null>(null);
|
|
46
48
|
const renderOptionsKey = getRenderOptionsKey(renderOptions);
|
|
47
49
|
|
|
48
50
|
const geomBuilder = useMemo(() => {
|
|
@@ -57,15 +59,17 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
|
|
|
57
59
|
const group = groupRef.current;
|
|
58
60
|
if (!model || !group) return;
|
|
59
61
|
|
|
60
|
-
// Skip if model
|
|
62
|
+
// Skip if neither the model, render options, nor the hidden-body set changed
|
|
61
63
|
if (
|
|
62
64
|
prevModelRef.current === model &&
|
|
63
|
-
prevRenderOptionsKeyRef.current === renderOptionsKey
|
|
65
|
+
prevRenderOptionsKeyRef.current === renderOptionsKey &&
|
|
66
|
+
prevHiddenVersionRef.current === hiddenBodiesVersion
|
|
64
67
|
) {
|
|
65
68
|
return;
|
|
66
69
|
}
|
|
67
70
|
prevModelRef.current = model;
|
|
68
71
|
prevRenderOptionsKeyRef.current = renderOptionsKey;
|
|
72
|
+
prevHiddenVersionRef.current = hiddenBodiesVersion;
|
|
69
73
|
|
|
70
74
|
// Clear previous bodies
|
|
71
75
|
while (group.children.length > 0) {
|
|
@@ -92,7 +96,7 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
|
|
|
92
96
|
refs.push(bodyGroup);
|
|
93
97
|
}
|
|
94
98
|
bodyRefs.current = refs;
|
|
95
|
-
}, [status, geomBuilder, mjModelRef, renderOptionsKey]);
|
|
99
|
+
}, [status, geomBuilder, mjModelRef, renderOptionsKey, hiddenBodiesRef, hiddenBodiesVersion]);
|
|
96
100
|
|
|
97
101
|
const syncBodiesToData = useCallback(() => {
|
|
98
102
|
const data = mjDataRef.current;
|
|
@@ -32,6 +32,7 @@ export const MujocoCanvas = forwardRef<MujocoSimAPI, MujocoCanvasProps>(
|
|
|
32
32
|
speed,
|
|
33
33
|
interpolate,
|
|
34
34
|
renderOptions,
|
|
35
|
+
hiddenBodies,
|
|
35
36
|
loadingFallback,
|
|
36
37
|
children,
|
|
37
38
|
...canvasProps
|
|
@@ -75,6 +76,7 @@ export const MujocoCanvas = forwardRef<MujocoSimAPI, MujocoCanvasProps>(
|
|
|
75
76
|
speed={speed}
|
|
76
77
|
interpolate={interpolate}
|
|
77
78
|
renderOptions={renderOptions}
|
|
79
|
+
hiddenBodies={hiddenBodies}
|
|
78
80
|
>
|
|
79
81
|
{children}
|
|
80
82
|
</MujocoSimProvider>
|
|
@@ -441,6 +441,8 @@ export interface MujocoSimContextValue {
|
|
|
441
441
|
errorRef: React.RefObject<string | null>;
|
|
442
442
|
bodyRegistryRef: React.RefObject<Map<string, { definition: SceneObject; hasCustomChildren: boolean }>>;
|
|
443
443
|
hiddenBodiesRef: React.RefObject<Set<string>>;
|
|
444
|
+
/** Bumped whenever the hidden-body set changes, forcing SceneRenderer to rebuild. */
|
|
445
|
+
hiddenBodiesVersion: number;
|
|
444
446
|
requestBodyReload: () => void;
|
|
445
447
|
status: 'loading' | 'ready' | 'error';
|
|
446
448
|
}
|
|
@@ -547,6 +549,7 @@ interface MujocoSimProviderProps {
|
|
|
547
549
|
speed?: number;
|
|
548
550
|
interpolate?: boolean;
|
|
549
551
|
renderOptions?: MujocoRenderOptions;
|
|
552
|
+
hiddenBodies?: readonly string[];
|
|
550
553
|
children: React.ReactNode;
|
|
551
554
|
}
|
|
552
555
|
|
|
@@ -565,6 +568,7 @@ export function MujocoSimProvider({
|
|
|
565
568
|
speed,
|
|
566
569
|
interpolate,
|
|
567
570
|
renderOptions,
|
|
571
|
+
hiddenBodies,
|
|
568
572
|
children,
|
|
569
573
|
}: MujocoSimProviderProps) {
|
|
570
574
|
const { gl, camera, scene } = useThree();
|
|
@@ -602,6 +606,8 @@ export function MujocoSimProvider({
|
|
|
602
606
|
const errorRef = useRef<string | null>(null);
|
|
603
607
|
const bodyRegistryRef = useRef(new Map<string, { definition: SceneObject; hasCustomChildren: boolean }>());
|
|
604
608
|
const hiddenBodiesRef = useRef(new Set<string>());
|
|
609
|
+
const providedHiddenBodiesRef = useRef<Set<string>>(new Set(hiddenBodies ?? []));
|
|
610
|
+
const [hiddenBodiesVersion, setHiddenBodiesVersion] = useState(0);
|
|
605
611
|
const bodyReloadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
606
612
|
|
|
607
613
|
configRef.current = config;
|
|
@@ -629,16 +635,43 @@ export function MujocoSimProvider({
|
|
|
629
635
|
model.opt.timestep = timestep;
|
|
630
636
|
}, [timestep]);
|
|
631
637
|
|
|
638
|
+
// Sync hiddenBodies prop. Rebuilds the render meshes (not the model) when the
|
|
639
|
+
// set changes so a toggle can never be lost to SceneRenderer's own rebuilds.
|
|
640
|
+
const hiddenBodiesKey = (hiddenBodies ?? []).join('');
|
|
641
|
+
useEffect(() => {
|
|
642
|
+
const next = new Set(hiddenBodies ?? []);
|
|
643
|
+
const prev = providedHiddenBodiesRef.current;
|
|
644
|
+
let unchanged = next.size === prev.size;
|
|
645
|
+
if (unchanged) {
|
|
646
|
+
for (const name of next) {
|
|
647
|
+
if (!prev.has(name)) { unchanged = false; break; }
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (unchanged) return;
|
|
651
|
+
providedHiddenBodiesRef.current = next;
|
|
652
|
+
recomputeHiddenBodies();
|
|
653
|
+
setHiddenBodiesVersion(v => v + 1);
|
|
654
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
655
|
+
}, [hiddenBodiesKey]);
|
|
656
|
+
|
|
657
|
+
// Recompute the set of bodies SceneRenderer must skip: bodies whose rendering
|
|
658
|
+
// a <Body> replaces with custom children, plus any named via the hiddenBodies
|
|
659
|
+
// prop. SceneRenderer reads this ref on every build.
|
|
660
|
+
function recomputeHiddenBodies() {
|
|
661
|
+
hiddenBodiesRef.current.clear();
|
|
662
|
+
for (const [name, entry] of bodyRegistryRef.current) {
|
|
663
|
+
if (entry.hasCustomChildren) hiddenBodiesRef.current.add(name);
|
|
664
|
+
}
|
|
665
|
+
for (const name of providedHiddenBodiesRef.current) hiddenBodiesRef.current.add(name);
|
|
666
|
+
}
|
|
667
|
+
|
|
632
668
|
// --- Build merged config (base + body registry) ---
|
|
633
669
|
function buildMergedConfig(baseConfig: SceneConfig): SceneConfig {
|
|
670
|
+
recomputeHiddenBodies();
|
|
634
671
|
if (bodyRegistryRef.current.size === 0) return baseConfig;
|
|
635
672
|
const registeredNames = new Set(bodyRegistryRef.current.keys());
|
|
636
673
|
const baseObjects = (baseConfig.sceneObjects ?? []).filter(o => !registeredNames.has(o.name));
|
|
637
674
|
const registeredBodies = Array.from(bodyRegistryRef.current.values()).map(e => e.definition);
|
|
638
|
-
hiddenBodiesRef.current.clear();
|
|
639
|
-
for (const [name, entry] of bodyRegistryRef.current) {
|
|
640
|
-
if (entry.hasCustomChildren) hiddenBodiesRef.current.add(name);
|
|
641
|
-
}
|
|
642
675
|
return { ...baseConfig, sceneObjects: [...baseObjects, ...registeredBodies] };
|
|
643
676
|
}
|
|
644
677
|
|
|
@@ -1967,10 +2000,11 @@ export function MujocoSimProvider({
|
|
|
1967
2000
|
errorRef,
|
|
1968
2001
|
bodyRegistryRef,
|
|
1969
2002
|
hiddenBodiesRef,
|
|
2003
|
+
hiddenBodiesVersion,
|
|
1970
2004
|
requestBodyReload,
|
|
1971
2005
|
status,
|
|
1972
2006
|
}),
|
|
1973
|
-
[api, status, requestBodyReload]
|
|
2007
|
+
[api, status, requestBodyReload, hiddenBodiesVersion]
|
|
1974
2008
|
);
|
|
1975
2009
|
|
|
1976
2010
|
return (
|
|
Binary file
|
|
@@ -61,7 +61,20 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
61
61
|
const ikTargetRef = useRef<THREE.Group>(new THREE.Group());
|
|
62
62
|
const siteIdRef = useRef(-1);
|
|
63
63
|
const controlGroupRef = useRef<ControlGroupInfo | null>(null);
|
|
64
|
-
|
|
64
|
+
// The solver must always call into the module that owns the live
|
|
65
|
+
// model/data. Capturing the module once at mount breaks when the provider
|
|
66
|
+
// remounts (e.g. StrictMode double-mount): embind then rejects the model
|
|
67
|
+
// with "Expected null or instance of MjModel, got an instance of MjModel".
|
|
68
|
+
const genericIkRef = useRef<GenericIK | null>(null);
|
|
69
|
+
const genericIkModuleRef = useRef<unknown>(null);
|
|
70
|
+
const getGenericIk = useCallback(() => {
|
|
71
|
+
const module = mujocoRef.current;
|
|
72
|
+
if (!genericIkRef.current || genericIkModuleRef.current !== module) {
|
|
73
|
+
genericIkRef.current = new GenericIK(module);
|
|
74
|
+
genericIkModuleRef.current = module;
|
|
75
|
+
}
|
|
76
|
+
return genericIkRef.current;
|
|
77
|
+
}, [mujocoRef]);
|
|
65
78
|
const firstIkEnableRef = useRef(true);
|
|
66
79
|
const needsInitialSync = useRef(true);
|
|
67
80
|
|
|
@@ -119,7 +132,7 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
119
132
|
const data = mjDataRef.current;
|
|
120
133
|
const controlGroup = controlGroupRef.current;
|
|
121
134
|
if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
|
|
122
|
-
return
|
|
135
|
+
return getGenericIk().solve(
|
|
123
136
|
model, data, siteIdRef.current, controlGroup.qposAdr,
|
|
124
137
|
targetPosition, targetQuaternion, currentQ,
|
|
125
138
|
{
|
|
@@ -135,7 +148,7 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
|
|
|
135
148
|
},
|
|
136
149
|
);
|
|
137
150
|
},
|
|
138
|
-
[config, mjModelRef, mjDataRef],
|
|
151
|
+
[config, mjModelRef, mjDataRef, getGenericIk],
|
|
139
152
|
);
|
|
140
153
|
const ikSolveFnRef = useRef<IKSolveFn>(ikSolveFn);
|
|
141
154
|
ikSolveFnRef.current = ikSolveFn;
|
package/src/index.ts
CHANGED
|
@@ -97,11 +97,13 @@ export { useBodyState } from './hooks/useBodyState';
|
|
|
97
97
|
export { useBodyPose, useGeomPose, useSitePose } from './hooks/usePose';
|
|
98
98
|
export type { PoseReadout, PoseResourceKind } from './hooks/usePose';
|
|
99
99
|
export { useCtrl } from './hooks/useCtrl';
|
|
100
|
-
export { controlGroup, useControlGroup } from './hooks/useControlGroup';
|
|
100
|
+
export { controlGroup, defineControls, useControlGroup, useControls } from './hooks/useControlGroup';
|
|
101
101
|
export type {
|
|
102
102
|
ControlGroup,
|
|
103
103
|
ControlGroupHandle,
|
|
104
104
|
ControlGroupSetOptions,
|
|
105
|
+
ControlsHandle,
|
|
106
|
+
DefinedControls,
|
|
105
107
|
UseControlGroupOptions,
|
|
106
108
|
} from './hooks/useControlGroup';
|
|
107
109
|
export { useControlWriter } from './hooks/useControlWriter';
|
|
@@ -155,16 +155,40 @@ export class GeomBuilder {
|
|
|
155
155
|
const fAdr = mjModel.mesh_faceadr[mId];
|
|
156
156
|
const fNum = mjModel.mesh_facenum[mId];
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
const tAdr = mjModel.mesh_texcoordadr?.[mId] ?? -1;
|
|
159
|
+
if (map && tAdr >= 0 && mjModel.mesh_texcoord && mjModel.mesh_facetexcoord) {
|
|
160
|
+
// Textured mesh. MuJoCo indexes texcoords per face corner
|
|
161
|
+
// (mesh_facetexcoord), independently of the vertex indices, so
|
|
162
|
+
// the geometry must be de-indexed to carry a 'uv' attribute.
|
|
163
|
+
const corners = fNum * 3;
|
|
164
|
+
const positions = new Float32Array(corners * 3);
|
|
165
|
+
const uvs = new Float32Array(corners * 2);
|
|
166
|
+
for (let c = 0; c < corners; c++) {
|
|
167
|
+
const v = vAdr + mjModel.mesh_face[fAdr * 3 + c];
|
|
168
|
+
positions[c * 3] = mjModel.mesh_vert[v * 3];
|
|
169
|
+
positions[c * 3 + 1] = mjModel.mesh_vert[v * 3 + 1];
|
|
170
|
+
positions[c * 3 + 2] = mjModel.mesh_vert[v * 3 + 2];
|
|
171
|
+
const t = tAdr + mjModel.mesh_facetexcoord[fAdr * 3 + c];
|
|
172
|
+
uvs[c * 2] = mjModel.mesh_texcoord[t * 2];
|
|
173
|
+
// MuJoCo stores V with image-top origin; flip back to GL convention.
|
|
174
|
+
uvs[c * 2 + 1] = 1 - mjModel.mesh_texcoord[t * 2 + 1];
|
|
175
|
+
}
|
|
176
|
+
geo = new THREE.BufferGeometry();
|
|
177
|
+
geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
178
|
+
geo.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
179
|
+
geo.computeVertexNormals();
|
|
180
|
+
} else {
|
|
181
|
+
geo = new THREE.BufferGeometry();
|
|
182
|
+
// 'position' attribute = vertices
|
|
183
|
+
geo.setAttribute('position', new THREE.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
|
|
184
|
+
// 'index' = faces (triangles connecting vertices)
|
|
185
|
+
geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
|
|
186
|
+
const smoothingTolerance = this.getMeshNormalSmoothingTolerance();
|
|
187
|
+
if (smoothingTolerance !== null) {
|
|
188
|
+
geo = mergeVertices(geo, smoothingTolerance);
|
|
189
|
+
}
|
|
190
|
+
geo.computeVertexNormals(); // Auto-calculate smooth lighting normals
|
|
166
191
|
}
|
|
167
|
-
geo.computeVertexNormals(); // Auto-calculate smooth lighting normals
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
// 5. Construct the final Mesh
|
package/src/types.ts
CHANGED
|
@@ -291,6 +291,9 @@ export interface MujocoModel {
|
|
|
291
291
|
mesh_vert: Float32Array;
|
|
292
292
|
mesh_face: Int32Array;
|
|
293
293
|
mesh_normal: Float32Array;
|
|
294
|
+
mesh_texcoordadr?: Int32Array;
|
|
295
|
+
mesh_texcoord?: Float32Array;
|
|
296
|
+
mesh_facetexcoord?: Int32Array;
|
|
294
297
|
|
|
295
298
|
// Site
|
|
296
299
|
site_bodyid: Int32Array;
|
|
@@ -1843,6 +1846,13 @@ export type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
|
1843
1846
|
speed?: number;
|
|
1844
1847
|
interpolate?: boolean;
|
|
1845
1848
|
renderOptions?: MujocoRenderOptions;
|
|
1849
|
+
/**
|
|
1850
|
+
* Names of model bodies whose geometry should not be rendered. The bodies stay
|
|
1851
|
+
* in the compiled model and continue to simulate — only their meshes are
|
|
1852
|
+
* skipped at scene-build time, so body/joint/actuator indices are unchanged.
|
|
1853
|
+
* Applied on every (re)build, so toggling names cannot be lost to a rebuild.
|
|
1854
|
+
*/
|
|
1855
|
+
hiddenBodies?: readonly string[];
|
|
1846
1856
|
};
|
|
1847
1857
|
|
|
1848
1858
|
// ---- Hook Return Types ----
|