Manim チュートリアル

## はじめに
この記事はPythonで数学の映像を作ることができる「Manim」について、簡単な使い方を説明したものです。
日本語での記事はあまりないので、作ってみました。

### 前提
- Pythonの基礎文法が分かる
- オブジェクト指向の基本がわかる
- Manimとその依存関係がインストール済み

### 環境
- Windows 11 Home
- Ubuntu 22.04 on WSL2
- Python 3.10
- Manim 0.18.1
- VScode (拡張機能：Manim Sideview)

## 目次 ##
**1.四角形を描画する**
シンプルなコードを通して、Manimの大まかな流れを掴む

**2.Manimの構成要素**
Scene, Mobject, Animation というManimの基本構造を理解する

**3.簡単なサンプルプログラム**
今までの知識を実践し、より深い理解を得る

**4.Manim独自の型と定数**
自分でメソッドとプロパティを使えるようになるための準備

**5.メソッド＆プロパティ紹介**
より高度で複雑な映像を作れるようになる

## 四角形を描画する
まず最初に、簡単なプログラムを動かしてみましょう。
```python:
from manim import *

class CreateSquare(Scene):
    def construct(self):
        square = Square()
        createAnim = Create(square)
        self.play(createAnim)
```

### 実行する方法
VScodeでManim Sideviewを入れている方であれば、このようなマーク（赤い四角で囲ったもの）が表示されていると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3837220/62e9cef7-6cc1-7cf8-c52c-aefcf9323a36.png)

このマークを押すと画面上部に「▷CreateSquare」が表示されるので押してください。
すると画面が分割されて、右側で四角形が描画されるアニメーションを見ることができたと思います。

### 結果
<iframe width="560" height="315" src="https://www.youtube.com/embed/98ebfMN2Aks?si=Iv6F1tYs8PoErdPm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

### コードの説明

#### - 1行目 -
Manimモジュールを読み込む。
```python:
from manim import *
```
#### - 3行目 -
Sceneを継承したCreateSquareという名前のクラスを作る。
そうすることで、オブジェクトを表示するためのキャンバスを用意している。
```python:
class CreateSquare(Scene):
```

#### - 4行目 -
Sceneクラスのconstructメソッドをオーバーライドする。
Manimのプログラムのほとんどはこのメソッドの中に書く。
```python:
def construct(self):
```

#### - 5行目 -
Squareクラスをインスタンス化して、変数squareに入れる。
そうすることで四角形を生成している。
```python:
square = Square()
```

#### - 6行目 -
Createクラスをインスタンス化して、変数createAnimに入れる。
そのとき、コンストラクタに mobject = square を渡す。
そうすることで、５行目で作った四角形を描画するアニメーションを作っている。
```python:
createAnim = Create(square)
```

#### - 7行目 -
Sceneクラスのplayメソッドを実行している。
そのときの引数にcreateAnimを渡している。
そうすることで、６行目で作ったアニメーションを実行している。
```python:
self.play(createAnim)
```
<br>
一回一回変数に代入せず、このようにまとめて書いてもよいです。

```python:
from manim import *

class CreateSquare(Scene):
    def construct(self):
        self.play(Create(Square()))
```

## Manimの構成要素
Manimは主に以下の３つの機能から成り立っています。

__1.Scene機能__
Mobjectを表示するキャンバスのことをSceneという。
Scene機能：映像の制御全般を行う。
Mobjectを表示したり、Animationを実行したりする。

__2.Mobject機能__
画面に表示するオブジェクト全てをMobject(mathematical object)という。
Mobject機能：Mobjectを生成したり、静的に回転・移動・変形させる。
※生成するだけで表示はしない

__3.Animation機能__
Mobjectのある決まった一連の動きをAnimationという。
その動きは、Mobjectを動的に回転・移動・変形させるものである。
Animation機能：Animationを生成する。
※生成するだけで実行はしない

### Scene機能
Scene機能は全てSceneクラスツリーに属します。
Sceneクラスツリーには純粋なSceneクラスと、それを継承した６つのクラスがあります。

