Skip to content

Commit abe7db1

Browse files
committed
feat(manim): upload the script for making our annimation video.
1 parent 2cfe21e commit abe7db1

9 files changed

Lines changed: 1028 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
# save data
33
*.json
44
*.txt
5-
*.pyc
5+
*.pyc
6+
7+
tools/manim/media/*

tools/manim_himo/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
HiMo annimation video
2+
---
3+
4+
We use manim to create the animation video for project website. The code is under this folder (`tools/manim_himo`).
5+
6+
I create a new conda python environment for manim package, please following their [installation guide](https://docs.manim.community/en/stable/installation.html) to setup your environment.
7+
8+
It's also my first time to use this tools, I refer to their documentation and discuss with ChatGPT to create the animation video. I would strongly recommend to read their [QuickStart Guide](https://docs.manim.community/en/stable/tutorials/quickstart.html) first to get familiar with the basic usage.
9+
10+
## Usage
11+
12+
To render our video, you can run the following command:
13+
```bash
14+
manim -pql scene.py
15+
manim -pql single_dynamic.py
16+
manim -qh dynamic.py
17+
manim -qh multi_v2.py
18+
```
19+
20+
And each of them will generate a mp4 video file under `media/videos/` folder. Here is a example folder structure from my local machine:
21+
```
22+
➜ manim_himo git:(main) tree -L 2
23+
.
24+
├── dynamic.py
25+
├── __init__.py
26+
├── main.py
27+
├── media
28+
│ ├── images
29+
│ │ ├── dynamic
30+
│ │ ├── multi_v2
31+
│ │ ├── scene
32+
│ │ └── single_dynamic
33+
│ ├── texts
34+
│ └── videos
35+
│ └── videos
36+
│ ├── dynamic
37+
│ │ ├── 1080p60
38+
│ │ └── 480p15
39+
│ ├── multi_v2
40+
│ │ ├── 1080p60
41+
│ │ └── 480p15
42+
│ ├── scene
43+
│ │ ├── 1080p60
44+
│ │ └── 480p15
45+
│ └── single_dynamic
46+
│ └── 480p15
47+
├── multi_dynamic.py
48+
├── multi_v2.py
49+
├── scene.py
50+
└── single_dynamic.py
51+
```
52+
53+
Feel free to explore more, and happy manimming! 🎬

tools/manim_himo/__init__.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import numpy as np
2+
MAX_RAY_LENGTH = 10000
3+
def get_line_intersection(p1, p2, p3, p4):
4+
A1 = p2[1] - p1[1]
5+
B1 = p1[0] - p2[0]
6+
C1 = A1 * p1[0] + B1 * p1[1]
7+
8+
A2 = p4[1] - p3[1]
9+
B2 = p3[0] - p4[0]
10+
C2 = A2 * p3[0] + B2 * p3[1]
11+
12+
determinant = A1 * B2 - A2 * B1
13+
14+
if determinant == 0:
15+
return None # The lines are parallel or coincident
16+
17+
x = (B2 * C1 - B1 * C2) / determinant
18+
y = (A1 * C2 - A2 * C1) / determinant
19+
20+
if (
21+
min(p1[0], p2[0]) <= x <= max(p1[0], p2[0])
22+
and min(p1[1], p2[1]) <= y <= max(p1[1], p2[1])
23+
and min(p3[0], p4[0])-0.01 <= x <= max(p3[0], p4[0])+0.1
24+
and min(p3[1], p4[1])-0.01 <= y <= max(p3[1], p4[1])+0.1
25+
):
26+
return np.array([x, y, 0])
27+
else:
28+
return None
29+
30+
def sort_angle(rays, dots, angles):
31+
# Assuming rays, dots, and angles are already populated
32+
combined = list(zip(angles, rays, dots))
33+
34+
# Sort combined list based on the angle
35+
combined.sort(key=lambda x: x[0])
36+
37+
# Unpack the sorted tuples back into individual lists
38+
angles, rays, dots = zip(*combined)
39+
40+
return list(rays), list(dots), list(angles)

tools/manim_himo/dynamic.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
# Created: 2024-08-25 22:30
3+
# Copyright (C) 2023-now, RPL, KTH Royal Institute of Technology
4+
# Author: Qingwen Zhang (https://kin-zhang.github.io/)
5+
#
6+
# This file is part of HiMo (https://github.com/KTH-RPL/HiMo) projects.
7+
# If you find this repo helpful, please cite the respective publication as
8+
# listed on the above website.
9+
10+
# Description: Draw animation video we present.
11+
"""
12+
13+
from manim import *
14+
import os, sys
15+
BASE_DIR = os.path.abspath(os.path.join( os.path.dirname( __file__ ), '..' ))
16+
sys.path.append(BASE_DIR)
17+
from manim_himo import get_line_intersection, MAX_RAY_LENGTH, sort_angle
18+
19+
class SingleLiDAR(Scene):
20+
21+
def ego_assets(self):
22+
ego_car = Rectangle(width=1, height=2, color=DARK_BROWN).scale(0.5).move_to(UP)
23+
lidar = Circle(radius=0.1, color=BLUE).scale(0.5).move_to(ego_car.get_center())
24+
return VGroup(ego_car, lidar)
25+
26+
def display_text(self, text, font_size=18):
27+
text = Text(text, font_size = font_size)
28+
self.play(Write(text))
29+
self.wait(1)
30+
self.remove(text)
31+
32+
def lidar_ray(self, sensor_pose, rect_points, start=-90, end=270, angle_interval=10):
33+
rays, dots, angles = [], [], []
34+
for angle in range(start, end, angle_interval):
35+
ray_dir = np.array([np.cos(angle * DEGREES), np.sin(angle * DEGREES), 0])
36+
intersections = []
37+
38+
for i in range(len(rect_points)):
39+
p1 = rect_points[i]
40+
p2 = rect_points[(i + 1) % len(rect_points)]
41+
intersection_point = get_line_intersection(sensor_pose, sensor_pose + MAX_RAY_LENGTH * ray_dir, p1, p2)
42+
if intersection_point is not None:
43+
intersections.append(intersection_point)
44+
45+
if intersections:
46+
# Find the closest intersection point
47+
closest_point = min(intersections, key=lambda point: np.linalg.norm(point - sensor_pose))
48+
ray = Line(start=sensor_pose, end=closest_point, color=YELLOW, buff=0).add_tip(tip_length=0.1).set_opacity(0.5)
49+
dot = Dot(closest_point, color=BLUE, radius=0.12)
50+
rays.append(ray)
51+
dots.append(dot)
52+
angles.append(angle)
53+
return rays, dots, angles
54+
55+
def scene_group(self, speed=0.1):
56+
Gego = self.ego_assets()
57+
obj1 = Rectangle(width=1, height=2).next_to(Gego[0], LEFT*8) # , color=BLACK
58+
obj2 = Rectangle(width=2, height=3).next_to(Gego[0], DOWN*8) # , color=BLACK
59+
obj3 = Rectangle(width=1, height=2).next_to(Gego[0], RIGHT*8) # , color=BLACK
60+
self.add(Gego, obj1, obj2, obj3)
61+
# Simulate the moving obj1 and obj2
62+
inter_set =5
63+
Graydots = VGroup()
64+
Gdots = VGroup()
65+
for inter_ in range(0, 360, inter_set):
66+
rays, dots, angles = [], [], []
67+
for obj in [obj1, obj2, obj3]:
68+
ray, dot, angle = self.lidar_ray(Gego[1].get_center(), obj.get_vertices(), start=inter_-90, end=inter_+inter_set-90)
69+
rays.extend(ray)
70+
dots.extend(dot)
71+
angles.extend(angle)
72+
# order rays and dots based on the angle
73+
if len(rays) == 0:
74+
continue
75+
rays, dots, angles = sort_angle(rays, dots, angles)
76+
start_pos = Gego[1].get_center()
77+
ray_dir = np.array([np.cos((inter_-90) * DEGREES), np.sin((inter_+inter_set-90) * DEGREES), 0])
78+
redray = Line(start=start_pos, end=start_pos+ray_dir*100, color=RED, buff=0).add_tip(tip_length=0.1).set_opacity(0.5)
79+
self.add(redray)
80+
for ray, dot in zip(rays, dots):
81+
self.play(Create(ray), Create(dot), run_time=0.05)
82+
Gdots.add(dot)
83+
Graydots.add(ray)
84+
# self.wait(0.1)
85+
self.play(obj1.animate.shift(DOWN*speed*inter_set/360), \
86+
obj2.animate.shift(UP*speed*inter_set/360), \
87+
obj3.animate.shift(UP*speed*inter_set/360), run_time=0.05)
88+
self.remove(redray)
89+
90+
for obj in [obj1, obj2, obj3]:
91+
obj.set_opacity(0.1)
92+
self.play(FadeOut(Graydots), FadeOut(Gdots), FadeOut(obj1), FadeOut(obj2), FadeOut(obj3), FadeOut(Gego))
93+
return VGroup(Gego, obj1, obj2, obj3, Gdots)
94+
95+
def construct(self):
96+
97+
slow = self.scene_group(speed=0.1)
98+
self.remove(slow)
99+
fast = self.scene_group(speed=2.2)
100+
self.remove(fast)
101+
total_width = slow.width + fast.width
102+
103+
# 计算第一个对象的中心点应该在的位置(场景中心的左侧)
104+
slow_target_position = ORIGIN + LEFT * (total_width / 2 - slow.width / 2) - LEFT
105+
106+
# 计算第二个对象的中心点应该在的位置(场景中心的右侧)
107+
fast_target_position = ORIGIN + RIGHT * (total_width / 2 - fast.width / 2) + LEFT
108+
109+
# 移动两个对象到计算好的位置
110+
self.play(slow.animate.scale(0.6).move_to(slow_target_position))
111+
self.play(FadeIn(Text("Slow-speed objects", font_size=18).next_to(slow, DOWN)))
112+
self.play(fast.animate.scale(0.6).move_to(fast_target_position))
113+
self.play(FadeIn(Text("High-speed objects", font_size=18).next_to(fast, DOWN)))
114+
115+
116+
self.wait(2)

0 commit comments

Comments
 (0)