| import { MathUtils } from "three"; |
| import { RobotAnimationConfig } from "@/lib/types"; |
|
|
| |
| export interface UrdfViewerElement extends HTMLElement { |
| setJointValue: (joint: string, value: number) => void; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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; |
|
|
| try { |
| |
| for (const joint of config.joints) { |
| |
| let ratio = 0; |
| const adjustedTime = |
| time * joint.speed * speedMultiplier + joint.offset; |
|
|
| switch (joint.type) { |
| case "sine": |
| |
| ratio = (Math.sin(adjustedTime) + 1) / 2; |
| break; |
| case "linear": |
| |
| ratio = (adjustedTime % (2 * Math.PI)) / (2 * Math.PI); |
| break; |
| case "constant": |
| |
| ratio = 1; |
| break; |
| default: |
| |
| if (joint.customEasing) { |
| ratio = joint.customEasing(adjustedTime); |
| } |
| } |
|
|
| |
| let value = MathUtils.lerp(joint.min, joint.max, ratio); |
|
|
| |
| if (joint.isDegrees) { |
| value = (value * Math.PI) / 180; |
| } |
|
|
| |
| try { |
| viewer.setJointValue(joint.name, value); |
| } catch (e) { |
| |
| } |
| } |
| } catch (err) { |
| console.error("Error in robot animation:", err); |
| } |
|
|
| |
| animationFrameId = requestAnimationFrame(animate); |
| }; |
|
|
| |
| animationFrameId = requestAnimationFrame(animate); |
|
|
| |
| return () => { |
| isRunning = false; |
|
|
| if (animationFrameId) { |
| cancelAnimationFrame(animationFrameId); |
| animationFrameId = null; |
| } |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| export function animateHexapodRobot(viewer: UrdfViewerElement): () => void { |
| let animationFrameId: number | null = null; |
| let isRunning = true; |
|
|
| const animate = () => { |
| |
| if (!isRunning) return; |
|
|
| |
| 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)); |
|
|
| |
| if (typeof viewer.setJointValue === "function") { |
| |
| viewer.setJointValue( |
| `HP${i}`, |
| (MathUtils.lerp(30, 0, ratio) * Math.PI) / 180 |
| ); |
| |
| viewer.setJointValue( |
| `KP${i}`, |
| (MathUtils.lerp(90, 150, ratio) * Math.PI) / 180 |
| ); |
| |
| viewer.setJointValue( |
| `AP${i}`, |
| (MathUtils.lerp(-30, -60, ratio) * Math.PI) / 180 |
| ); |
|
|
| |
| try { |
| |
| viewer.setJointValue(`TC${i}A`, MathUtils.lerp(0, 0.065, ratio)); |
| viewer.setJointValue(`TC${i}B`, MathUtils.lerp(0, 0.065, ratio)); |
| |
| viewer.setJointValue(`W${i}`, performance.now() * 0.001); |
| } catch (e) { |
| |
| } |
| } |
| } |
| } catch (err) { |
| console.error("Error in animation:", err); |
| } |
|
|
| |
| animationFrameId = requestAnimationFrame(animate); |
| }; |
|
|
| |
| animationFrameId = requestAnimationFrame(animate); |
|
|
| |
| return () => { |
| |
| isRunning = false; |
|
|
| if (animationFrameId) { |
| cancelAnimationFrame(animationFrameId); |
| animationFrameId = null; |
| } |
| }; |
| } |
|
|
| |
| export const cassieWalkingConfig: RobotAnimationConfig = { |
| speedMultiplier: 0.5, |
| joints: [ |
| |
| { |
| name: "hip_abduction_left", |
| type: "sine", |
| min: -0.1, |
| max: 0.1, |
| speed: 1, |
| offset: 0, |
| isDegrees: false, |
| }, |
| { |
| name: "hip_rotation_left", |
| type: "sine", |
| min: -0.2, |
| max: 0.2, |
| speed: 1, |
| offset: Math.PI / 2, |
| isDegrees: false, |
| }, |
| { |
| name: "hip_flexion_left", |
| type: "sine", |
| min: -0.3, |
| max: 0.6, |
| speed: 1, |
| offset: 0, |
| isDegrees: false, |
| }, |
| { |
| name: "knee_joint_left", |
| type: "sine", |
| min: 0.2, |
| max: 1.4, |
| speed: 1, |
| offset: Math.PI / 2, |
| isDegrees: false, |
| }, |
| { |
| name: "ankle_joint_left", |
| type: "sine", |
| min: -0.4, |
| max: 0.1, |
| speed: 1, |
| offset: Math.PI, |
| isDegrees: false, |
| }, |
| { |
| name: "toe_joint_left", |
| type: "sine", |
| min: -0.2, |
| max: 0.2, |
| speed: 1, |
| offset: Math.PI * 1.5, |
| isDegrees: false, |
| }, |
|
|
| |
| { |
| name: "hip_abduction_right", |
| type: "sine", |
| min: -0.1, |
| max: 0.1, |
| speed: 1, |
| offset: Math.PI, |
| isDegrees: false, |
| }, |
| { |
| name: "hip_rotation_right", |
| type: "sine", |
| min: -0.2, |
| max: 0.2, |
| speed: 1, |
| offset: Math.PI + Math.PI / 2, |
| isDegrees: false, |
| }, |
| { |
| name: "hip_flexion_right", |
| type: "sine", |
| min: -0.3, |
| max: 0.6, |
| speed: 1, |
| offset: Math.PI, |
| isDegrees: false, |
| }, |
| { |
| name: "knee_joint_right", |
| type: "sine", |
| min: 0.2, |
| max: 1.4, |
| speed: 1, |
| offset: Math.PI + Math.PI / 2, |
| isDegrees: false, |
| }, |
| { |
| name: "ankle_joint_right", |
| type: "sine", |
| min: -0.4, |
| max: 0.1, |
| speed: 1, |
| offset: 0, |
| isDegrees: false, |
| }, |
| { |
| name: "toe_joint_right", |
| type: "sine", |
| min: -0.2, |
| max: 0.2, |
| speed: 1, |
| offset: Math.PI / 2, |
| isDegrees: false, |
| }, |
| ], |
| }; |
|
|