```mermaid
flowchart LR
    Scene-->VectorScene
    Scene-->MovingCameraScene
    Scene-->ThreeDScene
    VectorScene-->LinearTransformationScene
    MovingCameraScene-->ZoomedScene
    ThreeDScene-->SpecialThreeDScene
```
#### それぞれのSceneクラスの特徴
**1. Sceneクラス**
最も基本的なSceneクラス。
シンプルな2Dアニメーションや描画、テキストの表示、図形の操作などに使用。

**2. VectorSceneクラス**
ベクトルや座標平面を扱うためのSceneクラス。
ベクトルの可視化、座標軸の描画、数学的なベクトル操作を表現するアニメーションに使用。

**3. LinearTransformationSceneクラス**
線形変換を可視化するためのSceneクラス。
線形代数の概念（行列変換、固有ベクトル、固有値など）を視覚的に示す際に使用。

**4. MovingCameraSceneクラス**
カメラを動かすためのSceneクラス。
特定の部分にフォーカスを当てたり、広い範囲をカバーするアニメーションを作成したりする場合に使用。

**5. ZoomedSceneクラス**
シーン内の特定の領域をズームインして強調表示することができるSceneクラス。
詳細な部分に焦点を当てたいときや、全体と部分を同時に表示するアニメーションを作成したいときに使用。

**6. ThreeDSceneクラス**
3Dシーンを扱うための基本的なSceneクラス。
3Dグラフィックスやアニメーション、立体的な図形やグラフの表示に使用。

**7. SpecialThreeDSceneクラス**
ThreeDSceneにさらに特殊なカメラ操作やシェーディング機能を追加したSceneクラス。
特殊な3D効果や、より高度な3Dカメラ操作を必要とするアニメーションに使用。

#### Scene機能の使い方
まず初めに、Sceneクラスを継承したクラスを作成する。
このプログラムのSceneの部分をVectorSceneやThreeDSceneに変更することで、
それぞれのSceneを使うことができる。
```python
class SceneName(Scene):
```

次に、SceneクラスのConstructメソッドをオーバーライドする。
Manimで作る映像本体に関わるコードは全てこのメソッド内に記述する。
Mobject生成・Animation生成・Mobject表示・Animation実行 など
```python
def construct(self):
```

Sceneクラスのメソッドを使用することで、以下のことができる。
```python
self.add(Mobject)           # Mobjectを表示する（シーンに追加する）
self.play(Animation)        # Animationを実行する
```

### Mobject機能
Mobject機能は全てMobjectクラスツリーに属します。
Mobjectクラスツリーには非常にたくさんの具体的なクラスがあります。
(Square, Circle, Triangle, Line, Text など)
Mobjectクラスには主に３つの子クラスがあり、
そこから上記のような具体的なクラスが派生します。

Mobjectクラスとその子クラスは抽象度が高く、
そのままインスタンス化されて使われることはありません。
そこから何度か継承を繰り返して具体的になったクラスが、
インスタンス化されて使われます。


```mermaid
flowchart LR
    Mobject-->PMobject
    Mobject-->VMobject
    Mobject-->AbstractImageMobject
```
#### それぞれのMobject子クラスの特徴
**1. PMobjectクラス**
Point Cloud Mobject の略であり、点の集合を扱うためのMobjectクラス。

**2. VMobjectクラス**
Vectorized Mobject の略であり、ベクトルグラフィックスを扱うためのMobjectクラス。
Manimで扱うMobjectのほとんどはVMobjectに属する。

**3. AbstractImageMobjectクラス**
画像を扱うためのMobjectクラス。

#### Mobject機能の使い方
SquareクラスやCircleクラスなどの具体的なクラスをインスタンス化することで、それぞれの図形を生成することができる。
```python
Square()                    # 正方形を生成する
Circle()                    # 円を生成する
```
インスタンス化するときに特定のプロパティを渡すことで、
図形の大きさや色などの特徴の初期設定をすることができる。
```python
Square(side_length = 4.0)   # 一辺の長さがside_lengthである正方形を生成する
Circle(color = BLUE)        # 色が青である円を生成する
```
Mobjectクラスのメソッドを使用することで、
Mobjectを静的に回転・移動・変形させることができる。
```python
mobject.move_to([2, 2, 0])  # Mobjectの位置を[2, 2, 0]に変更する
mobject.set_color(GREEN)    # Mobjectの色をGREENに変更する
```

