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,
    },
  ],
};