mujoco-react 8.0.0 → 8.1.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 +77 -64
- package/dist/index.d.ts +27 -7
- package/dist/index.js +125 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/TrajectoryPlayer.tsx +16 -2
- package/src/hooks/useTrajectoryPlayer.ts +152 -29
- package/src/index.ts +2 -0
- package/src/types.ts +9 -1
package/dist/index.js
CHANGED
|
@@ -956,7 +956,7 @@ function MujocoSimProvider({
|
|
|
956
956
|
const arr = values instanceof Float64Array ? values : new Float64Array(values);
|
|
957
957
|
data.qvel.set(arr.subarray(0, Math.min(arr.length, mjModelRef.current?.nv ?? 0)));
|
|
958
958
|
}, []);
|
|
959
|
-
const
|
|
959
|
+
const getQpos2 = useCallback(() => {
|
|
960
960
|
return mjDataRef.current ? new Float64Array(mjDataRef.current.qpos) : new Float64Array(0);
|
|
961
961
|
}, []);
|
|
962
962
|
const getQvel = useCallback(() => {
|
|
@@ -976,7 +976,7 @@ function MujocoSimProvider({
|
|
|
976
976
|
}
|
|
977
977
|
}
|
|
978
978
|
}, []);
|
|
979
|
-
const
|
|
979
|
+
const getCtrl2 = useCallback(() => {
|
|
980
980
|
return mjDataRef.current ? new Float64Array(mjDataRef.current.ctrl) : new Float64Array(0);
|
|
981
981
|
}, []);
|
|
982
982
|
const applyForce = useCallback((bodyName, force, point) => {
|
|
@@ -1384,10 +1384,10 @@ function MujocoSimProvider({
|
|
|
1384
1384
|
restoreState,
|
|
1385
1385
|
setQpos,
|
|
1386
1386
|
setQvel,
|
|
1387
|
-
getQpos,
|
|
1387
|
+
getQpos: getQpos2,
|
|
1388
1388
|
getQvel,
|
|
1389
1389
|
setCtrl,
|
|
1390
|
-
getCtrl,
|
|
1390
|
+
getCtrl: getCtrl2,
|
|
1391
1391
|
applyForce,
|
|
1392
1392
|
applyTorque: applyTorqueApi,
|
|
1393
1393
|
setExternalForce,
|
|
@@ -1430,10 +1430,10 @@ function MujocoSimProvider({
|
|
|
1430
1430
|
restoreState,
|
|
1431
1431
|
setQpos,
|
|
1432
1432
|
setQvel,
|
|
1433
|
-
|
|
1433
|
+
getQpos2,
|
|
1434
1434
|
getQvel,
|
|
1435
1435
|
setCtrl,
|
|
1436
|
-
|
|
1436
|
+
getCtrl2,
|
|
1437
1437
|
applyForce,
|
|
1438
1438
|
applyTorqueApi,
|
|
1439
1439
|
setExternalForce,
|
|
@@ -3098,60 +3098,133 @@ function ContactListener({
|
|
|
3098
3098
|
});
|
|
3099
3099
|
return null;
|
|
3100
3100
|
}
|
|
3101
|
+
function getQpos(input, idx) {
|
|
3102
|
+
const item = input[idx];
|
|
3103
|
+
if (!item) return null;
|
|
3104
|
+
if (Array.isArray(item)) return item;
|
|
3105
|
+
return item.qpos;
|
|
3106
|
+
}
|
|
3107
|
+
function getCtrl(input, idx) {
|
|
3108
|
+
const item = input[idx];
|
|
3109
|
+
if (!item || Array.isArray(item)) return null;
|
|
3110
|
+
return item.ctrl ?? null;
|
|
3111
|
+
}
|
|
3101
3112
|
function useTrajectoryPlayer(trajectory, options = {}) {
|
|
3102
3113
|
const { mjModelRef, mjDataRef, mujocoRef, pausedRef } = useMujocoContext();
|
|
3103
|
-
const
|
|
3104
|
-
|
|
3105
|
-
const
|
|
3114
|
+
const optionsRef = useRef(options);
|
|
3115
|
+
optionsRef.current = options;
|
|
3116
|
+
const stateRef = useRef("idle");
|
|
3106
3117
|
const frameRef = useRef(0);
|
|
3107
3118
|
const lastFrameTimeRef = useRef(0);
|
|
3119
|
+
const speedRef = useRef(options.speed ?? 1);
|
|
3120
|
+
const wasPausedRef = useRef(false);
|
|
3121
|
+
const trajectoryRef = useRef(trajectory);
|
|
3122
|
+
trajectoryRef.current = trajectory;
|
|
3123
|
+
const setState = useCallback((next) => {
|
|
3124
|
+
if (stateRef.current === next) return;
|
|
3125
|
+
stateRef.current = next;
|
|
3126
|
+
optionsRef.current.onStateChange?.(next);
|
|
3127
|
+
}, []);
|
|
3108
3128
|
const play = useCallback(() => {
|
|
3109
|
-
|
|
3110
|
-
|
|
3129
|
+
const traj = trajectoryRef.current;
|
|
3130
|
+
if (traj.length === 0) return;
|
|
3131
|
+
const mode = optionsRef.current.mode ?? "kinematic";
|
|
3132
|
+
if (stateRef.current === "completed") {
|
|
3133
|
+
frameRef.current = 0;
|
|
3134
|
+
}
|
|
3135
|
+
if (mode === "kinematic") {
|
|
3136
|
+
wasPausedRef.current = pausedRef.current;
|
|
3137
|
+
pausedRef.current = true;
|
|
3138
|
+
}
|
|
3111
3139
|
lastFrameTimeRef.current = performance.now();
|
|
3112
|
-
|
|
3140
|
+
setState("playing");
|
|
3141
|
+
}, [pausedRef, setState]);
|
|
3113
3142
|
const pause = useCallback(() => {
|
|
3114
|
-
|
|
3115
|
-
|
|
3143
|
+
if (stateRef.current !== "playing") return;
|
|
3144
|
+
setState("paused");
|
|
3145
|
+
}, [setState]);
|
|
3116
3146
|
const seek = useCallback((frameIdx) => {
|
|
3117
|
-
|
|
3147
|
+
const traj = trajectoryRef.current;
|
|
3148
|
+
if (traj.length === 0) return;
|
|
3149
|
+
frameRef.current = Math.max(0, Math.min(frameIdx, traj.length - 1));
|
|
3118
3150
|
const model = mjModelRef.current;
|
|
3119
3151
|
const data = mjDataRef.current;
|
|
3120
|
-
if (!model || !data
|
|
3121
|
-
const qpos =
|
|
3152
|
+
if (!model || !data) return;
|
|
3153
|
+
const qpos = getQpos(traj, frameRef.current);
|
|
3154
|
+
if (!qpos) return;
|
|
3122
3155
|
for (let i = 0; i < Math.min(qpos.length, model.nq); i++) {
|
|
3123
3156
|
data.qpos[i] = qpos[i];
|
|
3124
3157
|
}
|
|
3125
3158
|
mujocoRef.current.mj_forward(model, data);
|
|
3126
|
-
}, [
|
|
3159
|
+
}, [mjModelRef, mjDataRef, mujocoRef]);
|
|
3127
3160
|
const reset = useCallback(() => {
|
|
3161
|
+
const mode = optionsRef.current.mode ?? "kinematic";
|
|
3162
|
+
if (mode === "kinematic" && stateRef.current !== "idle") {
|
|
3163
|
+
pausedRef.current = wasPausedRef.current;
|
|
3164
|
+
}
|
|
3128
3165
|
frameRef.current = 0;
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3166
|
+
setState("idle");
|
|
3167
|
+
}, [pausedRef, setState]);
|
|
3168
|
+
const setSpeed = useCallback((s) => {
|
|
3169
|
+
speedRef.current = s;
|
|
3170
|
+
}, []);
|
|
3171
|
+
const complete = useCallback(() => {
|
|
3172
|
+
const mode = optionsRef.current.mode ?? "kinematic";
|
|
3173
|
+
if (mode === "kinematic") {
|
|
3174
|
+
pausedRef.current = wasPausedRef.current;
|
|
3175
|
+
}
|
|
3176
|
+
setState("completed");
|
|
3177
|
+
optionsRef.current.onComplete?.();
|
|
3178
|
+
}, [pausedRef, setState]);
|
|
3132
3179
|
useFrame(() => {
|
|
3133
|
-
if (
|
|
3180
|
+
if (stateRef.current !== "playing") return;
|
|
3181
|
+
if ((optionsRef.current.mode ?? "kinematic") !== "kinematic") return;
|
|
3182
|
+
const traj = trajectoryRef.current;
|
|
3183
|
+
if (traj.length === 0) return;
|
|
3134
3184
|
const now = performance.now();
|
|
3185
|
+
const fps = optionsRef.current.fps ?? 30;
|
|
3186
|
+
const frameInterval = 1e3 / (fps * speedRef.current);
|
|
3135
3187
|
const elapsed = now - lastFrameTimeRef.current;
|
|
3136
|
-
const frameInterval = 1e3 / fps;
|
|
3137
3188
|
if (elapsed < frameInterval) return;
|
|
3138
3189
|
lastFrameTimeRef.current = now;
|
|
3139
3190
|
const model = mjModelRef.current;
|
|
3140
3191
|
const data = mjDataRef.current;
|
|
3141
3192
|
if (!model || !data) return;
|
|
3142
|
-
const qpos =
|
|
3193
|
+
const qpos = getQpos(traj, frameRef.current);
|
|
3143
3194
|
if (!qpos) return;
|
|
3144
3195
|
for (let i = 0; i < Math.min(qpos.length, model.nq); i++) {
|
|
3145
3196
|
data.qpos[i] = qpos[i];
|
|
3146
3197
|
}
|
|
3147
3198
|
mujocoRef.current.mj_forward(model, data);
|
|
3148
3199
|
frameRef.current++;
|
|
3149
|
-
if (frameRef.current >=
|
|
3150
|
-
if (loop) {
|
|
3200
|
+
if (frameRef.current >= traj.length) {
|
|
3201
|
+
if (optionsRef.current.loop) {
|
|
3202
|
+
frameRef.current = 0;
|
|
3203
|
+
} else {
|
|
3204
|
+
complete();
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
useBeforePhysicsStep((model, data) => {
|
|
3209
|
+
if (stateRef.current !== "playing") return;
|
|
3210
|
+
if ((optionsRef.current.mode ?? "kinematic") !== "physics") return;
|
|
3211
|
+
const traj = trajectoryRef.current;
|
|
3212
|
+
if (traj.length === 0) return;
|
|
3213
|
+
const fps = optionsRef.current.fps ?? 30;
|
|
3214
|
+
const targetFrame = Math.floor(data.time * fps * speedRef.current);
|
|
3215
|
+
frameRef.current = Math.min(targetFrame, traj.length - 1);
|
|
3216
|
+
const ctrl = getCtrl(traj, frameRef.current);
|
|
3217
|
+
if (ctrl) {
|
|
3218
|
+
for (let i = 0; i < Math.min(ctrl.length, model.nu); i++) {
|
|
3219
|
+
data.ctrl[i] = ctrl[i];
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
if (frameRef.current >= traj.length - 1) {
|
|
3223
|
+
if (optionsRef.current.loop) {
|
|
3224
|
+
data.time = 0;
|
|
3151
3225
|
frameRef.current = 0;
|
|
3152
3226
|
} else {
|
|
3153
|
-
|
|
3154
|
-
pausedRef.current = false;
|
|
3227
|
+
complete();
|
|
3155
3228
|
}
|
|
3156
3229
|
}
|
|
3157
3230
|
});
|
|
@@ -3160,14 +3233,21 @@ function useTrajectoryPlayer(trajectory, options = {}) {
|
|
|
3160
3233
|
pause,
|
|
3161
3234
|
seek,
|
|
3162
3235
|
reset,
|
|
3236
|
+
setSpeed,
|
|
3237
|
+
get state() {
|
|
3238
|
+
return stateRef.current;
|
|
3239
|
+
},
|
|
3163
3240
|
get frame() {
|
|
3164
3241
|
return frameRef.current;
|
|
3165
3242
|
},
|
|
3166
3243
|
get playing() {
|
|
3167
|
-
return
|
|
3244
|
+
return stateRef.current === "playing";
|
|
3168
3245
|
},
|
|
3169
3246
|
get totalFrames() {
|
|
3170
3247
|
return trajectory.length;
|
|
3248
|
+
},
|
|
3249
|
+
get progress() {
|
|
3250
|
+
return trajectory.length > 1 ? frameRef.current / (trajectory.length - 1) : 0;
|
|
3171
3251
|
}
|
|
3172
3252
|
};
|
|
3173
3253
|
}
|
|
@@ -3176,14 +3256,28 @@ function useTrajectoryPlayer(trajectory, options = {}) {
|
|
|
3176
3256
|
function TrajectoryPlayer({
|
|
3177
3257
|
trajectory,
|
|
3178
3258
|
fps = 30,
|
|
3259
|
+
speed = 1,
|
|
3179
3260
|
loop = false,
|
|
3180
3261
|
playing = false,
|
|
3181
|
-
|
|
3262
|
+
mode = "kinematic",
|
|
3263
|
+
onFrame,
|
|
3264
|
+
onComplete,
|
|
3265
|
+
onStateChange
|
|
3182
3266
|
}) {
|
|
3183
|
-
const player = useTrajectoryPlayer(trajectory, {
|
|
3267
|
+
const player = useTrajectoryPlayer(trajectory, {
|
|
3268
|
+
fps,
|
|
3269
|
+
speed,
|
|
3270
|
+
loop,
|
|
3271
|
+
mode,
|
|
3272
|
+
onComplete,
|
|
3273
|
+
onStateChange
|
|
3274
|
+
});
|
|
3184
3275
|
const onFrameRef = useRef(onFrame);
|
|
3185
3276
|
onFrameRef.current = onFrame;
|
|
3186
3277
|
const lastReportedFrameRef = useRef(-1);
|
|
3278
|
+
useEffect(() => {
|
|
3279
|
+
player.setSpeed(speed);
|
|
3280
|
+
}, [speed, player]);
|
|
3187
3281
|
useEffect(() => {
|
|
3188
3282
|
if (playing) {
|
|
3189
3283
|
player.play();
|