### Animation機能
Animation機能を持つものは主に２つあります。

**1. Animationクラスツリー**
事前に定義されたAnimationを使用できる。
Animationの内容が明確。

**2. Mobjectクラスのanimateプロパティ**
MobjectクラスのメソッドをAnimation化できる。
> 先ほどMobjectクラスのメソッドを使用することで、
>Mobjectを静的に回転・移動・変形させることができる
>といったが、それを動的に行えるようになる。

#### Animation機能の使い方
##### Animationクラスツリーを使う場合
CreateクラスやFadeOutクラスなどの具体的なクラスをインスタンス化することで、それぞれのアニメーションを生成することができる。
プロパティには対象のMobjectを入れる
```python
Create(Mobject)             # Mobjectを描画するAnimationを生成する
FadeOut(Mobject)            # MobjectをフェードアウトするAnimationを生成する
```

##### Mobjectクラスのanimateプロパティを使う場合
先ほどMobjectクラスのメソッドの使い方を説明したが、
そのときのmobjectとメソッドの間に **.animate** を挿入する

```python
# Mobjectを[2, 2, 0]に移動するアニメーションを生成する
mobject.animate.move_to([2, 2, 0])

# Mobjectの色をGREENに変更するアニメーションを生成する
mobject.animate.set_color(GREEN)            
```

### まとめ
__1.Scene機能__
Mobjectの表示、Animationの実行など
全てSceneクラスツリーに属する

__2.Mobject機能__
Mobjectを生成したり、静的に回転・移動・変形させる
全てMobjectクラスツリーに属する

__3.Animation機能__
Mobjectを動的に回転・移動・変形させるAnimationを生成する
Animationクラスツリーに属するものと
Mobjectクラスのanimateプロパティに属するものがある

#### 初めのプログラムを改めて確認
```python:
from manim import *

class CreateSquare(Scene):
    def construct(self):
        square = Square()             # Mobject機能
        createAnim = Create(square)   # Animation機能
        self.play(createAnim)         # Scene機能
```

## 簡単なサンプルプログラム
細かいメソッドやプロパティの説明に移る前に、
今までの知識で作ることができる簡単なプログラムを見てみましょう。

```python:Sample1.py
from manim import *

class CircleAndSquare(Scene):
    def construct(self):
        circle = Circle(color = RED)                       # Mobject機能
        circle.move_to([2, 2, 0])                          # Mobject機能

        square = Square(color = GREEN, side_length = 4.0)  # Mobject機能
        self.add(square)                                   # Scene機能

        circleAnim = circle.animate.move_to([-2, -2, 0])   # Animation機能
        self.play(circleAnim)                              # Scene機能
```
### 結果
<iframe width="560" height="315" src="https://www.youtube.com/embed/EH4F7w0BMX0?si=00SqKnUi_ugkYJs2" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<br>
<br>

```python:Sample2.py
from manim import *

class CircleMove(Scene):
    def construct(self):
        circle = Circle(color = BLUE)  # Mobject機能
        circle.move_to([-2, 2, 0])     # Mobject機能

        animFirst = Create(circle)     # Animation機能
        animMiddle = circle.animate.move_to([-2, -2, 0]).set_color(GREEN)
        animEnd = FadeOut(circle)      # Animation機能

        self.play(animFirst)           # Scene機能
        self.play(animMiddle)          # Scene機能
        self.play(animEnd)             # Scene機能
```
### 結果
<iframe width="560" height="315" src="https://www.youtube.com/embed/IvNVPM7bZpM?si=opYdp1TIELq_9Of1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<br>
<br>

