Spaces:
Running
Running
File size: 7,828 Bytes
72f0edb 6bc7874 72f0edb 6bc7874 72f0edb 6bc7874 72f0edb 6bc7874 72f0edb 6bc7874 72f0edb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
import { MathUtils } from "three";
import { RobotAnimationConfig } from "@/lib/types";
// Define the interface for the Urdf viewer element
export interface UrdfViewerElement extends HTMLElement {
setJointValue: (joint: string, value: number) => void;
}
/**
* Generalized animation function for any robot
* @param viewer The Urdf viewer element
* @param config Configuration for the robot's joint animations
* @returns A cleanup function to cancel the animation
*/
export function animateRobot(
viewer: UrdfViewerElement,
config: RobotAnimationConfig
): () => void {
let animationFrameId: number | null = null;
let isRunning = true;
const speedMultiplier = config.speedMultiplier || 1;
const animate = () => {
if (!isRunning) return;
const time = Date.now() / 300; // Base time unit
try {
// Process each joint configuration
for (const joint of config.joints) {
// Calculate the animation ratio (0-1) based on the animation type
let ratio = 0;
const adjustedTime =
time * joint.speed * speedMultiplier + joint.offset;
switch (joint.type) {
case "sine":
// Sine wave oscillation mapped to 0-1
ratio = (Math.sin(adjustedTime) + 1) / 2;
break;
case "linear":
// Saw tooth pattern (0 to 1 repeated)
ratio = (adjustedTime % (2 * Math.PI)) / (2 * Math.PI);
break;
case "constant":
// Constant value (using max)
ratio = 1;
break;
default:
// Use custom easing if provided
if (joint.customEasing) {
ratio = joint.customEasing(adjustedTime);
}
}
// Calculate the joint value based on min/max and the ratio
let value = MathUtils.lerp(joint.min, joint.max, ratio);
// Convert from degrees to radians if specified
if (joint.isDegrees) {
value = (value * Math.PI) / 180;
}
// Set the joint value, catching errors for non-existent joints
try {
viewer.setJointValue(joint.name, value);
} catch (e) {
// Silently ignore if the joint doesn't exist
}
}
} catch (err) {
console.error("Error in robot animation:", err);
}
// Continue the animation loop
animationFrameId = requestAnimationFrame(animate);
};
// Start the animation
animationFrameId = requestAnimationFrame(animate);
// Return cleanup function
return () => {
isRunning = false;
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
};
}
/**
* Animates a hexapod robot (like T12) with walking motion
* @param viewer The Urdf viewer element
* @returns A cleanup function to cancel the animation
*/
export function animateHexapodRobot(viewer: UrdfViewerElement): () => void {
let animationFrameId: number | null = null;
let isRunning = true;
const animate = () => {
// Don't continue animation if we've been told to stop
if (!isRunning) return;
// Animate the legs (for T12 robot)
const time = Date.now() / 3e2;
try {
for (let i = 1; i <= 6; i++) {
const offset = (i * Math.PI) / 3;
const ratio = Math.max(0, Math.sin(time + offset));
// For a hexapod robot like T12
if (typeof viewer.setJointValue === "function") {
// Hip joints
viewer.setJointValue(
`HP${i}`,
(MathUtils.lerp(30, 0, ratio) * Math.PI) / 180
);
// Knee joints
viewer.setJointValue(
`KP${i}`,
(MathUtils.lerp(90, 150, ratio) * Math.PI) / 180
);
// Ankle joints
viewer.setJointValue(
`AP${i}`,
(MathUtils.lerp(-30, -60, ratio) * Math.PI) / 180
);
// Check if these joints exist before setting values
try {
// Tire/Contact joints
viewer.setJointValue(`TC${i}A`, MathUtils.lerp(0, 0.065, ratio));
viewer.setJointValue(`TC${i}B`, MathUtils.lerp(0, 0.065, ratio));
// Wheel rotation
viewer.setJointValue(`W${i}`, performance.now() * 0.001);
} catch (e) {
// Silently ignore if those joints don't exist
}
}
}
} catch (err) {
console.error("Error in animation:", err);
}
// Continue the animation loop
animationFrameId = requestAnimationFrame(animate);
};
// Start the animation
animationFrameId = requestAnimationFrame(animate);
// Return cleanup function
return () => {
// Mark animation as stopped but DO NOT reset joint positions
isRunning = false;
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
};
}
// Example: Walking animation for Cassie robot
export const cassieWalkingConfig: RobotAnimationConfig = {
speedMultiplier: 0.5, // Adjust overall speed
joints: [
// Left leg
{
name: "hip_abduction_left",
type: "sine",
min: -0.1, // Small side-to-side movement
max: 0.1,
speed: 1,
offset: 0,
isDegrees: false, // Already in radians
},
{
name: "hip_rotation_left", // Assuming this joint exists
type: "sine",
min: -0.2,
max: 0.2,
speed: 1,
offset: Math.PI / 2, // 90 degrees out of phase
isDegrees: false,
},
{
name: "hip_flexion_left", // Assuming this joint exists
type: "sine",
min: -0.3,
max: 0.6,
speed: 1,
offset: 0,
isDegrees: false,
},
{
name: "knee_joint_left", // Assuming this joint exists
type: "sine",
min: 0.2,
max: 1.4,
speed: 1,
offset: Math.PI / 2, // 90 degrees phase shifted from hip
isDegrees: false,
},
{
name: "ankle_joint_left", // Assuming this joint exists
type: "sine",
min: -0.4,
max: 0.1,
speed: 1,
offset: Math.PI, // 180 degrees out of phase with hip
isDegrees: false,
},
{
name: "toe_joint_left", // Assuming this joint exists
type: "sine",
min: -0.2,
max: 0.2,
speed: 1,
offset: Math.PI * 1.5, // 270 degrees phase
isDegrees: false,
},
// Right leg (with appropriate phase shift to alternate with left leg)
{
name: "hip_abduction_right", // Assuming this joint exists
type: "sine",
min: -0.1,
max: 0.1,
speed: 1,
offset: Math.PI, // 180 degrees out of phase with left side
isDegrees: false,
},
{
name: "hip_rotation_right", // Assuming this joint exists
type: "sine",
min: -0.2,
max: 0.2,
speed: 1,
offset: Math.PI + Math.PI / 2, // 180 + 90 degrees phase
isDegrees: false,
},
{
name: "hip_flexion_right", // Assuming this joint exists
type: "sine",
min: -0.3,
max: 0.6,
speed: 1,
offset: Math.PI, // 180 degrees out of phase with left hip
isDegrees: false,
},
{
name: "knee_joint_right", // Assuming this joint exists
type: "sine",
min: 0.2,
max: 1.4,
speed: 1,
offset: Math.PI + Math.PI / 2, // 180 + 90 degrees phase
isDegrees: false,
},
{
name: "ankle_joint_right", // Assuming this joint exists
type: "sine",
min: -0.4,
max: 0.1,
speed: 1,
offset: 0, // 180 + 180 degrees = 360 = 0
isDegrees: false,
},
{
name: "toe_joint_right", // Assuming this joint exists
type: "sine",
min: -0.2,
max: 0.2,
speed: 1,
offset: Math.PI / 2, // 180 + 270 = 450 degrees = 90 degrees
isDegrees: false,
},
],
};
|