mujoco-react 10.2.1 → 10.4.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 +23 -1
- package/dist/{chunk-CYDGWNKQ.js → chunk-FBXXXPLQ.js} +169 -22
- package/dist/chunk-FBXXXPLQ.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +345 -40
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +4 -10
- package/dist/spark.js.map +1 -1
- package/dist/{types-B-J8fpPP.d.ts → types-CdFZCYmy.d.ts} +115 -1
- package/package.json +1 -1
- package/src/components/Debug.tsx +174 -3
- package/src/components/SceneRenderer.tsx +25 -6
- package/src/core/MujocoCanvas.tsx +8 -4
- package/src/core/MujocoPhysics.tsx +6 -4
- package/src/core/MujocoProvider.tsx +6 -4
- package/src/core/MujocoSimProvider.tsx +189 -9
- package/src/index.ts +1 -0
- package/src/rendering/GeomBuilder.ts +18 -2
- package/src/rendering/cameraFrameCapture.ts +229 -19
- package/src/spark.tsx +4 -12
- package/src/types.ts +121 -0
- package/dist/chunk-CYDGWNKQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY } from './chunk-
|
|
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, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-
|
|
1
|
+
import { withContacts, getContact, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY } from './chunk-FBXXXPLQ.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, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withSplatEnvironment } from './chunk-FBXXXPLQ.js';
|
|
3
3
|
import loadMujoco from '@mujoco/mujoco';
|
|
4
4
|
import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
|
|
5
|
-
import { createContext, forwardRef, useEffect, useContext, useState,
|
|
5
|
+
import { createContext, forwardRef, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect } from 'react';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
import { Canvas, useThree, useFrame } from '@react-three/fiber';
|
|
8
8
|
import * as THREE11 from 'three';
|
|
9
|
+
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
9
10
|
import { PivotControls } from '@react-three/drei';
|
|
10
11
|
|
|
11
12
|
var MujocoContext = createContext({
|
|
@@ -43,6 +44,8 @@ function MujocoProvider({
|
|
|
43
44
|
const [error, setError] = useState(null);
|
|
44
45
|
const moduleRef = useRef(null);
|
|
45
46
|
const isMounted = useRef(true);
|
|
47
|
+
const onErrorRef = useRef(onError);
|
|
48
|
+
onErrorRef.current = onError;
|
|
46
49
|
useEffect(() => {
|
|
47
50
|
isMounted.current = true;
|
|
48
51
|
const variant = resolveWasmVariant(wasmVariant, threadedLoader, mtWasmUrl);
|
|
@@ -50,7 +53,7 @@ function MujocoProvider({
|
|
|
50
53
|
const err = new Error('MujocoProvider wasmVariant="threaded" requires a threadedLoader from @mujoco/mujoco/mt');
|
|
51
54
|
setError(err.message);
|
|
52
55
|
setStatus("error");
|
|
53
|
-
|
|
56
|
+
onErrorRef.current?.(err);
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
59
|
let selectedWasmUrl = wasmUrl ?? defaultMujocoWasmUrl;
|
|
@@ -59,7 +62,7 @@ function MujocoProvider({
|
|
|
59
62
|
const err = new Error('MujocoProvider wasmVariant="threaded" requires mtWasmUrl from @mujoco/mujoco/mt/mujoco.wasm?url');
|
|
60
63
|
setError(err.message);
|
|
61
64
|
setStatus("error");
|
|
62
|
-
|
|
65
|
+
onErrorRef.current?.(err);
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
68
|
selectedWasmUrl = mtWasmUrl;
|
|
@@ -90,13 +93,13 @@ function MujocoProvider({
|
|
|
90
93
|
const msg = err.message || "Failed to init spatial simulation";
|
|
91
94
|
setError(msg);
|
|
92
95
|
setStatus("error");
|
|
93
|
-
|
|
96
|
+
onErrorRef.current?.(new Error(msg));
|
|
94
97
|
}
|
|
95
98
|
});
|
|
96
99
|
return () => {
|
|
97
100
|
isMounted.current = false;
|
|
98
101
|
};
|
|
99
|
-
}, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, timeout
|
|
102
|
+
}, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, timeout]);
|
|
100
103
|
return /* @__PURE__ */ jsx(
|
|
101
104
|
MujocoContext.Provider,
|
|
102
105
|
{
|
|
@@ -891,11 +894,20 @@ function collectDependencyPaths(xmlString, currentFile, parser) {
|
|
|
891
894
|
}
|
|
892
895
|
|
|
893
896
|
// src/rendering/GeomBuilder.ts
|
|
897
|
+
var DEFAULT_MESH_NORMAL_SMOOTHING_TOLERANCE = 1e-4;
|
|
894
898
|
var GeomBuilder = class {
|
|
895
899
|
mujoco;
|
|
896
900
|
textureCache = /* @__PURE__ */ new Map();
|
|
897
|
-
|
|
901
|
+
renderOptions;
|
|
902
|
+
constructor(mujoco, renderOptions) {
|
|
898
903
|
this.mujoco = mujoco;
|
|
904
|
+
this.renderOptions = renderOptions;
|
|
905
|
+
}
|
|
906
|
+
getMeshNormalSmoothingTolerance() {
|
|
907
|
+
const smoothing = this.renderOptions?.meshNormalSmoothing;
|
|
908
|
+
if (!smoothing) return null;
|
|
909
|
+
if (smoothing === true) return DEFAULT_MESH_NORMAL_SMOOTHING_TOLERANCE;
|
|
910
|
+
return smoothing.tolerance ?? DEFAULT_MESH_NORMAL_SMOOTHING_TOLERANCE;
|
|
899
911
|
}
|
|
900
912
|
getMaterialTexture(mjModel, matId) {
|
|
901
913
|
if (matId < 0 || !mjModel.mat_texid || !mjModel.tex_data) return null;
|
|
@@ -990,6 +1002,10 @@ var GeomBuilder = class {
|
|
|
990
1002
|
geo = new THREE11.BufferGeometry();
|
|
991
1003
|
geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
|
|
992
1004
|
geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
|
|
1005
|
+
const smoothingTolerance = this.getMeshNormalSmoothingTolerance();
|
|
1006
|
+
if (smoothingTolerance !== null) {
|
|
1007
|
+
geo = mergeVertices(geo, smoothingTolerance);
|
|
1008
|
+
}
|
|
993
1009
|
geo.computeVertexNormals();
|
|
994
1010
|
}
|
|
995
1011
|
if (geo) {
|
|
@@ -1020,7 +1036,13 @@ var GeomBuilder = class {
|
|
|
1020
1036
|
return null;
|
|
1021
1037
|
}
|
|
1022
1038
|
};
|
|
1023
|
-
function
|
|
1039
|
+
function getRenderOptionsKey(renderOptions) {
|
|
1040
|
+
const smoothing = renderOptions?.meshNormalSmoothing;
|
|
1041
|
+
if (!smoothing) return "default";
|
|
1042
|
+
if (smoothing === true) return "meshNormalSmoothing:true";
|
|
1043
|
+
return `meshNormalSmoothing:${smoothing.tolerance ?? "default"}`;
|
|
1044
|
+
}
|
|
1045
|
+
function SceneRenderer({ renderOptions, ...props }) {
|
|
1024
1046
|
const {
|
|
1025
1047
|
mjModelRef,
|
|
1026
1048
|
mjDataRef,
|
|
@@ -1034,17 +1056,22 @@ function SceneRenderer(props) {
|
|
|
1034
1056
|
const groupRef = useRef(null);
|
|
1035
1057
|
const bodyRefs = useRef([]);
|
|
1036
1058
|
const prevModelRef = useRef(null);
|
|
1059
|
+
const prevRenderOptionsKeyRef = useRef(null);
|
|
1060
|
+
const renderOptionsKey = getRenderOptionsKey(renderOptions);
|
|
1037
1061
|
const geomBuilder = useMemo(() => {
|
|
1038
1062
|
if (status !== "ready") return null;
|
|
1039
|
-
return new GeomBuilder(mujocoRef.current);
|
|
1040
|
-
}, [status, mujocoRef]);
|
|
1063
|
+
return new GeomBuilder(mujocoRef.current, renderOptions);
|
|
1064
|
+
}, [status, mujocoRef, renderOptionsKey]);
|
|
1041
1065
|
useEffect(() => {
|
|
1042
1066
|
if (status !== "ready" || !geomBuilder) return;
|
|
1043
1067
|
const model = mjModelRef.current;
|
|
1044
1068
|
const group = groupRef.current;
|
|
1045
1069
|
if (!model || !group) return;
|
|
1046
|
-
if (prevModelRef.current === model)
|
|
1070
|
+
if (prevModelRef.current === model && prevRenderOptionsKeyRef.current === renderOptionsKey) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1047
1073
|
prevModelRef.current = model;
|
|
1074
|
+
prevRenderOptionsKeyRef.current = renderOptionsKey;
|
|
1048
1075
|
while (group.children.length > 0) {
|
|
1049
1076
|
group.remove(group.children[0]);
|
|
1050
1077
|
}
|
|
@@ -1065,7 +1092,7 @@ function SceneRenderer(props) {
|
|
|
1065
1092
|
refs.push(bodyGroup);
|
|
1066
1093
|
}
|
|
1067
1094
|
bodyRefs.current = refs;
|
|
1068
|
-
}, [status, geomBuilder, mjModelRef]);
|
|
1095
|
+
}, [status, geomBuilder, mjModelRef, renderOptionsKey]);
|
|
1069
1096
|
const syncBodiesToData = useCallback(() => {
|
|
1070
1097
|
const data = mjDataRef.current;
|
|
1071
1098
|
if (!data) return;
|
|
@@ -1930,6 +1957,106 @@ function applyMountedCameraPoseOffsets(options, position, quaternion) {
|
|
|
1930
1957
|
]
|
|
1931
1958
|
};
|
|
1932
1959
|
}
|
|
1960
|
+
function resolveMujocoCameraCompatibilityOptions(options) {
|
|
1961
|
+
const compatibility = options.mujocoCameraCompatibility;
|
|
1962
|
+
if (!compatibility) return null;
|
|
1963
|
+
if (compatibility === true) {
|
|
1964
|
+
return {
|
|
1965
|
+
useResolution: true,
|
|
1966
|
+
useIntrinsics: true,
|
|
1967
|
+
useClipping: true,
|
|
1968
|
+
preserveAspect: true,
|
|
1969
|
+
preferResolution: false
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
return {
|
|
1973
|
+
useResolution: compatibility.useResolution ?? true,
|
|
1974
|
+
useIntrinsics: compatibility.useIntrinsics ?? true,
|
|
1975
|
+
useClipping: compatibility.useClipping ?? true,
|
|
1976
|
+
preserveAspect: compatibility.preserveAspect ?? true,
|
|
1977
|
+
preferResolution: compatibility.preferResolution ?? false
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function mujocoVisualClip(model) {
|
|
1981
|
+
const map = model.vis?.map;
|
|
1982
|
+
const near = typeof map?.znear === "number" && map.znear > 0 ? map.znear : void 0;
|
|
1983
|
+
const far = typeof map?.zfar === "number" && map.zfar > 0 ? map.zfar : void 0;
|
|
1984
|
+
return { near, far };
|
|
1985
|
+
}
|
|
1986
|
+
function mujocoCameraResolution(model, cameraId) {
|
|
1987
|
+
const resolution = model.cam_resolution;
|
|
1988
|
+
if (!resolution) return {};
|
|
1989
|
+
const width = Number(resolution[cameraId * 2]);
|
|
1990
|
+
const height = Number(resolution[cameraId * 2 + 1]);
|
|
1991
|
+
return {
|
|
1992
|
+
width: Number.isFinite(width) && width > 0 ? width : void 0,
|
|
1993
|
+
height: Number.isFinite(height) && height > 0 ? height : void 0
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
function mujocoCameraProjectionMatrix(model, cameraId, width, height, near, far) {
|
|
1997
|
+
const intrinsic = model.cam_intrinsic;
|
|
1998
|
+
const sensorSize = model.cam_sensorsize;
|
|
1999
|
+
if (!intrinsic || !sensorSize || !width || !height) return void 0;
|
|
2000
|
+
const intrinsicOffset = cameraId * 4;
|
|
2001
|
+
const sensorOffset = cameraId * 2;
|
|
2002
|
+
const focalX = Number(intrinsic[intrinsicOffset]);
|
|
2003
|
+
const focalY = Number(intrinsic[intrinsicOffset + 1]);
|
|
2004
|
+
const principalX = Number(intrinsic[intrinsicOffset + 2]);
|
|
2005
|
+
const principalY = Number(intrinsic[intrinsicOffset + 3]);
|
|
2006
|
+
const sensorWidth = Number(sensorSize[sensorOffset]);
|
|
2007
|
+
const sensorHeight = Number(sensorSize[sensorOffset + 1]);
|
|
2008
|
+
if (!Number.isFinite(focalX) || !Number.isFinite(focalY) || !Number.isFinite(principalX) || !Number.isFinite(principalY) || !Number.isFinite(sensorWidth) || !Number.isFinite(sensorHeight) || focalX <= 0 || focalY <= 0 || sensorWidth <= 0 || sensorHeight <= 0) {
|
|
2009
|
+
return void 0;
|
|
2010
|
+
}
|
|
2011
|
+
const fx = focalX / sensorWidth * width;
|
|
2012
|
+
const fy = focalY / sensorHeight * height;
|
|
2013
|
+
const cx = width * (0.5 + principalX / sensorWidth);
|
|
2014
|
+
const cy = height * (0.5 + principalY / sensorHeight);
|
|
2015
|
+
const znear = near ?? 0.01;
|
|
2016
|
+
const zfar = far ?? 100;
|
|
2017
|
+
return new THREE11.Matrix4().set(
|
|
2018
|
+
2 * fx / width,
|
|
2019
|
+
0,
|
|
2020
|
+
1 - 2 * cx / width,
|
|
2021
|
+
0,
|
|
2022
|
+
0,
|
|
2023
|
+
2 * fy / height,
|
|
2024
|
+
2 * cy / height - 1,
|
|
2025
|
+
0,
|
|
2026
|
+
0,
|
|
2027
|
+
0,
|
|
2028
|
+
-(zfar + znear) / (zfar - znear),
|
|
2029
|
+
-2 * zfar * znear / (zfar - znear),
|
|
2030
|
+
0,
|
|
2031
|
+
0,
|
|
2032
|
+
-1,
|
|
2033
|
+
0
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
function resolveMujocoCameraCaptureDimensions(requested, cameraResolution, compatibility) {
|
|
2037
|
+
if (!compatibility.useResolution) {
|
|
2038
|
+
return {
|
|
2039
|
+
width: requested.width,
|
|
2040
|
+
height: requested.height
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
if (compatibility.preferResolution) {
|
|
2044
|
+
return {
|
|
2045
|
+
width: cameraResolution.width ?? requested.width,
|
|
2046
|
+
height: cameraResolution.height ?? requested.height
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
let width = requested.width ?? cameraResolution.width;
|
|
2050
|
+
let height = requested.height ?? cameraResolution.height;
|
|
2051
|
+
if (compatibility.preserveAspect && cameraResolution.width && cameraResolution.height) {
|
|
2052
|
+
if (requested.width !== void 0 && requested.height === void 0) {
|
|
2053
|
+
height = requested.width * cameraResolution.height / cameraResolution.width;
|
|
2054
|
+
} else if (requested.height !== void 0 && requested.width === void 0) {
|
|
2055
|
+
width = requested.height * cameraResolution.width / cameraResolution.height;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return { width, height };
|
|
2059
|
+
}
|
|
1933
2060
|
function countMountedCameraSelectors(options) {
|
|
1934
2061
|
return Number(Boolean(options.cameraName)) + Number(Boolean(options.siteName)) + Number(Boolean(options.bodyName));
|
|
1935
2062
|
}
|
|
@@ -2033,6 +2160,7 @@ function MujocoSimProvider({
|
|
|
2033
2160
|
paused,
|
|
2034
2161
|
speed,
|
|
2035
2162
|
interpolate,
|
|
2163
|
+
renderOptions,
|
|
2036
2164
|
children
|
|
2037
2165
|
}) {
|
|
2038
2166
|
const { gl, camera, scene } = useThree();
|
|
@@ -2067,24 +2195,12 @@ function MujocoSimProvider({
|
|
|
2067
2195
|
const bodyRegistryRef = useRef(/* @__PURE__ */ new Map());
|
|
2068
2196
|
const hiddenBodiesRef = useRef(/* @__PURE__ */ new Set());
|
|
2069
2197
|
const bodyReloadTimerRef = useRef(null);
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
useEffect(() => {
|
|
2077
|
-
pausedRef.current = paused ?? false;
|
|
2078
|
-
}, [paused]);
|
|
2079
|
-
useEffect(() => {
|
|
2080
|
-
speedRef.current = speed ?? 1;
|
|
2081
|
-
}, [speed]);
|
|
2082
|
-
useEffect(() => {
|
|
2083
|
-
substepsRef.current = substeps ?? 1;
|
|
2084
|
-
}, [substeps]);
|
|
2085
|
-
useEffect(() => {
|
|
2086
|
-
interpolateRef.current = interpolate ?? false;
|
|
2087
|
-
}, [interpolate]);
|
|
2198
|
+
configRef.current = config;
|
|
2199
|
+
mujocoRef.current = mujoco;
|
|
2200
|
+
pausedRef.current = paused ?? false;
|
|
2201
|
+
speedRef.current = speed ?? 1;
|
|
2202
|
+
substepsRef.current = substeps ?? 1;
|
|
2203
|
+
interpolateRef.current = interpolate ?? false;
|
|
2088
2204
|
useEffect(() => {
|
|
2089
2205
|
if (!gravity) return;
|
|
2090
2206
|
const model = mjModelRef.current;
|
|
@@ -2590,11 +2706,27 @@ function MujocoSimProvider({
|
|
|
2590
2706
|
for (let i = 0; i < ncam; i += 1) {
|
|
2591
2707
|
const posOffset = i * 3;
|
|
2592
2708
|
const quatOffset = i * 4;
|
|
2709
|
+
const intrinsicOffset = i * 4;
|
|
2710
|
+
const resolutionOffset = i * 2;
|
|
2593
2711
|
result.push({
|
|
2594
2712
|
id: i,
|
|
2595
2713
|
name: getName(model, nameAddresses[i]),
|
|
2596
2714
|
bodyId: model.cam_bodyid?.[i] ?? -1,
|
|
2597
2715
|
fov: model.cam_fovy?.[i] ?? null,
|
|
2716
|
+
resolution: model.cam_resolution ? [
|
|
2717
|
+
model.cam_resolution[resolutionOffset],
|
|
2718
|
+
model.cam_resolution[resolutionOffset + 1]
|
|
2719
|
+
] : null,
|
|
2720
|
+
sensorSize: model.cam_sensorsize ? [
|
|
2721
|
+
model.cam_sensorsize[resolutionOffset],
|
|
2722
|
+
model.cam_sensorsize[resolutionOffset + 1]
|
|
2723
|
+
] : null,
|
|
2724
|
+
intrinsic: model.cam_intrinsic ? [
|
|
2725
|
+
model.cam_intrinsic[intrinsicOffset],
|
|
2726
|
+
model.cam_intrinsic[intrinsicOffset + 1],
|
|
2727
|
+
model.cam_intrinsic[intrinsicOffset + 2],
|
|
2728
|
+
model.cam_intrinsic[intrinsicOffset + 3]
|
|
2729
|
+
] : null,
|
|
2598
2730
|
position: model.cam_pos ? vector3FromArray(model.cam_pos, posOffset) : null,
|
|
2599
2731
|
quaternion: model.cam_quat ? quaternionFromMujocoQuat(model.cam_quat, quatOffset) : null
|
|
2600
2732
|
});
|
|
@@ -2622,10 +2754,22 @@ function MujocoSimProvider({
|
|
|
2622
2754
|
);
|
|
2623
2755
|
}
|
|
2624
2756
|
const pose = applyMountedCameraPoseOffsets(options, position, quaternion);
|
|
2757
|
+
const compatibility = resolveMujocoCameraCompatibilityOptions(options);
|
|
2758
|
+
const cameraResolution = compatibility?.useResolution ? mujocoCameraResolution(model, cameraId) : { width: void 0, height: void 0 };
|
|
2759
|
+
const clip = compatibility?.useClipping ? mujocoVisualClip(model) : { near: void 0, far: void 0 };
|
|
2760
|
+
const { width, height } = compatibility ? resolveMujocoCameraCaptureDimensions(options, cameraResolution, compatibility) : { width: options.width, height: options.height };
|
|
2761
|
+
const near = options.near ?? clip.near;
|
|
2762
|
+
const far = options.far ?? clip.far;
|
|
2763
|
+
const projectionMatrix = compatibility?.useIntrinsics ? mujocoCameraProjectionMatrix(model, cameraId, width, height, near, far) : void 0;
|
|
2625
2764
|
return {
|
|
2626
2765
|
...baseOptions,
|
|
2766
|
+
width,
|
|
2767
|
+
height,
|
|
2627
2768
|
...pose,
|
|
2628
2769
|
fov: options.fov ?? model.cam_fovy?.[cameraId],
|
|
2770
|
+
near,
|
|
2771
|
+
far,
|
|
2772
|
+
projectionMatrix: options.projectionMatrix ?? projectionMatrix,
|
|
2629
2773
|
source: { kind: "mujoco-camera", cameraName: options.cameraName }
|
|
2630
2774
|
};
|
|
2631
2775
|
}
|
|
@@ -3271,7 +3415,7 @@ function MujocoSimProvider({
|
|
|
3271
3415
|
[api, status, requestBodyReload]
|
|
3272
3416
|
);
|
|
3273
3417
|
return /* @__PURE__ */ jsxs(MujocoSimContext.Provider, { value: contextValue, children: [
|
|
3274
|
-
/* @__PURE__ */ jsx(SceneRenderer, {}),
|
|
3418
|
+
/* @__PURE__ */ jsx(SceneRenderer, { renderOptions }),
|
|
3275
3419
|
children
|
|
3276
3420
|
] });
|
|
3277
3421
|
}
|
|
@@ -3289,16 +3433,19 @@ var MujocoCanvas = forwardRef(
|
|
|
3289
3433
|
paused,
|
|
3290
3434
|
speed,
|
|
3291
3435
|
interpolate,
|
|
3436
|
+
renderOptions,
|
|
3292
3437
|
loadingFallback,
|
|
3293
3438
|
children,
|
|
3294
3439
|
...canvasProps
|
|
3295
3440
|
}, ref) {
|
|
3296
3441
|
const { mujoco, status: wasmStatus, error: wasmError } = useMujocoWasm();
|
|
3442
|
+
const onErrorRef = useRef(onError);
|
|
3443
|
+
onErrorRef.current = onError;
|
|
3297
3444
|
useEffect(() => {
|
|
3298
|
-
if (wasmStatus === "error"
|
|
3299
|
-
|
|
3445
|
+
if (wasmStatus === "error") {
|
|
3446
|
+
onErrorRef.current?.(new Error(wasmError ?? "WASM load failed"));
|
|
3300
3447
|
}
|
|
3301
|
-
}, [wasmStatus, wasmError
|
|
3448
|
+
}, [wasmStatus, wasmError]);
|
|
3302
3449
|
if (wasmStatus === "loading" || !mujoco) {
|
|
3303
3450
|
return loadingFallback ? /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: loadingFallback }) : null;
|
|
3304
3451
|
}
|
|
@@ -3321,6 +3468,7 @@ var MujocoCanvas = forwardRef(
|
|
|
3321
3468
|
paused,
|
|
3322
3469
|
speed,
|
|
3323
3470
|
interpolate,
|
|
3471
|
+
renderOptions,
|
|
3324
3472
|
children
|
|
3325
3473
|
}
|
|
3326
3474
|
) });
|
|
@@ -3329,11 +3477,13 @@ var MujocoCanvas = forwardRef(
|
|
|
3329
3477
|
var MujocoPhysics = forwardRef(
|
|
3330
3478
|
function MujocoPhysics2({ onError, children, ...props }, ref) {
|
|
3331
3479
|
const { mujoco, status: wasmStatus, error: wasmError } = useMujocoWasm();
|
|
3480
|
+
const onErrorRef = useRef(onError);
|
|
3481
|
+
onErrorRef.current = onError;
|
|
3332
3482
|
useEffect(() => {
|
|
3333
|
-
if (wasmStatus === "error"
|
|
3334
|
-
|
|
3483
|
+
if (wasmStatus === "error") {
|
|
3484
|
+
onErrorRef.current?.(new Error(wasmError ?? "WASM load failed"));
|
|
3335
3485
|
}
|
|
3336
|
-
}, [wasmStatus, wasmError
|
|
3486
|
+
}, [wasmStatus, wasmError]);
|
|
3337
3487
|
if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
|
|
3338
3488
|
return null;
|
|
3339
3489
|
}
|
|
@@ -4717,11 +4867,161 @@ var _contactNormal = new THREE11.Vector3();
|
|
|
4717
4867
|
var MAX_CONTACT_ARROWS = 50;
|
|
4718
4868
|
var CAMERA_DEBUG_LENGTH = 0.12;
|
|
4719
4869
|
var CAMERA_DEBUG_FRUSTUM_DEPTH = 0.08;
|
|
4870
|
+
function toVector32(value, fallback) {
|
|
4871
|
+
if (!value) return fallback.clone();
|
|
4872
|
+
return value instanceof THREE11.Vector3 ? value.clone() : new THREE11.Vector3(value[0], value[1], value[2]);
|
|
4873
|
+
}
|
|
4874
|
+
function createCameraLabel(text, color) {
|
|
4875
|
+
const canvas = document.createElement("canvas");
|
|
4876
|
+
canvas.width = 256;
|
|
4877
|
+
canvas.height = 64;
|
|
4878
|
+
const ctx = canvas.getContext("2d");
|
|
4879
|
+
ctx.fillStyle = new THREE11.Color(color).getStyle();
|
|
4880
|
+
ctx.font = "bold 32px monospace";
|
|
4881
|
+
ctx.textAlign = "center";
|
|
4882
|
+
ctx.fillText(text, 128, 42);
|
|
4883
|
+
const texture = new THREE11.CanvasTexture(canvas);
|
|
4884
|
+
const sprite = new THREE11.Sprite(
|
|
4885
|
+
new THREE11.SpriteMaterial({
|
|
4886
|
+
map: texture,
|
|
4887
|
+
depthTest: false,
|
|
4888
|
+
transparent: true
|
|
4889
|
+
})
|
|
4890
|
+
);
|
|
4891
|
+
sprite.position.set(0, 0.014, 0.01);
|
|
4892
|
+
sprite.scale.set(0.05, 0.012, 1);
|
|
4893
|
+
sprite.renderOrder = 999;
|
|
4894
|
+
return sprite;
|
|
4895
|
+
}
|
|
4896
|
+
function createVirtualCameraDebugObject(camera, index) {
|
|
4897
|
+
const color = camera.color ?? "#ff3d71";
|
|
4898
|
+
const aimColor = camera.aimColor ?? "#38bdf8";
|
|
4899
|
+
const markerScale = camera.markerScale ?? 1;
|
|
4900
|
+
const cameraPosition = toVector32(camera.position, new THREE11.Vector3());
|
|
4901
|
+
const configuredUp = toVector32(camera.up, new THREE11.Vector3(0, 0, 1)).normalize();
|
|
4902
|
+
const cameraQuaternion = new THREE11.Quaternion();
|
|
4903
|
+
const forward = new THREE11.Vector3();
|
|
4904
|
+
if (camera.quaternion) {
|
|
4905
|
+
if (camera.quaternion instanceof THREE11.Quaternion) {
|
|
4906
|
+
cameraQuaternion.copy(camera.quaternion);
|
|
4907
|
+
} else {
|
|
4908
|
+
cameraQuaternion.set(
|
|
4909
|
+
camera.quaternion[0],
|
|
4910
|
+
camera.quaternion[1],
|
|
4911
|
+
camera.quaternion[2],
|
|
4912
|
+
camera.quaternion[3]
|
|
4913
|
+
);
|
|
4914
|
+
}
|
|
4915
|
+
forward.set(0, 0, -1).applyQuaternion(cameraQuaternion).normalize();
|
|
4916
|
+
} else {
|
|
4917
|
+
const target2 = toVector32(
|
|
4918
|
+
camera.lookAt,
|
|
4919
|
+
cameraPosition.clone().add(new THREE11.Vector3(0, 0, -1))
|
|
4920
|
+
);
|
|
4921
|
+
forward.copy(target2).sub(cameraPosition);
|
|
4922
|
+
if (forward.lengthSq() < 1e-8) forward.set(0, 0, -1);
|
|
4923
|
+
forward.normalize();
|
|
4924
|
+
cameraQuaternion.setFromRotationMatrix(
|
|
4925
|
+
new THREE11.Matrix4().lookAt(cameraPosition, target2, configuredUp)
|
|
4926
|
+
);
|
|
4927
|
+
}
|
|
4928
|
+
const target = camera.lookAt ? toVector32(camera.lookAt, cameraPosition.clone().add(forward)) : cameraPosition.clone().addScaledVector(forward, 0.4);
|
|
4929
|
+
const distanceToTarget = Math.max(target.distanceTo(cameraPosition), 1e-3);
|
|
4930
|
+
const depth = camera.frustumDepth ?? Math.min(Math.max(distanceToTarget * 0.42, 0.16), 0.45);
|
|
4931
|
+
const fov = camera.fov ?? 50;
|
|
4932
|
+
const aspect = (camera.width ?? 640) / (camera.height ?? 480);
|
|
4933
|
+
const right = forward.clone().cross(configuredUp);
|
|
4934
|
+
if (right.lengthSq() < 1e-8) right.set(1, 0, 0);
|
|
4935
|
+
right.normalize();
|
|
4936
|
+
const orthogonalUp = right.clone().cross(forward).normalize();
|
|
4937
|
+
const frustumHeight = 2 * Math.tan(THREE11.MathUtils.degToRad(fov) / 2) * depth;
|
|
4938
|
+
const frustumWidth = frustumHeight * aspect;
|
|
4939
|
+
const center = cameraPosition.clone().addScaledVector(forward, depth);
|
|
4940
|
+
const halfRight = right.clone().multiplyScalar(frustumWidth / 2);
|
|
4941
|
+
const halfUp = orthogonalUp.clone().multiplyScalar(frustumHeight / 2);
|
|
4942
|
+
const topLeft = center.clone().sub(halfRight).add(halfUp);
|
|
4943
|
+
const topRight = center.clone().add(halfRight).add(halfUp);
|
|
4944
|
+
const bottomRight = center.clone().add(halfRight).sub(halfUp);
|
|
4945
|
+
const bottomLeft = center.clone().sub(halfRight).sub(halfUp);
|
|
4946
|
+
const frustumPoints = [
|
|
4947
|
+
cameraPosition,
|
|
4948
|
+
topLeft,
|
|
4949
|
+
cameraPosition,
|
|
4950
|
+
topRight,
|
|
4951
|
+
cameraPosition,
|
|
4952
|
+
bottomRight,
|
|
4953
|
+
cameraPosition,
|
|
4954
|
+
bottomLeft,
|
|
4955
|
+
topLeft,
|
|
4956
|
+
topRight,
|
|
4957
|
+
topRight,
|
|
4958
|
+
bottomRight,
|
|
4959
|
+
bottomRight,
|
|
4960
|
+
bottomLeft,
|
|
4961
|
+
bottomLeft,
|
|
4962
|
+
topLeft
|
|
4963
|
+
];
|
|
4964
|
+
const group = new THREE11.Group();
|
|
4965
|
+
group.name = camera.name ?? `virtual-camera-${index}`;
|
|
4966
|
+
group.renderOrder = 999;
|
|
4967
|
+
group.frustumCulled = false;
|
|
4968
|
+
const frustum = new THREE11.LineSegments(
|
|
4969
|
+
new THREE11.BufferGeometry().setFromPoints(frustumPoints),
|
|
4970
|
+
new THREE11.LineBasicMaterial({
|
|
4971
|
+
color,
|
|
4972
|
+
transparent: true,
|
|
4973
|
+
opacity: 0.9,
|
|
4974
|
+
depthTest: false
|
|
4975
|
+
})
|
|
4976
|
+
);
|
|
4977
|
+
frustum.renderOrder = 999;
|
|
4978
|
+
frustum.frustumCulled = false;
|
|
4979
|
+
group.add(frustum);
|
|
4980
|
+
const aim = new THREE11.LineSegments(
|
|
4981
|
+
new THREE11.BufferGeometry().setFromPoints([cameraPosition, target]),
|
|
4982
|
+
new THREE11.LineBasicMaterial({
|
|
4983
|
+
color: aimColor,
|
|
4984
|
+
transparent: true,
|
|
4985
|
+
opacity: 0.95,
|
|
4986
|
+
depthTest: false
|
|
4987
|
+
})
|
|
4988
|
+
);
|
|
4989
|
+
aim.renderOrder = 999;
|
|
4990
|
+
aim.frustumCulled = false;
|
|
4991
|
+
group.add(aim);
|
|
4992
|
+
const markerGroup = new THREE11.Group();
|
|
4993
|
+
markerGroup.position.copy(cameraPosition);
|
|
4994
|
+
markerGroup.quaternion.copy(cameraQuaternion);
|
|
4995
|
+
markerGroup.renderOrder = 999;
|
|
4996
|
+
markerGroup.frustumCulled = false;
|
|
4997
|
+
markerGroup.add(new THREE11.Mesh(
|
|
4998
|
+
new THREE11.BoxGeometry(0.045 * markerScale, 0.028 * markerScale, 0.022 * markerScale),
|
|
4999
|
+
new THREE11.MeshBasicMaterial({ color, depthTest: false })
|
|
5000
|
+
));
|
|
5001
|
+
const lens = new THREE11.Mesh(
|
|
5002
|
+
new THREE11.BoxGeometry(0.025 * markerScale, 0.018 * markerScale, 0.014 * markerScale),
|
|
5003
|
+
new THREE11.MeshBasicMaterial({ color: aimColor, depthTest: false })
|
|
5004
|
+
);
|
|
5005
|
+
lens.position.set(0, 0, -0.021 * markerScale);
|
|
5006
|
+
markerGroup.add(lens);
|
|
5007
|
+
if (camera.name) markerGroup.add(createCameraLabel(camera.name, color));
|
|
5008
|
+
group.add(markerGroup);
|
|
5009
|
+
const targetMarker = new THREE11.Mesh(
|
|
5010
|
+
new THREE11.SphereGeometry(0.018 * markerScale, 16, 10),
|
|
5011
|
+
new THREE11.MeshBasicMaterial({ color: aimColor, depthTest: false })
|
|
5012
|
+
);
|
|
5013
|
+
targetMarker.position.copy(target);
|
|
5014
|
+
targetMarker.renderOrder = 999;
|
|
5015
|
+
targetMarker.frustumCulled = false;
|
|
5016
|
+
group.add(targetMarker);
|
|
5017
|
+
return group;
|
|
5018
|
+
}
|
|
4720
5019
|
function Debug({
|
|
4721
5020
|
showGeoms = false,
|
|
4722
5021
|
showSites = false,
|
|
4723
5022
|
showJoints = false,
|
|
4724
5023
|
showCameras = false,
|
|
5024
|
+
virtualCameras = [],
|
|
4725
5025
|
showContacts = false,
|
|
4726
5026
|
showCOM = false,
|
|
4727
5027
|
showInertia = false,
|
|
@@ -4738,6 +5038,7 @@ function Debug({
|
|
|
4738
5038
|
const sites = [];
|
|
4739
5039
|
const joints = [];
|
|
4740
5040
|
const cameras = [];
|
|
5041
|
+
const virtualCameraObjects = [];
|
|
4741
5042
|
const comMarkers = [];
|
|
4742
5043
|
if (showGeoms) {
|
|
4743
5044
|
for (let i = 0; i < model.ngeom; i++) {
|
|
@@ -4916,6 +5217,9 @@ function Debug({
|
|
|
4916
5217
|
cameras.push(group);
|
|
4917
5218
|
}
|
|
4918
5219
|
}
|
|
5220
|
+
for (let i = 0; i < virtualCameras.length; i += 1) {
|
|
5221
|
+
virtualCameraObjects.push(createVirtualCameraDebugObject(virtualCameras[i], i));
|
|
5222
|
+
}
|
|
4919
5223
|
if (showCOM) {
|
|
4920
5224
|
for (let i = 1; i < model.nbody; i++) {
|
|
4921
5225
|
const geometry = new THREE11.SphereGeometry(5e-3, 6, 6);
|
|
@@ -4925,8 +5229,8 @@ function Debug({
|
|
|
4925
5229
|
comMarkers.push(mesh);
|
|
4926
5230
|
}
|
|
4927
5231
|
}
|
|
4928
|
-
return { geoms, sites, joints, cameras, comMarkers };
|
|
4929
|
-
}, [status, mjModelRef, showGeoms, showSites, showJoints, showCameras, showCOM]);
|
|
5232
|
+
return { geoms, sites, joints, cameras, virtualCameraObjects, comMarkers };
|
|
5233
|
+
}, [status, mjModelRef, showGeoms, showSites, showJoints, showCameras, virtualCameras, showCOM]);
|
|
4930
5234
|
useEffect(() => {
|
|
4931
5235
|
const group = groupRef.current;
|
|
4932
5236
|
if (!group || !debugGeometry) return;
|
|
@@ -4935,6 +5239,7 @@ function Debug({
|
|
|
4935
5239
|
...debugGeometry.sites,
|
|
4936
5240
|
...debugGeometry.joints,
|
|
4937
5241
|
...debugGeometry.cameras,
|
|
5242
|
+
...debugGeometry.virtualCameraObjects,
|
|
4938
5243
|
...debugGeometry.comMarkers
|
|
4939
5244
|
];
|
|
4940
5245
|
for (const obj of allObjects) group.add(obj);
|