```python:Sample3.py
from manim import *

class DoubleSquare(Scene):
    def construct(self):
        square1 = Square(color = BLUE)                # Mobject機能
        square2 = Square(color = RED)                 # Mobject機能
        circle = Circle(color = GREEN)                # Mobject機能

        square1.move_to([2, 2, 0])                    # Mobject機能
        square2.move_to([-2, 2, 0])                   # Mobject機能

        anim1 = square1.animate.move_to([-2, -2, 0])  # Animation機能
        anim2 = square2.animate.move_to([2, -2, 0])   # Animation機能
        animEnd = FadeOut(square1, square2)           # Animation機能

        self.add(circle)                              # Scene機能
        self.play(anim1, anim2)                       # Scene機能
        self.play(animEnd)                            # Scene機能
```
### 結果
<iframe width="560" height="315" src="https://www.youtube.com/embed/dGewLy5hBL0?si=QJXaME-Xqhswl-GA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## Manim独自の型と定数
Manimには独自の型と定数があります。
これからその一部を紹介します。
### 独自の型
```python
# 正確には違いますが、簡略化して書いています
Point3D = tuple[float, float, float]
Vector3D = np.ndarray[float, float, float]
ManimColor = str           # 16進数カラーコード
```
Pythonには数値計算を効率的に行うための **NumPy** ライブラリがあります。
２行目の np.ndarray は、NumPyによって提供される配列の型です。
**`from manim import *`** 　をするときに、内部では
**`import numpy as np`**  　が同時に実行されています。

### 独自の定数
```python
PI = np.pi                         # PIには円周率が入っている
```
#### Vector3D型の定数
```python
UP = np.array([0.0, 1.0, 0.0])
DOWN = np.array([0.0, -1.0, 0.0])
RIGHT = np.array([1.0, 0.0, 0.0])
LEFT = np.array([-1.0, 0.0, 0.0])
UL = np.array([-1.0, 1.0, 0.0])
UR = np.array([1.0, 1.0, 0.0])
DL = np.array([-1.0, -1.0, 0.0])
DR = np.array([1.0, -1.0, 0.0])
```
#### ManimColor型の定数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3837220/4a0932a5-8dc0-0eaf-c4a6-4204d9833ec4.png)
この画像のように、
定数 `BLUE_A` には、`#C7E9F1` が、
定数 `GREEN_C` には、`#83C167` が、
定数 `RED_E`　には、`#CF5044` が、
定数 `LIGHT_PINK` には、`#DC75CD` が入っている
定数名がすべて大文字であることに注意。

## メソッド＆プロパティ紹介
ここからは、私がよく使うと思うメソッドとプロパティを紹介します。
色を指定するプロパティについては、`str型`の場合も`ParsableManimColor型`の場合も簡略化して`ManimColor型`と表示しています。

### Sceneクラス
#### メソッド
```python
# 見やすくするために、一部仮引数名を変更しています
add(*mobjects: Mobject)            # Mobjectsをシーンに追加する
remove(*mobjects: Mobject)         # Mobjectsをシーンから削除する
play(*animations: Animation)       # Animationsを実行する
wait(duration: float)              # duration秒間何もしない
```

### Mobjectクラス
#### プロパティ
```python
color(ManimColor)                  # Mobjectの色
```

#### メソッド
```python
# 見やすくするために、一部仮引数名を変更しています
move_to(point: Point3D)                        # pointの場所に移動する
next_to(mobject: Mobject, direction: Vector3D) # mobjectの場所からdirectionだけ離れた場所に移動する
shift(vector: Vector3D)                        # vectorだけ移動する
set_x(x: float)                                # 指定されたx座標に移動する
set_y(y: float)                                # 指定されたy座標に移動する
to_corner(corner: Vector3D)                    # cornerに移動する
to_edge(edge: Vector3D)                        # edgeに移動する
rotate(angle: float)                           # angle(radian)だけ反時計回りに回転する
set_color(color: ManimColor)                   # 色をcolorに変更する
scale(scale_factor: float)                     # 大きさをscale_factor倍に変更する
```

#### VMobjectクラスのプロパティ
```python
fill_color(ManimColor)             # 塗りつぶす色
fill_opacity(float)                # 塗りつぶす色の透明度(0.0 ~ 1.0)
stroke_color(ManimColor)           # 枠の色
stroke_opacity(float)              # 枠の色の透明度(0.0 ~ 1.0)
stroke_width(float)                # 枠の幅
```

#### VMobjectから派生したクラス
```python
Rectangle()                        # 長方形を生成する
Square()                           # 正方形を生成する
RegularPolygon()                   # 正多角形を生成する
Circle()                           # 円を生成する
Line()                             # 直線を生成する
Arrow()                            # 矢印を生成する
NumberLine()                       # 数直線を生成する
Axes()                             # x軸とy軸を生成する
NumberPlane()                      # 座標平面を生成する
Text()                             # 文章を生成する
MathTex()                          # 数式を生成する
```

