mujoco-react 8.9.2 → 8.11.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 +82 -1
- package/dist/chunk-SEWQULWO.js +400 -0
- package/dist/chunk-SEWQULWO.js.map +1 -0
- package/dist/index.d.ts +114 -744
- package/dist/index.js +329 -35
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +53 -0
- package/dist/spark.js +235 -0
- package/dist/spark.js.map +1 -0
- package/dist/types-BmneHLBM.d.ts +871 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +4 -0
- package/dist/vite.js.map +1 -1
- package/package.json +15 -2
- package/src/components/Body.tsx +3 -1
- package/src/components/VisualScenario.tsx +566 -0
- package/src/core/MujocoCanvas.tsx +8 -1
- package/src/core/SceneLoader.ts +182 -3
- package/src/hooks/useFrameCapture.ts +206 -0
- package/src/hooks/usePolicy.ts +12 -8
- package/src/hooks/useSceneLights.ts +49 -18
- package/src/index.ts +48 -0
- package/src/spark.tsx +336 -0
- package/src/types.ts +159 -3
- package/src/vite.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment } from './chunk-SEWQULWO.js';
|
|
1
2
|
import loadMujoco from '@mujoco/mujoco';
|
|
2
3
|
import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
|
|
3
4
|
import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
|
@@ -6,7 +7,6 @@ import { Canvas, useThree, useFrame } from '@react-three/fiber';
|
|
|
6
7
|
import * as THREE11 from 'three';
|
|
7
8
|
import { PivotControls } from '@react-three/drei';
|
|
8
9
|
|
|
9
|
-
// src/core/MujocoProvider.tsx
|
|
10
10
|
var MujocoContext = createContext({
|
|
11
11
|
mujoco: null,
|
|
12
12
|
status: "loading",
|
|
@@ -776,7 +776,8 @@ function sceneObjectToXml(obj) {
|
|
|
776
776
|
const solref = obj.solref ? ` solref="${obj.solref}"` : "";
|
|
777
777
|
const solimp = obj.solimp ? ` solimp="${obj.solimp}"` : "";
|
|
778
778
|
const condim = obj.condim ? ` condim="${obj.condim}"` : "";
|
|
779
|
-
|
|
779
|
+
const group = obj.group !== void 0 ? ` group="${obj.group}"` : "";
|
|
780
|
+
return `<body name="${obj.name}" pos="${pos}">${joint}<geom type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
|
|
780
781
|
}
|
|
781
782
|
function ensureDir(mujoco, fname) {
|
|
782
783
|
const dirParts = fname.split("/");
|
|
@@ -816,6 +817,20 @@ function normalizeVfsPath(path) {
|
|
|
816
817
|
function localFilePath(file) {
|
|
817
818
|
return normalizeVfsPath(file.webkitRelativePath || file.name);
|
|
818
819
|
}
|
|
820
|
+
function dirname(path) {
|
|
821
|
+
const normalized = normalizeVfsPath(path);
|
|
822
|
+
const idx = normalized.lastIndexOf("/");
|
|
823
|
+
return idx === -1 ? "" : normalized.slice(0, idx + 1);
|
|
824
|
+
}
|
|
825
|
+
function relativeVfsPath(fromDir, targetPath) {
|
|
826
|
+
const from = normalizeVfsPath(fromDir).split("/").filter(Boolean);
|
|
827
|
+
const target = normalizeVfsPath(targetPath).split("/").filter(Boolean);
|
|
828
|
+
while (from.length && target.length && from[0] === target[0]) {
|
|
829
|
+
from.shift();
|
|
830
|
+
target.shift();
|
|
831
|
+
}
|
|
832
|
+
return [...from.map(() => ".."), ...target].join("/") || ".";
|
|
833
|
+
}
|
|
819
834
|
function inferSceneFile(files, options) {
|
|
820
835
|
if (options?.sceneFile) return normalizeVfsPath(options.sceneFile);
|
|
821
836
|
const paths = files.map(localFilePath);
|
|
@@ -834,12 +849,120 @@ function createSceneConfigFromFiles(files, options = {}) {
|
|
|
834
849
|
src: "",
|
|
835
850
|
sceneFile: inferSceneFile(fileArray, options),
|
|
836
851
|
files: fileArray,
|
|
852
|
+
environmentFiles: options.environmentFiles?.map(normalizeVfsPath),
|
|
837
853
|
homeJoints: options.homeJoints,
|
|
838
854
|
xmlPatches: options.xmlPatches,
|
|
839
855
|
sceneObjects: options.sceneObjects,
|
|
840
856
|
onReset: options.onReset
|
|
841
857
|
};
|
|
842
858
|
}
|
|
859
|
+
var ENVIRONMENT_MERGE_SECTIONS = [
|
|
860
|
+
"asset",
|
|
861
|
+
"worldbody",
|
|
862
|
+
"contact",
|
|
863
|
+
"equality",
|
|
864
|
+
"tendon",
|
|
865
|
+
"sensor",
|
|
866
|
+
"keyframe",
|
|
867
|
+
"custom",
|
|
868
|
+
"extension"
|
|
869
|
+
];
|
|
870
|
+
function directChild(parent, tagName) {
|
|
871
|
+
const lower = tagName.toLowerCase();
|
|
872
|
+
for (const child of Array.from(parent.children)) {
|
|
873
|
+
if (child.tagName.toLowerCase() === lower) return child;
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
function ensureTopLevelSection(doc, tagName) {
|
|
878
|
+
const root = doc.documentElement;
|
|
879
|
+
const existing = directChild(root, tagName);
|
|
880
|
+
if (existing) return existing;
|
|
881
|
+
const section = doc.createElement(tagName);
|
|
882
|
+
if (tagName === "asset") {
|
|
883
|
+
const worldbody = directChild(root, "worldbody");
|
|
884
|
+
if (worldbody) root.insertBefore(section, worldbody);
|
|
885
|
+
else root.appendChild(section);
|
|
886
|
+
} else {
|
|
887
|
+
root.appendChild(section);
|
|
888
|
+
}
|
|
889
|
+
return section;
|
|
890
|
+
}
|
|
891
|
+
function readCompilerDirs(doc) {
|
|
892
|
+
const compiler = directChild(doc.documentElement, "compiler");
|
|
893
|
+
const assetDir = compiler?.getAttribute("assetdir") || "";
|
|
894
|
+
return {
|
|
895
|
+
meshDir: compiler?.getAttribute("meshdir") || assetDir,
|
|
896
|
+
textureDir: compiler?.getAttribute("texturedir") || assetDir
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function isExternalPath(path) {
|
|
900
|
+
return /^[a-z]+:\/\//i.test(path) || path.startsWith("package://") || path.startsWith("/");
|
|
901
|
+
}
|
|
902
|
+
function fileReferencePrefix(el, compilerDirs) {
|
|
903
|
+
const tag = el.tagName.toLowerCase();
|
|
904
|
+
if (tag === "mesh") return compilerDirs.meshDir ? compilerDirs.meshDir + "/" : "";
|
|
905
|
+
if (tag === "texture" || tag === "hfield") return compilerDirs.textureDir ? compilerDirs.textureDir + "/" : "";
|
|
906
|
+
return "";
|
|
907
|
+
}
|
|
908
|
+
function rewriteFileReferencesForMerge(node, sourceFile, targetFile, sourceDoc) {
|
|
909
|
+
const sourceDir = dirname(sourceFile);
|
|
910
|
+
const targetDir = dirname(targetFile);
|
|
911
|
+
const compilerDirs = readCompilerDirs(sourceDoc);
|
|
912
|
+
node.querySelectorAll("[file], [filename]").forEach((el) => {
|
|
913
|
+
const attr = el.hasAttribute("file") ? "file" : "filename";
|
|
914
|
+
const value = el.getAttribute(attr);
|
|
915
|
+
if (!value || isExternalPath(value)) return;
|
|
916
|
+
const sourceRelativePath = normalizeVfsPath(fileReferencePrefix(el, compilerDirs) + value);
|
|
917
|
+
const resolvedPath = normalizeVfsPath(sourceDir + sourceRelativePath);
|
|
918
|
+
el.setAttribute(attr, relativeVfsPath(targetDir, resolvedPath));
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
function hasParseError(doc) {
|
|
922
|
+
return doc.getElementsByTagName("parsererror").length > 0;
|
|
923
|
+
}
|
|
924
|
+
function composeEnvironmentXml(sceneXml, config, parser, environmentXmlByPath) {
|
|
925
|
+
const environmentFiles = config.environmentFiles?.map(normalizeVfsPath) ?? [];
|
|
926
|
+
if (!environmentFiles.length) return sceneXml;
|
|
927
|
+
const sceneDoc = parser.parseFromString(sceneXml, "text/xml");
|
|
928
|
+
if (hasParseError(sceneDoc)) {
|
|
929
|
+
console.warn(`Could not compose environments: failed to parse ${config.sceneFile}`);
|
|
930
|
+
return sceneXml;
|
|
931
|
+
}
|
|
932
|
+
for (const environmentFile of environmentFiles) {
|
|
933
|
+
const environmentXml = environmentXmlByPath.get(environmentFile);
|
|
934
|
+
if (!environmentXml) {
|
|
935
|
+
console.warn(`Environment XML not found: ${environmentFile}`);
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
const environmentDoc = parser.parseFromString(environmentXml, "text/xml");
|
|
939
|
+
if (hasParseError(environmentDoc)) {
|
|
940
|
+
console.warn(`Skipping environment XML with parse errors: ${environmentFile}`);
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
for (const sectionName of ENVIRONMENT_MERGE_SECTIONS) {
|
|
944
|
+
const environmentSection = directChild(environmentDoc.documentElement, sectionName);
|
|
945
|
+
if (!environmentSection?.children.length) continue;
|
|
946
|
+
const targetSection = ensureTopLevelSection(sceneDoc, sectionName);
|
|
947
|
+
for (const child of Array.from(environmentSection.children)) {
|
|
948
|
+
const imported = sceneDoc.importNode(child, true);
|
|
949
|
+
rewriteFileReferencesForMerge(imported, environmentFile, config.sceneFile, environmentDoc);
|
|
950
|
+
targetSection.appendChild(imported);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return new XMLSerializer().serializeToString(sceneDoc);
|
|
955
|
+
}
|
|
956
|
+
function findTextByConfiguredPath(textByPath, configuredPath) {
|
|
957
|
+
const normalized = normalizeVfsPath(configuredPath);
|
|
958
|
+
const direct = textByPath.get(normalized);
|
|
959
|
+
if (direct) return direct;
|
|
960
|
+
const suffix = "/" + normalized;
|
|
961
|
+
for (const [path, text] of textByPath) {
|
|
962
|
+
if (path.endsWith(suffix) || path === normalized.split("/").pop()) return text;
|
|
963
|
+
}
|
|
964
|
+
return void 0;
|
|
965
|
+
}
|
|
843
966
|
function applyXmlPatches(text, fname, config) {
|
|
844
967
|
let result = text;
|
|
845
968
|
for (const patch of config.xmlPatches ?? []) {
|
|
@@ -902,10 +1025,21 @@ async function loadSceneFromFiles(mujoco, config, onProgress) {
|
|
|
902
1025
|
if (isModelTextFile(path)) {
|
|
903
1026
|
const text = applyXmlPatches(await file.text(), path, config);
|
|
904
1027
|
textByPath.set(path, text);
|
|
905
|
-
mujoco.FS.writeFile(`/working/${path}`, text);
|
|
906
1028
|
} else {
|
|
907
1029
|
mujoco.FS.writeFile(`/working/${path}`, new Uint8Array(await file.arrayBuffer()));
|
|
1030
|
+
written.add(path);
|
|
908
1031
|
}
|
|
1032
|
+
}
|
|
1033
|
+
const environmentXmlByPath = /* @__PURE__ */ new Map();
|
|
1034
|
+
for (const environmentFile of config.environmentFiles?.map(normalizeVfsPath) ?? []) {
|
|
1035
|
+
const environmentXml = findTextByConfiguredPath(textByPath, environmentFile);
|
|
1036
|
+
if (environmentXml) environmentXmlByPath.set(environmentFile, environmentXml);
|
|
1037
|
+
}
|
|
1038
|
+
for (const [path, text] of textByPath) {
|
|
1039
|
+
const composedText = path === config.sceneFile ? composeEnvironmentXml(text, config, parser, environmentXmlByPath) : text;
|
|
1040
|
+
textByPath.set(path, composedText);
|
|
1041
|
+
ensureDir(mujoco, path);
|
|
1042
|
+
mujoco.FS.writeFile(`/working/${path}`, composedText);
|
|
909
1043
|
written.add(path);
|
|
910
1044
|
}
|
|
911
1045
|
for (const [path, text] of textByPath) {
|
|
@@ -954,6 +1088,17 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
954
1088
|
} catch {
|
|
955
1089
|
}
|
|
956
1090
|
const baseUrl = config.src.endsWith("/") ? config.src : config.src + "/";
|
|
1091
|
+
const environmentXmlByPath = /* @__PURE__ */ new Map();
|
|
1092
|
+
const environmentFiles = config.environmentFiles?.map(normalizeVfsPath) ?? [];
|
|
1093
|
+
for (const environmentFile of environmentFiles) {
|
|
1094
|
+
onProgress?.(`Downloading ${environmentFile}...`);
|
|
1095
|
+
const res = await fetch(baseUrl + environmentFile);
|
|
1096
|
+
if (!res.ok) {
|
|
1097
|
+
console.warn(`Failed to fetch environment XML ${environmentFile}: ${res.status} ${res.statusText}`);
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
environmentXmlByPath.set(environmentFile, applyXmlPatches(await res.text(), environmentFile, config));
|
|
1101
|
+
}
|
|
957
1102
|
const downloaded = /* @__PURE__ */ new Set();
|
|
958
1103
|
const xmlQueue = [config.sceneFile];
|
|
959
1104
|
const assetFiles = [];
|
|
@@ -972,7 +1117,8 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
972
1117
|
console.warn(`Failed to fetch ${fname}: ${res.status} ${res.statusText}`);
|
|
973
1118
|
continue;
|
|
974
1119
|
}
|
|
975
|
-
const
|
|
1120
|
+
const patchedText = applyXmlPatches(await res.text(), fname, config);
|
|
1121
|
+
const text = fname === config.sceneFile ? composeEnvironmentXml(patchedText, config, parser, environmentXmlByPath) : patchedText;
|
|
976
1122
|
ensureDir(mujoco, fname);
|
|
977
1123
|
mujoco.FS.writeFile(`/working/${fname}`, text);
|
|
978
1124
|
scanDependencies(text, fname, parser, downloaded, xmlQueue);
|
|
@@ -2209,6 +2355,7 @@ var MujocoCanvas = forwardRef(
|
|
|
2209
2355
|
paused,
|
|
2210
2356
|
speed,
|
|
2211
2357
|
interpolate,
|
|
2358
|
+
loadingFallback,
|
|
2212
2359
|
children,
|
|
2213
2360
|
...canvasProps
|
|
2214
2361
|
}, ref) {
|
|
@@ -2218,7 +2365,10 @@ var MujocoCanvas = forwardRef(
|
|
|
2218
2365
|
onError(new Error(wasmError ?? "WASM load failed"));
|
|
2219
2366
|
}
|
|
2220
2367
|
}, [wasmStatus, wasmError, onError]);
|
|
2221
|
-
if (wasmStatus === "
|
|
2368
|
+
if (wasmStatus === "loading" || !mujoco) {
|
|
2369
|
+
return loadingFallback ? /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: loadingFallback }) : null;
|
|
2370
|
+
}
|
|
2371
|
+
if (wasmStatus === "error") {
|
|
2222
2372
|
return null;
|
|
2223
2373
|
}
|
|
2224
2374
|
return /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: /* @__PURE__ */ jsx(
|
|
@@ -2860,6 +3010,7 @@ function Body({
|
|
|
2860
3010
|
solref,
|
|
2861
3011
|
solimp,
|
|
2862
3012
|
condim,
|
|
3013
|
+
group,
|
|
2863
3014
|
children
|
|
2864
3015
|
}) {
|
|
2865
3016
|
const { bodyRegistryRef, hiddenBodiesRef, requestBodyReload, mjDataRef, mjModelRef, status } = useMujocoContext();
|
|
@@ -2879,7 +3030,8 @@ function Body({
|
|
|
2879
3030
|
friction,
|
|
2880
3031
|
solref,
|
|
2881
3032
|
solimp,
|
|
2882
|
-
condim
|
|
3033
|
+
condim,
|
|
3034
|
+
group
|
|
2883
3035
|
};
|
|
2884
3036
|
bodyRegistryRef.current.set(name, { definition, hasCustomChildren: hasChildren });
|
|
2885
3037
|
if (hasChildren) {
|
|
@@ -2892,7 +3044,7 @@ function Body({
|
|
|
2892
3044
|
requestBodyReload();
|
|
2893
3045
|
}
|
|
2894
3046
|
};
|
|
2895
|
-
}, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
|
|
3047
|
+
}, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, group, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
|
|
2896
3048
|
useEffect(() => {
|
|
2897
3049
|
if (status !== "ready") return;
|
|
2898
3050
|
const model = mjModelRef.current;
|
|
@@ -2904,12 +3056,12 @@ function Body({
|
|
|
2904
3056
|
if (!hasChildren) return;
|
|
2905
3057
|
const data = mjDataRef.current;
|
|
2906
3058
|
const id = bodyIdRef.current;
|
|
2907
|
-
const
|
|
2908
|
-
if (!data || id < 0 || !
|
|
3059
|
+
const group2 = groupRef.current;
|
|
3060
|
+
if (!data || id < 0 || !group2) return;
|
|
2909
3061
|
const i3 = id * 3;
|
|
2910
3062
|
const i4 = id * 4;
|
|
2911
|
-
|
|
2912
|
-
|
|
3063
|
+
group2.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
|
|
3064
|
+
group2.quaternion.set(
|
|
2913
3065
|
data.xquat[i4 + 1],
|
|
2914
3066
|
data.xquat[i4 + 2],
|
|
2915
3067
|
data.xquat[i4 + 3],
|
|
@@ -3242,24 +3394,35 @@ function useSceneLights(intensity = 1) {
|
|
|
3242
3394
|
targetsRef.current = [];
|
|
3243
3395
|
const nlight = model.nlight ?? 0;
|
|
3244
3396
|
if (nlight === 0) return;
|
|
3397
|
+
const lightActive = getModelArray(model, "light_active");
|
|
3398
|
+
const lightTypeArray = getModelArray(model, "light_type");
|
|
3399
|
+
const lightCastShadow = getModelArray(model, "light_castshadow");
|
|
3400
|
+
const lightIntensity = getModelArray(model, "light_intensity");
|
|
3401
|
+
const lightDiffuse = getModelArray(model, "light_diffuse");
|
|
3402
|
+
const lightPos = getModelArray(model, "light_pos");
|
|
3403
|
+
const lightDir = getModelArray(model, "light_dir");
|
|
3404
|
+
const lightCutoff = getModelArray(model, "light_cutoff");
|
|
3405
|
+
const lightExponent = getModelArray(model, "light_exponent");
|
|
3406
|
+
const lightAttenuation = getModelArray(model, "light_attenuation");
|
|
3407
|
+
if (!lightPos || !lightDir) return;
|
|
3245
3408
|
for (let i = 0; i < nlight; i++) {
|
|
3246
|
-
const active =
|
|
3409
|
+
const active = lightActive ? lightActive[i] : 1;
|
|
3247
3410
|
if (!active) continue;
|
|
3248
|
-
const lightType =
|
|
3411
|
+
const lightType = lightTypeArray ? lightTypeArray[i] : 0;
|
|
3249
3412
|
const isDirectional = lightType === 0;
|
|
3250
|
-
const castShadow =
|
|
3251
|
-
const mjIntensity =
|
|
3413
|
+
const castShadow = lightCastShadow ? lightCastShadow[i] !== 0 : false;
|
|
3414
|
+
const mjIntensity = lightIntensity ? lightIntensity[i] : 1;
|
|
3252
3415
|
const finalIntensity = intensity * mjIntensity;
|
|
3253
|
-
const dr =
|
|
3254
|
-
const dg =
|
|
3255
|
-
const db =
|
|
3416
|
+
const dr = lightDiffuse ? lightDiffuse[3 * i] : 1;
|
|
3417
|
+
const dg = lightDiffuse ? lightDiffuse[3 * i + 1] : 1;
|
|
3418
|
+
const db = lightDiffuse ? lightDiffuse[3 * i + 2] : 1;
|
|
3256
3419
|
const color = new THREE11.Color(dr, dg, db);
|
|
3257
|
-
const px =
|
|
3258
|
-
const py =
|
|
3259
|
-
const pz =
|
|
3260
|
-
const dx =
|
|
3261
|
-
const dy =
|
|
3262
|
-
const dz =
|
|
3420
|
+
const px = lightPos[3 * i];
|
|
3421
|
+
const py = lightPos[3 * i + 1];
|
|
3422
|
+
const pz = lightPos[3 * i + 2];
|
|
3423
|
+
const dx = lightDir[3 * i];
|
|
3424
|
+
const dy = lightDir[3 * i + 1];
|
|
3425
|
+
const dz = lightDir[3 * i + 2];
|
|
3263
3426
|
if (isDirectional) {
|
|
3264
3427
|
const light = new THREE11.DirectionalLight(color, finalIntensity);
|
|
3265
3428
|
light.position.set(px, py, pz);
|
|
@@ -3281,16 +3444,16 @@ function useSceneLights(intensity = 1) {
|
|
|
3281
3444
|
lightsRef.current.push(light);
|
|
3282
3445
|
targetsRef.current.push(light.target);
|
|
3283
3446
|
} else {
|
|
3284
|
-
const cutoff =
|
|
3285
|
-
const exponent =
|
|
3447
|
+
const cutoff = lightCutoff ? lightCutoff[i] : 45;
|
|
3448
|
+
const exponent = lightExponent ? lightExponent[i] : 10;
|
|
3286
3449
|
const angle = cutoff * Math.PI / 180;
|
|
3287
3450
|
const light = new THREE11.SpotLight(color, finalIntensity, 0, angle, exponent / 128);
|
|
3288
3451
|
light.position.set(px, py, pz);
|
|
3289
3452
|
light.target.position.set(px + dx, py + dy, pz + dz);
|
|
3290
3453
|
light.castShadow = castShadow;
|
|
3291
|
-
if (
|
|
3292
|
-
const att1 =
|
|
3293
|
-
const att2 =
|
|
3454
|
+
if (lightAttenuation) {
|
|
3455
|
+
const att1 = lightAttenuation[3 * i + 1];
|
|
3456
|
+
const att2 = lightAttenuation[3 * i + 2];
|
|
3294
3457
|
light.decay = att2 > 0 ? 2 : att1 > 0 ? 1 : 0;
|
|
3295
3458
|
light.distance = att1 > 0 ? 1 / att1 : 0;
|
|
3296
3459
|
}
|
|
@@ -3315,6 +3478,17 @@ function useSceneLights(intensity = 1) {
|
|
|
3315
3478
|
};
|
|
3316
3479
|
}, [status, mjModelRef, scene, intensity]);
|
|
3317
3480
|
}
|
|
3481
|
+
function getModelArray(model, key) {
|
|
3482
|
+
try {
|
|
3483
|
+
const value = model[key];
|
|
3484
|
+
return isArrayLikeNumber(value) ? value : void 0;
|
|
3485
|
+
} catch {
|
|
3486
|
+
return void 0;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
function isArrayLikeNumber(value) {
|
|
3490
|
+
return typeof value === "object" && value !== null && "length" in value && typeof value.length === "number";
|
|
3491
|
+
}
|
|
3318
3492
|
|
|
3319
3493
|
// src/components/SceneLights.tsx
|
|
3320
3494
|
function SceneLights({ intensity = 1 }) {
|
|
@@ -4562,22 +4736,25 @@ function useKeyboardTeleop(config) {
|
|
|
4562
4736
|
});
|
|
4563
4737
|
}
|
|
4564
4738
|
function usePolicy(config) {
|
|
4565
|
-
const { mjModelRef } = useMujocoContext();
|
|
4566
4739
|
const lastActionTimeRef = useRef(0);
|
|
4740
|
+
const lastObservationRef = useRef(null);
|
|
4567
4741
|
const lastActionRef = useRef(null);
|
|
4568
|
-
const isRunningRef = useRef(true);
|
|
4742
|
+
const isRunningRef = useRef(config.enabled ?? true);
|
|
4569
4743
|
const configRef = useRef(config);
|
|
4570
4744
|
configRef.current = config;
|
|
4745
|
+
isRunningRef.current = config.enabled ?? isRunningRef.current;
|
|
4571
4746
|
useBeforePhysicsStep((model, data) => {
|
|
4572
4747
|
if (!isRunningRef.current) return;
|
|
4573
4748
|
const cfg = configRef.current;
|
|
4574
4749
|
model.opt?.timestep ?? 2e-3;
|
|
4575
4750
|
const interval = 1 / cfg.frequency;
|
|
4576
4751
|
if (data.time - lastActionTimeRef.current >= interval) {
|
|
4577
|
-
const
|
|
4578
|
-
cfg.
|
|
4752
|
+
const observation = cfg.onObservation({ model, data });
|
|
4753
|
+
const action = cfg.infer ? cfg.infer({ observation, model, data }) : observation;
|
|
4754
|
+
cfg.onAction({ action, observation, model, data });
|
|
4579
4755
|
lastActionTimeRef.current = data.time;
|
|
4580
|
-
|
|
4756
|
+
lastObservationRef.current = observation;
|
|
4757
|
+
lastActionRef.current = action;
|
|
4581
4758
|
}
|
|
4582
4759
|
});
|
|
4583
4760
|
return {
|
|
@@ -4591,6 +4768,9 @@ function usePolicy(config) {
|
|
|
4591
4768
|
isRunningRef.current = false;
|
|
4592
4769
|
},
|
|
4593
4770
|
get lastObservation() {
|
|
4771
|
+
return lastObservationRef.current;
|
|
4772
|
+
},
|
|
4773
|
+
get lastAction() {
|
|
4594
4774
|
return lastActionRef.current;
|
|
4595
4775
|
}
|
|
4596
4776
|
};
|
|
@@ -4787,6 +4967,114 @@ function useVideoRecorder(options = {}) {
|
|
|
4787
4967
|
}
|
|
4788
4968
|
};
|
|
4789
4969
|
}
|
|
4970
|
+
function isTargetRef(target) {
|
|
4971
|
+
return Boolean(target && typeof target === "object" && "current" in target);
|
|
4972
|
+
}
|
|
4973
|
+
function resolveCanvasTarget(target) {
|
|
4974
|
+
const resolvedTarget = isTargetRef(target) ? target.current : target;
|
|
4975
|
+
if (!resolvedTarget) {
|
|
4976
|
+
throw new Error("No frame capture target is available.");
|
|
4977
|
+
}
|
|
4978
|
+
if (resolvedTarget instanceof HTMLCanvasElement) {
|
|
4979
|
+
return resolvedTarget;
|
|
4980
|
+
}
|
|
4981
|
+
const canvas = resolvedTarget.querySelector("canvas");
|
|
4982
|
+
if (!canvas) {
|
|
4983
|
+
throw new Error("Frame capture target does not contain a canvas.");
|
|
4984
|
+
}
|
|
4985
|
+
return canvas;
|
|
4986
|
+
}
|
|
4987
|
+
function waitForNextAnimationFrame() {
|
|
4988
|
+
return new Promise((resolve) => {
|
|
4989
|
+
requestAnimationFrame(() => resolve());
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
async function captureFrame(options) {
|
|
4993
|
+
const type = options.type ?? "image/png";
|
|
4994
|
+
const canvas = resolveCanvasTarget(options.target);
|
|
4995
|
+
if (options.waitForAnimationFrame ?? true) {
|
|
4996
|
+
await waitForNextAnimationFrame();
|
|
4997
|
+
}
|
|
4998
|
+
return {
|
|
4999
|
+
canvas,
|
|
5000
|
+
dataUrl: canvas.toDataURL(type, options.quality),
|
|
5001
|
+
type
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
async function captureFrameBlob(options) {
|
|
5005
|
+
const type = options.type ?? "image/png";
|
|
5006
|
+
const canvas = resolveCanvasTarget(options.target);
|
|
5007
|
+
if (options.waitForAnimationFrame ?? true) {
|
|
5008
|
+
await waitForNextAnimationFrame();
|
|
5009
|
+
}
|
|
5010
|
+
const blob = await new Promise((resolve, reject) => {
|
|
5011
|
+
canvas.toBlob(
|
|
5012
|
+
(nextBlob) => {
|
|
5013
|
+
if (nextBlob) {
|
|
5014
|
+
resolve(nextBlob);
|
|
5015
|
+
} else {
|
|
5016
|
+
reject(new Error("Canvas frame capture did not produce a Blob."));
|
|
5017
|
+
}
|
|
5018
|
+
},
|
|
5019
|
+
type,
|
|
5020
|
+
options.quality
|
|
5021
|
+
);
|
|
5022
|
+
});
|
|
5023
|
+
return { canvas, blob, type };
|
|
5024
|
+
}
|
|
5025
|
+
function useFrameCapture(defaultOptions = {}) {
|
|
5026
|
+
const [status, setStatus] = useState("idle");
|
|
5027
|
+
const [error, setError] = useState(null);
|
|
5028
|
+
const reset = useCallback(() => {
|
|
5029
|
+
setStatus("idle");
|
|
5030
|
+
setError(null);
|
|
5031
|
+
}, []);
|
|
5032
|
+
const capture = useCallback(
|
|
5033
|
+
async (options = {}) => {
|
|
5034
|
+
setStatus("capturing");
|
|
5035
|
+
setError(null);
|
|
5036
|
+
try {
|
|
5037
|
+
const result = await captureFrame({ ...defaultOptions, ...options });
|
|
5038
|
+
setStatus("captured");
|
|
5039
|
+
return result;
|
|
5040
|
+
} catch (nextError) {
|
|
5041
|
+
const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
|
|
5042
|
+
setError(error2);
|
|
5043
|
+
setStatus("error");
|
|
5044
|
+
throw error2;
|
|
5045
|
+
}
|
|
5046
|
+
},
|
|
5047
|
+
[defaultOptions]
|
|
5048
|
+
);
|
|
5049
|
+
const captureBlob = useCallback(
|
|
5050
|
+
async (options = {}) => {
|
|
5051
|
+
setStatus("capturing");
|
|
5052
|
+
setError(null);
|
|
5053
|
+
try {
|
|
5054
|
+
const result = await captureFrameBlob({
|
|
5055
|
+
...defaultOptions,
|
|
5056
|
+
...options
|
|
5057
|
+
});
|
|
5058
|
+
setStatus("captured");
|
|
5059
|
+
return result;
|
|
5060
|
+
} catch (nextError) {
|
|
5061
|
+
const error2 = nextError instanceof Error ? nextError : new Error("Unable to capture the current canvas frame.");
|
|
5062
|
+
setError(error2);
|
|
5063
|
+
setStatus("error");
|
|
5064
|
+
throw error2;
|
|
5065
|
+
}
|
|
5066
|
+
},
|
|
5067
|
+
[defaultOptions]
|
|
5068
|
+
);
|
|
5069
|
+
return {
|
|
5070
|
+
status,
|
|
5071
|
+
error,
|
|
5072
|
+
isCapturing: status === "capturing",
|
|
5073
|
+
capture,
|
|
5074
|
+
captureBlob,
|
|
5075
|
+
reset
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
4790
5078
|
function useCtrlNoise(config = {}) {
|
|
4791
5079
|
const { mjModelRef } = useMujocoContext();
|
|
4792
5080
|
const configRef = useRef(config);
|
|
@@ -5072,6 +5360,12 @@ function useCameraAnimation() {
|
|
|
5072
5360
|
*
|
|
5073
5361
|
* useVideoRecorder — canvas video recording hook (spec 13.3)
|
|
5074
5362
|
*/
|
|
5363
|
+
/**
|
|
5364
|
+
* @license
|
|
5365
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5366
|
+
*
|
|
5367
|
+
* useFrameCapture — still-frame capture for canvas-backed MuJoCo/R3F scenes.
|
|
5368
|
+
*/
|
|
5075
5369
|
/**
|
|
5076
5370
|
* @license
|
|
5077
5371
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -5102,6 +5396,6 @@ function useCameraAnimation() {
|
|
|
5102
5396
|
* useCameraAnimation — composable camera animation hook.
|
|
5103
5397
|
*/
|
|
5104
5398
|
|
|
5105
|
-
export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, RobotActuators, RobotBodies, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, SceneLights, TendonRenderer, TrajectoryPlayer, buildObservation, createContiguousControlGroup, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getContact, getControlMap, getName, loadScene, registerRobotResources, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
|
|
5399
|
+
export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, InstancedGeomRenderer, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, RobotActuators, RobotBodies, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, SceneLights, TendonRenderer, TrajectoryPlayer, buildObservation, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getContact, getControlMap, getName, loadScene, registerRobotResources, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
|
|
5106
5400
|
//# sourceMappingURL=index.js.map
|
|
5107
5401
|
//# sourceMappingURL=index.js.map
|