Skip to content

Commit 76a628e

Browse files
Create an animation class (#51)
1 parent 9a04d78 commit 76a628e

File tree

6 files changed

+246
-52
lines changed

6 files changed

+246
-52
lines changed

examples/cddp_car.cpp

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <filesystem>
77
#include <random>
88
#include <cmath>
9+
#include "animation.hpp"
910
#include "cddp.hpp"
1011

1112
namespace plt = matplotlibcpp;
@@ -114,17 +115,14 @@ void plotCarBox(const Eigen::VectorXd& state, Eigen::VectorXd& control,
114115
plt::plot(base_x, base_y, keywords);
115116

116117
// Plot steering direction
117-
// Calculate front axle center point
118118
double front_x = x + length/2 * cos(theta);
119119
double front_y = y + length/2 * sin(theta);
120120

121-
// Calculate steering indicator endpoint
122-
double steering_length = width/2; // Length of steering indicator line
123-
double steering_angle = theta + steering; // Global steering angle
121+
double steering_length = width/2;
122+
double steering_angle = theta + steering;
124123
double steering_end_x = front_x + steering_length * cos(steering_angle);
125124
double steering_end_y = front_y + steering_length * sin(steering_angle);
126125

127-
// Plot steering indicator
128126
std::vector<double> steer_x = {front_x, steering_end_x};
129127
std::vector<double> steer_y = {front_y, steering_end_y};
130128
keywords["color"] = "green";
@@ -133,7 +131,15 @@ void plotCarBox(const Eigen::VectorXd& state, Eigen::VectorXd& control,
133131
}
134132

135133
int main() {
136-
// Problem parameters
134+
// Set up animation configuration
135+
Animation::AnimationConfig config;
136+
config.width = 800;
137+
config.height = 800;
138+
config.frame_skip = 5; // Save every 5th frame
139+
config.frame_delay = 10; // 10/100 = 0.1 seconds between frames
140+
Animation animation(config);
141+
142+
// Problem parameters
137143
int state_dim = 4; // [x y theta v]
138144
int control_dim = 2; // [wheel_angle acceleration]
139145
int horizon = 500; // Same as MATLAB example
@@ -224,61 +230,44 @@ int main() {
224230
auto U_sol = solution.control_sequence;
225231
auto t_sol = solution.time_sequence;
226232

227-
// Create directory for plots
228-
const std::string plotDirectory = "../results/tests";
229-
if (!fs::exists(plotDirectory)) {
230-
fs::create_directories(plotDirectory);
231-
}
232-
233-
// Prepare visualization data
234-
std::vector<double> x_hist, y_hist, theta_hist, v_hist;
233+
// Prepare trajectory data
234+
std::vector<double> x_hist, y_hist;
235235
for(const auto& x : X_sol) {
236236
x_hist.push_back(x(0));
237237
y_hist.push_back(x(1));
238-
theta_hist.push_back(x(2));
239-
v_hist.push_back(x(3));
240238
}
241239

242-
// Visualization matching MATLAB style
243-
plt::figure_size(800, 800);
244-
245-
// Set plot limits and properties matching MATLAB
246-
plt::xlim(-4, 4);
247-
plt::ylim(-4, 4);
248-
plt::grid(true);
249-
250-
// Plot goal configuration
251-
double car_length = 2.1; // From MATLAB body = [0.9 2.1 0.3]
252-
double car_width = 0.9;
240+
// Car dimensions
241+
double car_length = 2.1;
242+
double car_width = 0.9;
243+
244+
// Create animation
253245
Eigen::VectorXd empty_control = Eigen::VectorXd::Zero(control_dim);
254-
plotCarBox(goal_state, empty_control, car_length, car_width, "r");
255-
256-
// Animation loop
257246
for(size_t i = 0; i < X_sol.size(); i++) {
258-
if(i % 5 == 0) {
259-
plt::clf();
260-
261-
// Plot full trajectory
262-
plt::plot(x_hist, y_hist, "b-");
263-
264-
// Plot current car position
265-
plotCarBox(X_sol[i], U_sol[i], car_length, car_width, "k");
266-
267-
// Plot settings
268-
plt::grid(true);
269-
plt::axis("equal");
270-
plt::xlim(-4, 4);
271-
plt::ylim(-4, 4);
272-
273-
std::string filename = plotDirectory + "/car_frame_" +
274-
std::to_string(i) + ".png";
275-
plt::save(filename);
276-
plt::pause(0.01);
277-
}
247+
animation.newFrame();
248+
249+
// Plot full trajectory
250+
plt::plot(x_hist, y_hist, "b-");
251+
252+
// Plot goal configuration
253+
plotCarBox(goal_state, empty_control, car_length, car_width, "r");
254+
255+
// Plot current car position
256+
plotCarBox(X_sol[i], U_sol[i], car_length, car_width, "k");
257+
258+
// Plot settings
259+
plt::grid(true);
260+
// plt::axis("equal");
261+
plt::xlim(-4, 4);
262+
plt::ylim(-4, 4);
263+
264+
animation.saveFrame(i);
278265
}
279266

267+
// Create the final GIF
268+
animation.createGif("car_parking.gif");
269+
280270
return 0;
281271
}
282272

283-
// Create animation from frames using ImageMagick:
284-
// convert -delay 1 ../results/tests/car_frame_*.png ../results/tests/car_motion.gif
273+
// convert -delay 3 ../results/frames/frame_*.png ../results/animations/car_parking.gif

include/cddp-cpp/animation.hpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
Copyright 2024 Tomo Sasaki
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#ifndef ANIMATION_HPP
18+
#define ANIMATION_HPP
19+
20+
#include <string>
21+
#include <vector>
22+
#include <filesystem>
23+
#include <memory>
24+
#include <stdexcept>
25+
#include <cstdlib>
26+
#include "matplotlibcpp.hpp"
27+
28+
namespace plt = matplotlibcpp;
29+
namespace fs = std::filesystem;
30+
31+
class Animation {
32+
public:
33+
struct AnimationConfig {
34+
int width; // Figure width in pixels
35+
int height; // Figure height in pixels
36+
int frame_skip; // Save every nth frame
37+
int frame_delay; // Delay between frames in 1/100ths of a second
38+
std::string temp_dir; // Directory for temporary frame files
39+
std::string output_dir; // Directory for output GIFs
40+
bool cleanup_frames; // Whether to delete frame files after creating GIF
41+
42+
AnimationConfig() :
43+
width(800),
44+
height(600),
45+
frame_skip(1),
46+
frame_delay(20),
47+
temp_dir("../results/frames"),
48+
output_dir("../results/animations"),
49+
cleanup_frames(true)
50+
{}
51+
};
52+
53+
// Constructor
54+
explicit Animation(const AnimationConfig& config = AnimationConfig())
55+
: config_(config) {
56+
// Create directories if they don't exist
57+
fs::create_directories(config_.temp_dir);
58+
fs::create_directories(config_.output_dir);
59+
60+
// Initialize figure
61+
plt::figure_size(config_.width, config_.height);
62+
}
63+
64+
// Start a new frame
65+
void newFrame() {
66+
plt::clf(); // Clear current figure
67+
}
68+
69+
// Save current frame
70+
void saveFrame(const int frame_number) {
71+
if (frame_number % config_.frame_skip == 0) {
72+
std::string filename = config_.temp_dir + "/frame_" +
73+
std::to_string(frame_number/config_.frame_skip) + ".png";
74+
plt::save(filename);
75+
}
76+
}
77+
78+
// Create GIF from saved frames
79+
bool createGif(const std::string& output_filename) {
80+
std::string command = "convert -delay " + std::to_string(config_.frame_delay) + " " +
81+
config_.temp_dir + "/frame_*.png " +
82+
config_.output_dir + "/" + output_filename;
83+
84+
int result = std::system(command.c_str());
85+
86+
if (result != 0) {
87+
std::cerr << "Failed to create GIF. Is ImageMagick installed?" << std::endl;
88+
return false;
89+
}
90+
91+
// Clean up temporary frame files if requested
92+
if (config_.cleanup_frames) {
93+
cleanupFrames();
94+
}
95+
96+
return true;
97+
}
98+
99+
// Clean up temporary frame files
100+
void cleanupFrames() {
101+
for (const auto& entry : fs::directory_iterator(config_.temp_dir)) {
102+
fs::remove(entry.path());
103+
}
104+
}
105+
106+
// Setter for configuration
107+
void setConfig(const AnimationConfig& config) {
108+
config_ = config;
109+
// Update figure size if changed
110+
plt::figure_size(config_.width, config_.height);
111+
}
112+
113+
// Getter for configuration
114+
const AnimationConfig& getConfig() const {
115+
return config_;
116+
}
117+
118+
private:
119+
AnimationConfig config_;
120+
};
121+
122+
#endif // ANIMATION_HPP

include/cddp-cpp/cddp.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "dynamics_model/lti_system.hpp"
4545

4646
#include "matplotlibcpp.hpp"
47+
#include "animation.hpp"
4748

4849

4950
#endif // CDDP_HPP

results/animations/car_parking.gif

2.53 MB
Loading

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ add_executable(test_logcddp_core cddp_core/test_logcddp_core.cpp)
8484
target_link_libraries(test_logcddp_core gtest gmock gtest_main cddp)
8585
gtest_discover_tests(test_logcddp_core)
8686

87+
add_executable(test_animation test_animation.cpp)
88+
target_link_libraries(test_animation gtest gmock gtest_main cddp)
8789

8890
# Test for Eigen installation
8991
add_executable(test_eigen test_eigen.cpp)

tests/test_animation.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Example showing how to use the Animation class with the cart-pole system
3+
*/
4+
5+
#include "animation.hpp"
6+
#include <cmath>
7+
8+
int main() {
9+
// Create animation with custom configuration
10+
Animation::AnimationConfig config;
11+
config.width = 800;
12+
config.height = 600;
13+
config.frame_skip = 2; // Save every other frame
14+
config.frame_delay = 10; // Faster animation
15+
Animation animation(config);
16+
17+
// Set up cart-pole parameters
18+
double cart_width = 0.3;
19+
double cart_height = 0.2;
20+
double pole_width = 0.05;
21+
double pole_length = 1.0;
22+
23+
// Simulate cart-pole motion
24+
int num_frames = 200;
25+
double dt = 0.05;
26+
double theta = M_PI/3; // Initial angle (60 degrees)
27+
double omega = 0.0; // Initial angular velocity
28+
double x = 0.0; // Initial cart position
29+
30+
for (int i = 0; i < num_frames; ++i) {
31+
animation.newFrame();
32+
33+
// Update simulation
34+
double g = 9.81;
35+
omega += -3*g/(2*pole_length) * sin(theta) * dt;
36+
theta += omega * dt;
37+
x = sin(i * 0.05) * 0.5; // Simple sinusoidal cart motion
38+
39+
// Draw cart
40+
std::vector<double> cart_x = {
41+
x - cart_width/2, x + cart_width/2,
42+
x + cart_width/2, x - cart_width/2,
43+
x - cart_width/2
44+
};
45+
std::vector<double> cart_y = {
46+
-cart_height/2, -cart_height/2,
47+
cart_height/2, cart_height/2,
48+
-cart_height/2
49+
};
50+
plt::plot(cart_x, cart_y, "k-");
51+
52+
// Draw pole
53+
double pole_end_x = x + pole_length * sin(theta);
54+
double pole_end_y = pole_length * cos(theta);
55+
std::vector<double> pole_x = {x, pole_end_x};
56+
std::vector<double> pole_y = {0, pole_end_y};
57+
plt::plot(pole_x, pole_y, "b-");
58+
59+
// Draw pole bob
60+
std::vector<double> circle_x, circle_y;
61+
for (int j = 0; j <= 20; ++j) {
62+
double t = 2 * M_PI * j / 20;
63+
circle_x.push_back(pole_end_x + pole_width * cos(t));
64+
circle_y.push_back(pole_end_y + pole_width * sin(t));
65+
}
66+
plt::plot(circle_x, circle_y, "b-");
67+
68+
// Set fixed axis limits
69+
plt::xlim(x - 2, x + 2);
70+
plt::ylim(-2, 2);
71+
72+
// Save the frame
73+
animation.saveFrame(i);
74+
}
75+
76+
// Create the final GIF
77+
animation.createGif("cartpole_animation.gif");
78+
79+
return 0;
80+
}

0 commit comments

Comments
 (0)