##### Rectangleクラスのプロパティ
```python
height(float)                      # 高さ
width(float)                       # 横幅
```

##### Squareクラスのプロパティ
```python
side_length(float)                 # 1辺の長さ
```

##### RegularPolygonクラスのプロパティ
```python
n(int)                             # 頂点の数
radius(float)                      # 頂点が通る円の半径
```

##### Circleクラスのプロパティ
```python
radius(float)                      # 半径
```

##### Lineクラスのプロパティ
```python
start(Point3D)                     # 始点(Point3D)
end(Point3D)                       # 終点(Point3D)
```

##### ArrowはLineを継承している

##### NumberLineクラスのプロパティ
```python
x_range([float, float, float])     # [xの最小値, xの最大値, 目盛りを降る間隔]
length(float)                      # 数直線の長さ
include_numbers(bool)              # 目盛りを降るかどうか
include_tip(bool)                  # 矢印を含むかどうか
```

##### Axesクラスのプロパティ
```python
x_range([float, float, float])     # [xの最小値, xの最大値, 目盛りを降る間隔]
y_range([float, float, float])     # [yの最小値, yの最大値, 目盛りを降る間隔]
x_length(float)                    # x軸の長さ
y_length(float)                    # y軸の長さ
tips(bool)                         # 両軸に矢印を含むかどうか
axis_config({str, any})            # {NumberLineクラスの変数, 値} 両軸に影響を与える
x_axis_config({str, any})          # {NumberLineクラスの変数, 値} x軸に影響を与える
y_axis_config({str, any})          # {NumberLineクラスの変数, 値} y軸に影響を与える
```

##### NumberPlaneはAxesを継承している（ただし矢印は表示できない）

##### Textクラスのプロパティ
```python
text(str)                          # 文字
font_size(float)                   # 文字サイズ
```
##### MathTexクラスのプロパティ
```python
tex_strings(str)                   # LaTeX記法で書かれた数式
font_size(float)                   # 文字サイズ
```

#### Axis・NumberPlaneで使えるメソッド
```python
# functionに基づいて生成した曲線を返す
plot(function: Callable[[float], float])   # 引数・返り値ともに１つで、floatである関数が引数


# graphとx軸がなす領域を塗る
get_area(graph: ParametricFunction,                  # plot関数の返り値
         x_range: [float, float],                    # [xの最小値, xの最大値]
         color: ManimColor,                          # 領域を塗る色
         opacity: float)                             # 領域を塗る色の透明度


# graphとx軸がなす領域を細長い長方形で埋める
get_riemann_rectangles(graph: ParametricFunction,    # plot関数の返り値
                       x_range: [float, float],      # [xの最小値, xの最大値]
                       dx: float,                    # 長方形の幅
                       color: ManimColor,            # 長方形を塗る色
                       fill_opacity: float)          # 長方形を塗る色の透明度
```

### Animationクラス
#### プロパティ
```python
mobject(Mobject)                   # Animationの対象となるMobject
run_time(float)                    # アニメーションの継続時間（秒）
```
#### Animationから派生したクラス
```python
FadeIn()                           # Mobjectをフェードインする
FadeOut()                          # Mobjectをフェードアウトする
Create()                           # Mobjectを描画する（端から）
Write()                            # Mobjectを描画する（外側から手書きするイメージ）
Transform(target_mobject: Mobject) # あるMobjectをtarget_mobjectに変形させる
FadeToColor(color: ManimColor)     # 色をcolorに変化させる
Indicate(color: ManimColor)        # color色に点灯させる、目立たせる
```

## さいごに
私自身、Manimを使う際に日本語の説明があまり無く大変だったので、
備忘録もかねてこれから学ぶ人のために作ってみました。
最後の メソッド＆プロパティ紹介 は読むだけでは分からないと思いますので、
気になったものがあったら自分で試してみるのが良いと思います。
今後、複雑なサンプルプログラムも追加するかもしれません。

### 参考
Manim Community 公式ページ

https://docs.manim.community/en/stable/index.html#

Manim Community 公式GitHubリポジトリ

https://github.com/ManimCommunity/manim/tree/main