HansonServo/animation.cpp

338 lines
8.4 KiB
C++

#include "animation.h"
Animation::Animation() {
clear();
memcpy(header.magic, "ANIM", 4);
header.version = 1;
header.frameRate = FRAMES_PER_SECOND;
header.frameCount = MAX_FRAMES;
memset(header.reserved, 0, sizeof(header.reserved));
}
void Animation::addCurveSegment(const CurveSegment& segment) {
if (segment.motorID < NUM_CHANNELS) {
curves[segment.motorID].push_back(segment);
}
}
void Animation::clearCurves(uint8_t motorID) {
if (motorID < NUM_CHANNELS) {
curves[motorID].clear();
}
}
void Animation::clearAllCurves() {
for (int i = 0; i < NUM_CHANNELS; ++i) {
curves[i].clear();
}
}
uint16_t Animation::getMotorPosition(uint8_t motorID, uint16_t timeCS) {
if (motorID >= NUM_CHANNELS) return 0;
for (const auto& seg : curves[motorID]) {
if (timeCS >= seg.startTime && timeCS <= seg.endTime) {
// Convert uint16_t to float in range -1 to 1
auto toFloat = [](uint16_t v) {
return (float(v) / 65535.0f) * 2.0f - 1.0f;
};
// Define control points
float x0 = seg.startTime;
float x1 = seg.startHandleX;
float x2 = seg.endHandleX;
float x3 = seg.endTime;
float y0 = toFloat(seg.startPointY);
float y1 = toFloat(seg.startHandleY);
float y2 = toFloat(seg.endHandleY);
float y3 = toFloat(seg.endPointY);
// Solve for t such that Bézier x(t) ≈ timeCS
auto bezierX = [&](float t) {
float u = 1.0f - t;
return u * u * u * x0 + 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t * x3;
};
float t = 0.5f;
float lower = 0.0f;
float upper = 1.0f;
for (int i = 0; i < 20; ++i) {
float x = bezierX(t);
if (fabs(x - timeCS) < 0.5f) break;
if (x < timeCS) lower = t;
else upper = t;
t = (lower + upper) * 0.5f;
}
// Evaluate Bézier y(t)
float u = 1.0f - t;
float value = u * u * u * y0 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y3;
// Remap to PWM range
return constrain((value + 1.0f) * 2047.5f, 0, 4095);
}
}
return 2048; // Default center
}
void Animation::clear() {
//memset(data, 0, sizeof(data));
}
// uint16_t* Animation::getRawData() {
// return &data[0][0];
// }
// size_t Animation::getSize() const {
// return sizeof(data);
// }
void Animation::setFrameCount(uint16_t count) {
header.frameCount = count;
}
uint16_t Animation::getFrameCount() const {
return header.frameCount;
}
bool Animation::saveToFile(const char* filename) {
// Auto-detect actual frame count
uint16_t lastFrame = 0;
// for (uint16_t frame = 0; frame < MAX_FRAMES; frame++) {
// for (uint8_t ch = 0; ch < NUM_CHANNELS; ch++) {
// if (data[frame][ch] != 0) {
// lastFrame = frame;
// break;
// }
// }
// }
//header.frameCount = lastFrame + 1;
File file = FFat.open(filename, FILE_WRITE);
if (!file) return false;
// Write header and motion data
file.write((uint8_t*)&header, sizeof(header));
//file.write((uint8_t*)data, sizeof(data));
// Count total curve segments
uint16_t curveCount = 0;
for (uint8_t ch = 0; ch < NUM_CHANNELS; ch++) {
curveCount += curves[ch].size();
}
// Write curve count
file.write((uint8_t*)&curveCount, sizeof(curveCount));
// Write all curve segments
for (uint8_t ch = 0; ch < NUM_CHANNELS; ch++) {
for (const CurveSegment& seg : curves[ch]) {
file.write((uint8_t*)&seg, sizeof(CurveSegment));
}
}
file.close();
return true;
}
bool Animation::loadFromFile(const char* filename) {
File file = FFat.open(filename, FILE_READ);
if (!file) return false;
// Read and validate header
AnimationHeader tempHeader;
if (file.read((uint8_t*)&tempHeader, sizeof(tempHeader)) != sizeof(tempHeader)) {
file.close();
return false;
}
if (strncmp(tempHeader.magic, "ANIM", 4) != 0 || tempHeader.version != 1) {
file.close();
return false;
}
header = tempHeader;
// Read motion data
// size_t expectedSize = sizeof(data);
// if (file.read((uint8_t*)data, expectedSize) != expectedSize) {
// file.close();
// return false;
// }
// Read curve count
uint16_t curveCount;
if (file.read((uint8_t*)&curveCount, sizeof(curveCount)) != sizeof(curveCount)) {
file.close();
return false;
}
// Clear existing curves
clearAllCurves();
// Read curve segments
for (uint16_t i = 0; i < curveCount; i++) {
CurveSegment seg;
if (file.read((uint8_t*)&seg, sizeof(CurveSegment)) != sizeof(CurveSegment)) {
file.close();
return false;
}
if (seg.motorID < NUM_CHANNELS) {
curves[seg.motorID].push_back(seg);
}
}
file.close();
return true;
}
void Animation::printCurves() {
Serial.println("=== Curve Segments ===");
auto toFloat = [](uint16_t v) -> float {
return (float(v) / 65535.0f) * 2.0f - 1.0f;
};
for (uint8_t motorID = 0; motorID < NUM_CHANNELS; motorID++) {
if (curves[motorID].empty()) continue;
Serial.print("Motor ");
Serial.print(motorID);
Serial.println(":");
for (size_t i = 0; i < curves[motorID].size(); ++i) {
const CurveSegment& seg = curves[motorID][i];
Serial.print(" Segment ");
Serial.print(i);
Serial.print(" | Time: ");
Serial.print(seg.startTime * 0.01f, 2);
Serial.print("s → ");
Serial.print(seg.endTime * 0.01f, 2);
Serial.println("s");
Serial.print(" Start Point Y: ");
Serial.println(toFloat(seg.startPointY), 3);
Serial.print(" Start Handle: (");
Serial.print(seg.startHandleX * 0.01f, 2);
Serial.print("s, ");
Serial.print(toFloat(seg.startHandleY), 3);
Serial.println(")");
Serial.print(" End Handle: (");
Serial.print(seg.endHandleX * 0.01f, 2);
Serial.print("s, ");
Serial.print(toFloat(seg.endHandleY), 3);
Serial.println(")");
Serial.print(" End Point Y: ");
Serial.println(toFloat(seg.endPointY), 3);
}
}
Serial.println("======================");
}
void Animation::createBasicSCurve() {
clearAllCurves();
// Helper to convert float [-1, 1] to uint16_t [0, 65535]
auto toUint16 = [](float v) -> uint16_t {
return constrain((v + 1.0f) * 32767.5f, 0, 65535);
};
setFrameCount(800); // 8.00 seconds
// First segment: -1 at 0s → +1 at 0.40s
CurveSegment seg1;
seg1.motorID = 0;
seg1.startTime = 0; // 0.00s
seg1.endTime = 40; // 0.40s = 40 centiseconds
seg1.startPointY = toUint16(-1.0f); // P0.y
seg1.startHandleX = 10; // P1.x (early pull)
seg1.startHandleY = toUint16(-1.0f);
seg1.endHandleX = 30; // P2.x (late pull)
seg1.endHandleY = toUint16(+1.0f);
seg1.endPointY = toUint16(+1.0f); // P3.y
addCurveSegment(seg1);
// Second segment: +1 at 0.40s → -1 at 8.00s
CurveSegment seg2;
seg2.motorID = 0;
seg2.startTime = 40; // 0.40s
seg2.endTime = 800; // 8.00s
seg2.startPointY = toUint16(+1.0f); // P0.y
seg2.startHandleX = 200; // P1.x (early pull)
seg2.startHandleY = toUint16(+1.0f);
seg2.endHandleX = 600; // P2.x (late pull)
seg2.endHandleY = toUint16(-1.0f);
seg2.endPointY = toUint16(-1.0f); // P3.y
addCurveSegment(seg2);
}
void Animation::createEaseOutCurve() {
clearAllCurves();
// Helper to convert float [-1, 1] to uint16_t [0, 65535]
auto toUint16 = [](float v) -> uint16_t {
return constrain((v + 1.0f) * 32767.5f, 0, 65535);
};
setFrameCount(400); // 8.00 seconds total
// Segment 1: Ease out from -1 to +1 over 4 seconds
CurveSegment seg;
seg.motorID = 0;
seg.startTime = 0; // 0.00s
seg.endTime = 200; // 4.00s
seg.startPointY = toUint16(-1.0f); // P0.y
seg.startHandleX = 50; // P1.x (early pull → slow start)
seg.startHandleY = toUint16(-1.0f);
seg.endHandleX = 180; // P2.x (late pull → fast finish)
seg.endHandleY = toUint16(+0.5f);
seg.endPointY = toUint16(+1.0f); // P3.y
addCurveSegment(seg);
// Segment 2: Ease out from +1 to -1 over 4 seconds
CurveSegment returnSeg;
returnSeg.motorID = 0;
returnSeg.startTime = 200; // 4.00s
returnSeg.endTime = 400; // 8.00s
returnSeg.startPointY = toUint16(+1.0f); // P0.y
returnSeg.startHandleX = 250; // P1.x (early pull → slow start)
returnSeg.startHandleY = toUint16(+1.0f);
returnSeg.endHandleX = 380; // P2.x (late pull → fast finish)
returnSeg.endHandleY = toUint16(-0.5f);
returnSeg.endPointY = toUint16(-1.0f); // P3.y
addCurveSegment(returnSeg);
}