mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Merge branch 'master' of https://github.com/techdude/manim
This commit is contained in:
commit
fc482c107f
432 changed files with 10582 additions and 285141 deletions
36
.github/workflows/docs.yml
vendored
Normal file
36
.github/workflows/docs.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
name: build up document and deploy
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Install sphinx and manim env
|
||||
run: |
|
||||
pip3 install --upgrade pip
|
||||
sudo apt install python3-setuptools
|
||||
pip3 install -r docs/requirements.txt
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
- name: Build document with Sphinx
|
||||
run: |
|
||||
cd docs
|
||||
export PATH="$PATH:/home/runner/.local/bin"
|
||||
export SPHINXBUILD="python3 -m sphinx"
|
||||
make html
|
||||
|
||||
- name: Deploy to GitHub pages
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
ACCESS_TOKEN: ${{ secrets.DOC_DEPLOY_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: docs/build/html
|
29
.github/workflows/docs_test.yml
vendored
Normal file
29
.github/workflows/docs_test.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: docs_test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
docs_test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test document for pull requests
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Install sphinx and manim env
|
||||
run: |
|
||||
pip3 install --upgrade pip
|
||||
sudo apt install python3-setuptools
|
||||
pip3 install -r docs/requirements.txt
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
- name: Build document with Sphinx
|
||||
run: |
|
||||
cd docs
|
||||
export PATH="$PATH:/home/runner/.local/bin"
|
||||
export SPHINXBUILD="python3 -m sphinx"
|
||||
make html
|
175
.gitignore
vendored
175
.gitignore
vendored
|
@ -1,28 +1,149 @@
|
|||
*.pyc
|
||||
*.bak
|
||||
.DS_Store
|
||||
homeless.py
|
||||
playground.py
|
||||
cairo_test.py
|
||||
mayavi_test.py
|
||||
random_scenes/
|
||||
files/
|
||||
assets/
|
||||
ben_playground.py
|
||||
ben_cairo_test.py
|
||||
.floo
|
||||
.flooignore
|
||||
.vscode
|
||||
.vs
|
||||
*.xml
|
||||
*.iml
|
||||
media
|
||||
manim.sublime-project
|
||||
manim.sublime-workspace
|
||||
.eggs/
|
||||
build/
|
||||
dist/
|
||||
manim.egg-info/
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||
|
||||
primes.py
|
||||
/media_dir.txt
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
pytestdebug.log
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
doc/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
pythonenv*
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# profiling data
|
||||
.prof
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||
# Custom exclusions:
|
||||
.DS_Store
|
||||
|
||||
#
|
||||
/videos
|
30
.travis.yml
30
.travis.yml
|
@ -1,30 +0,0 @@
|
|||
language: python
|
||||
dist: bionic
|
||||
python: "3.7"
|
||||
cache: pip
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- python3-sphinx
|
||||
install:
|
||||
- pip install --upgrade pip
|
||||
- pip install -r requirements.txt
|
||||
- pip install flake8
|
||||
before_script:
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
- flake8 manimlib/ --count --select=E9,F63,F72,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
- flake8 manimlib/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
script:
|
||||
- python setup.py test
|
||||
- python setup.py bdist_wheel
|
||||
after_success:
|
||||
- test $TRAVIS_BRANCH = "master" && test $TRAVIS_PULL_REQUEST = "false" && travis/build_docs.sh
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: eulertour
|
||||
on:
|
||||
tags: true
|
||||
password:
|
||||
secure: j5M2hiJo9kDWJhl0/iSuIQmfd2G2O1Qoc455AkUPMCheAcALnX9xJgFsYBmqfgOXTCtUCQf52XGdOIG4o4s5TY340NZ9eLKI9cWae+sTeSrDCkdwChUilm3D0jQf1FWPUf9ywScwGi20m0sRtzxEJyTuX+JMFd7PIa8bFoDXWPtEjoFOOJrfBusMsANzrI+j+vIMdJ48lc1J8UsQdZapwusTrYU9s12JLhKBPLavmaDKf0HDAJdEhFQ9SaINdkiW/QY8qbfJ/MVu5jHai168zXjD/IaswxoKqCO1G+fWlOq3KwVhG7gI7rwhnnuF+wcA7yLAaMdo0CjO2V7z15S6cG721V2Il2IIh1jq0F8irSH1ZOLOkv/fFk9hkSUQyEU0i8k4m1wE9L47a6GP/66+b+gI91PGfxBOqq4gE/1BdZJqceh0qc13KpcehtYrQwR05bSw0Ye5OoTkqAnCeON0B0Ur4ejfHd3TzkjgB06fw76cZtjAK8f/YjB3KyNCvysOixgzE4tRxlY92yX/tAKZ3iX3yD0MjsinSfwo52N5sIEaCS/FmPRMhJOQBa6ftkfbcUNQBTG9G3b134XXF/LbC4vBloCaTm5VSXagta+oY3SFKQxPAZXx7X+wcFGjqxDjZXG1e66QnA2JJH4aBDsRfSXmUtD8MblwFYdcCJWz+Ck=
|
19
Dockerfile
19
Dockerfile
|
@ -1,19 +0,0 @@
|
|||
FROM python:3.7
|
||||
RUN apt-get update \
|
||||
&& apt-get install -qqy --no-install-recommends \
|
||||
apt-utils \
|
||||
ffmpeg \
|
||||
sox \
|
||||
libcairo2-dev \
|
||||
texlive \
|
||||
texlive-fonts-extra \
|
||||
texlive-latex-extra \
|
||||
texlive-latex-recommended \
|
||||
texlive-science \
|
||||
tipa \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY . /manim
|
||||
RUN cd /manim \
|
||||
&& python setup.py sdist \
|
||||
&& python -m pip install dist/manimlib*
|
||||
ENTRYPOINT ["/bin/bash"]
|
|
@ -1,10 +1,6 @@
|
|||
All files of this project under the directory "from_3b1b" are copyright 3Blue1Brown LLC and used by permission for this project only.
|
||||
|
||||
Any other file of this project is available under the MIT license as follow:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 3Blue1Brown LLC
|
||||
Copyright (c) 2020 3Blue1Brown LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
100
README.md
100
README.md
|
@ -1,30 +1,19 @@
|
|||

|
||||
|
||||
[](https://travis-ci.org/3b1b/manim)
|
||||
[](https://www.eulertour.com/docs)
|
||||
[](http://choosealicense.com/licenses/mit/)
|
||||
[](https://www.reddit.com/r/manim/)
|
||||
[](https://discord.gg/mMRrZQW)
|
||||
[](https://3b1b.github.io/manim/)
|
||||
|
||||
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos at [3Blue1Brown](https://www.3blue1brown.com/).
|
||||
|
||||
This repository contains the version of manim used by 3Blue1Brown. There is also a community maintained version at https://github.com/ManimCommunity/manim/.
|
||||
To get help or to join the development effort, please join the [discord](https://discord.gg/mMRrZQW).
|
||||
This repository contains the version of manim used by 3Blue1Brown. There is also a community maintained version at https://github.com/ManimCommunity/manim/. To get help or to join the development effort, please join the discord.
|
||||
|
||||
## Installation
|
||||
Manim runs on Python 3.6 or higher version. You can install it from PyPI via pip:
|
||||
Manim runs on Python 3.8.
|
||||
|
||||
```sh
|
||||
pip3 install manimlib
|
||||
```
|
||||
|
||||
System requirements are [cairo](https://www.cairographics.org), [ffmpeg](https://www.ffmpeg.org), [sox](http://sox.sourceforge.net) (optional, if you want to play the prompt tone after running), [latex](https://www.latex-project.org) (optional, if you want to use LaTeX).
|
||||
|
||||
You can now use it via the `manim` command. For example:
|
||||
|
||||
```sh
|
||||
manim my_project.py MyScene
|
||||
```
|
||||
System requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org//), [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX)
|
||||
and [cairo](https://www.cairographics.org/) (optional, if you want to use Text).
|
||||
|
||||
For more options, take a look at the [Using manim](#using-manim) sections further below.
|
||||
|
||||
|
@ -34,36 +23,28 @@ If you want to hack on manimlib itself, clone this repository and in that direct
|
|||
|
||||
```sh
|
||||
# Install python requirements
|
||||
python3 -m pip install -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Try it out
|
||||
python3 ./manim.py example_scenes.py SquareToCircle -pl
|
||||
python manim.py example_scenes.py OpeningManimExample
|
||||
```
|
||||
|
||||
### Directly (Windows)
|
||||
1. [Install FFmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows).
|
||||
2. [Install Cairo](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo). For most users, ``pycairo‑1.18.0‑cp37‑cp37m‑win32.whl`` will do fine (you can download it or other versions from [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo)).
|
||||
```sh
|
||||
pip3 install C:\path\to\wheel\pycairo‑1.18.0‑cp37‑cp37m‑win32.whl
|
||||
```
|
||||
3. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended.
|
||||
|
||||
4. [Install SoX](https://sourceforge.net/projects/sox/files/sox/).
|
||||
|
||||
5. Install the remaining Python packages.
|
||||
2. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended.
|
||||
3. Install the remaining Python packages.
|
||||
```sh
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
cd manim
|
||||
pip3 install -r requirements.txt
|
||||
python3 manim.py example_scenes.py SquareToCircle -pl
|
||||
pip install -r requirements.txt
|
||||
python manim.py example_scenes.py OpeningManimExample
|
||||
```
|
||||
|
||||
|
||||
## Anaconda Install
|
||||
|
||||
* Install sox and latex as above.
|
||||
* Install latex as above.
|
||||
* Create a conda environment using `conda env create -f environment.yml`
|
||||
* **WINDOWS ONLY** Install `pyreadline` via `pip install pyreadline`.
|
||||
|
||||
|
||||
### Using `virtualenv` and `virtualenvwrapper`
|
||||
|
@ -71,68 +52,41 @@ After installing `virtualenv` and `virtualenvwrapper`
|
|||
```sh
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
mkvirtualenv -a manim -r requirements.txt manim
|
||||
python3 -m manim example_scenes.py SquareToCircle -pl
|
||||
python -m manim example_scenes.py OpeningManimExample
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
Since it's a bit tricky to get all the dependencies set up just right, there is a Dockerfile and Compose file provided in this repo as well as [a premade image on Docker Hub](https://hub.docker.com/r/eulertour/manim/tags/). The Dockerfile contains instructions on how to build a manim image, while the Compose file contains instructions on how to run the image.
|
||||
|
||||
The prebuilt container image has manim repository included.
|
||||
`INPUT_PATH` is where the container looks for scene files. You must set the `INPUT_PATH`
|
||||
environment variable to the absolute path containing your scene file and the
|
||||
`OUTPUT_PATH` environment variable to the directory where you want media to be written.
|
||||
|
||||
1. [Install Docker](https://docs.docker.com)
|
||||
2. [Install Docker Compose](https://docs.docker.com/compose/install/)
|
||||
3. Render an animation:
|
||||
```sh
|
||||
INPUT_PATH=/path/to/dir/containing/source/code \
|
||||
OUTPUT_PATH=/path/to/output/ \
|
||||
docker-compose run manim example_scenes.py SquareToCircle -l
|
||||
```
|
||||
The command needs to be run as root if your username is not in the docker group.
|
||||
|
||||
You can replace `example.scenes.py` with any relative path from your `INPUT_PATH`.
|
||||
|
||||

|
||||
|
||||
After running the output will say files ready at `/tmp/output/`, which refers to path inside the container. Your `OUTPUT_PATH` is bind mounted to this `/tmp/output` so any changes made by the container to `/tmp/output` will be mirrored on your `OUTPUT_PATH`. `/media/` will be created in `OUTPUT_PATH`.
|
||||
|
||||
`-p` won't work as manim would look for video player in the container system, which it does not have.
|
||||
|
||||
The first time you execute the above command, Docker will pull the image from Docker Hub and cache it. Any subsequent runs until the image is evicted will use the cached image.
|
||||
Note that the image doesn't have any development tools installed and can't preview animations. Its purpose is building and testing only.
|
||||
|
||||
## Using manim
|
||||
Try running the following:
|
||||
```sh
|
||||
python3 -m manim example_scenes.py SquareToCircle -pl
|
||||
python -m manim example_scenes.py OpeningManimExample
|
||||
```
|
||||
The `-p` flag in the command above is for previewing, meaning the video file will automatically open when it is done rendering. The `-l` flag is for a faster rendering at a lower quality.
|
||||
This should pop up a window playing a simple scene.
|
||||
|
||||
Some other useful flags include:
|
||||
Some useful flags include:
|
||||
* `-w` to write the scene to a file
|
||||
* `-o` to write the scene to a file and open the result
|
||||
* `-s` to skip to the end and just show the final frame.
|
||||
* `-so` will save the final frame to an image and show it
|
||||
* `-n <number>` to skip ahead to the `n`'th animation of a scene.
|
||||
* `-f` to show the file in finder (for OSX).
|
||||
* `-f` to make the playback window fullscreen
|
||||
|
||||
Set `MEDIA_DIR` environment variable to specify where the image and animation files will be written.
|
||||
Take a look at custom_defaults.yml for further configuration. To add your customization, you can either edit this file, or add another file by the same name "custom_defaults.yml" to whatever directory you are running manim from. For example [this is the one](https://github.com/3b1b/videos/blob/master/custom_defaults.yml) for 3blue1brown videos. There you can specify where videos should be output to, where manim should look for image files and sounds you want to read in, and other defaults regarding style and video quality.
|
||||
|
||||
Look through the `old_projects` folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project.
|
||||
|
||||
While developing a scene, the `-sp` flags are helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to use the `-n` flag to skip over some number of animations.
|
||||
Look through [https://github.com/3b1b/videos](https://github.com/3b1b/videos) to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project.
|
||||
|
||||
### Documentation
|
||||
Documentation is in progress at [eulertour.com/docs](https://www.eulertour.com/docs/). And there is also an all-in-one documentation and tutorials maintained by **@manim-kindergarten**: [manim.ml](https://manim.ml/) (in Chinese).
|
||||
Documentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by **@manim-kindergarten**: [manim.ml](https://manim.ml/) (in Chinese).
|
||||
|
||||
### Walkthrough
|
||||
Todd Zimmerman put together a [tutorial](https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/) on getting started with manim, which has been updated to run on Python 3.7.
|
||||
|
||||
[manim-kindergarten](https://github.com/manim-kindergarten/) wrote and collected some useful extra classes and some codes of videos in [manim_sandbox repo](https://github.com/manim-kindergarten/manim_sandbox).
|
||||
|
||||
|
||||
## Contributing
|
||||
Only accepts pull requests that fixes bugs / fixes typos / improves existing content (for more information, see [#1243](https://github.com/3b1b/manim/issues/1243)). Most pull requests should be directed to the [community version](https://github.com/ManimCommunity/manim/).
|
||||
Is always welcome. In particular, there is a dire need for tests and documentation.
|
||||
|
||||
|
||||
## License
|
||||
All files in the directory `from_3b1b`, which by and large generate the visuals for 3b1b videos, are copyright 3Blue1Brown.
|
||||
|
||||
The general purpose animation code found in the remainder of the repository, on the other hand, is under the MIT license.
|
||||
This project falls under the MIT license.
|
||||
|
|
56
custom_defaults.yml
Normal file
56
custom_defaults.yml
Normal file
|
@ -0,0 +1,56 @@
|
|||
directories:
|
||||
# Set this to true if you want the path to video files
|
||||
# to match the directory structure of the path to the
|
||||
# sourcecode generating that video
|
||||
mirror_module_path: False
|
||||
# Where should manim output video and image files?
|
||||
output: ""
|
||||
# If you want to use images, manim will look to these folders to find them
|
||||
raster_images: ""
|
||||
vector_images: ""
|
||||
# If you want to use sounds, manim will look here to find it.
|
||||
sounds: ""
|
||||
# Manim often generates tex_files or other kinds of serialized data
|
||||
# to keep from having to generate the same thing too many times. By
|
||||
# default, these will be stored at tempfile.gettempdir(), e.g. this might
|
||||
# return whatever is at to the TMPDIR environment variable. If you want to
|
||||
# specify them elsewhere,
|
||||
temporary_storage: ""
|
||||
tex:
|
||||
executable: "latex"
|
||||
template_file: "tex_template.tex"
|
||||
intermediate_filetype: "dvi"
|
||||
text_to_replace: "[tex_expression]"
|
||||
# # For ctex, use the following configuration
|
||||
# executable: "xelatex -no-pdf"
|
||||
# template_file: "ctex_template.tex"
|
||||
# intermediate_filetype: "xdv"
|
||||
universal_import_line: "from manimlib.imports import *"
|
||||
style:
|
||||
font: "Consolas"
|
||||
background_color: "#333333"
|
||||
# Set the position of preview window, you can use directions, e.g. UL/DR/OL/OO/...
|
||||
# also, you can also specify the position(pixel) of the upper left corner of
|
||||
# the window on the monitor, e.g. "960,540"
|
||||
window_position: UR
|
||||
# If break_into_partial_movies is set to True, then many small
|
||||
# files will be written corresponding to each Scene.play and
|
||||
# Scene.wait call, and these files will then be combined
|
||||
# to form the full scene. Sometimes video-editing is made
|
||||
# easier when working with the broken up scene, which
|
||||
# effectively has cuts at all the places you might want.
|
||||
break_into_partial_movies: False
|
||||
camera_qualities:
|
||||
low:
|
||||
resolution: "854x480"
|
||||
frame_rate: 15
|
||||
medium:
|
||||
resolution: "1280x720"
|
||||
frame_rate: 30
|
||||
high:
|
||||
resolution: "1920x1080"
|
||||
frame_rate: 30
|
||||
ultra_high:
|
||||
resolution: "3840x2160"
|
||||
frame_rate: 60
|
||||
default_quality: "high"
|
|
@ -1,16 +0,0 @@
|
|||
version: '3.1'
|
||||
|
||||
services:
|
||||
manim:
|
||||
# comment this line if you build the image to prevent overwriting the tag
|
||||
image: eulertour/manim:latest
|
||||
# uncomment this line to build rather than pull the image
|
||||
# build: .
|
||||
entrypoint:
|
||||
- manim
|
||||
- --media_dir=/tmp/output
|
||||
volumes:
|
||||
- ${INPUT_PATH:?INPUT_PATH environment variable isn't set}:/tmp/input
|
||||
- ${OUTPUT_PATH:?OUTPUT_PATH environment variable isn't set}:/tmp/output
|
||||
working_dir: /tmp/input
|
||||
network_mode: "none"
|
|
@ -1,9 +1,10 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
|
@ -16,4 +17,4 @@ help:
|
|||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
|
37
docs/example.py
Normal file
37
docs/example.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from manimlib.imports import *
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
square = Square()
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(square, circle))
|
||||
self.wait()
|
||||
# Try typing the following lines
|
||||
# self.play(circle.stretch, 4, {"dim": 0})
|
||||
# self.play(Rotate(circle, TAU / 4))
|
||||
# self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25)
|
||||
# circle.insert_n_curves(10)
|
||||
# self.play(circle.apply_complex_function, lambda z: z**2)
|
||||
|
||||
class SquareToCircleEmbed(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
|
||||
self.add(circle)
|
||||
self.wait()
|
||||
self.play(circle.stretch, 4, {"dim": 0})
|
||||
self.wait(1.5)
|
||||
self.play(Rotate(circle, TAU / 4))
|
||||
self.wait(1.5)
|
||||
self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25)
|
||||
self.wait(1.5)
|
||||
circle.insert_n_curves(10)
|
||||
self.play(circle.apply_complex_function, lambda z: z**2)
|
||||
self.wait(2)
|
|
@ -1,35 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
|
|
4
docs/requirements.txt
Normal file
4
docs/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
Sphinx==3.0.3
|
||||
sphinx-copybutton
|
||||
furo==2020.10.5b9
|
||||
Jinja2
|
293
docs/source/_static/colors.css
Normal file
293
docs/source/_static/colors.css
Normal file
|
@ -0,0 +1,293 @@
|
|||
p.color-text {
|
||||
font-size: inherit;
|
||||
font-family: var(--font-stack--monospace);
|
||||
margin-top: 25px;
|
||||
color: WHITE;
|
||||
}
|
||||
|
||||
p.color-text-small {
|
||||
font-size: small;
|
||||
font-family: var(--font-stack--monospace);
|
||||
margin-top: 28px;
|
||||
color: WHITE;
|
||||
}
|
||||
|
||||
.colors {
|
||||
float: left;
|
||||
padding: 10px;
|
||||
border: 10px;
|
||||
margin: 0;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.BLUE_A {
|
||||
background: #C7E9F1;
|
||||
color:#C7E9F1;
|
||||
}
|
||||
|
||||
.BLUE_B {
|
||||
background: #9CDCEB;
|
||||
color:#9CDCEB;
|
||||
}
|
||||
|
||||
.BLUE_C {
|
||||
background: #58C4DD;
|
||||
color:#58C4DD;
|
||||
}
|
||||
|
||||
.BLUE_D {
|
||||
background: #29ABCA;
|
||||
color:#29ABCA;
|
||||
}
|
||||
|
||||
.BLUE_E {
|
||||
background: #1C758A;
|
||||
color:#1C758A;
|
||||
}
|
||||
|
||||
.TEAL_A {
|
||||
background: #ACEAD7;
|
||||
color:#ACEAD7 ;
|
||||
}
|
||||
|
||||
.TEAL_B {
|
||||
background: #76DDC0;
|
||||
color: #76DDC0;
|
||||
}
|
||||
|
||||
.TEAL_C {
|
||||
background: #5CD0B3;
|
||||
color: #5CD0B3;
|
||||
}
|
||||
|
||||
.TEAL_D {
|
||||
background: #55C1A7;
|
||||
color: #55C1A7;
|
||||
}
|
||||
|
||||
.TEAL_E {
|
||||
background: #49A88F;
|
||||
color: #49A88F;
|
||||
}
|
||||
|
||||
.GREEN_A {
|
||||
background: #C9E2AE;
|
||||
color: #C9E2AE;
|
||||
}
|
||||
|
||||
.GREEN_B {
|
||||
background: #A6CF8C;
|
||||
color: #A6CF8C;
|
||||
}
|
||||
|
||||
.GREEN_C {
|
||||
background: #83C167;
|
||||
color: #83C167;
|
||||
}
|
||||
|
||||
.GREEN_D {
|
||||
background: #77B05D;
|
||||
color: #77B05D;
|
||||
}
|
||||
|
||||
.GREEN_E {
|
||||
background: #699C52;
|
||||
color: #699C52;
|
||||
}
|
||||
|
||||
.YELLOW_A {
|
||||
background: #FFF1B6;
|
||||
color: #FFF1B6;
|
||||
}
|
||||
|
||||
.YELLOW_B {
|
||||
background: #FFEA94;
|
||||
color:#FFEA94 ;
|
||||
}
|
||||
|
||||
.YELLOW_C {
|
||||
background: #FFFF00;
|
||||
color: #FFFF00;
|
||||
}
|
||||
|
||||
.YELLOW_D {
|
||||
background: #F4D345;
|
||||
color: #F4D345;
|
||||
}
|
||||
|
||||
.YELLOW_E {
|
||||
background: #E8C11C;
|
||||
color: #E8C11C;
|
||||
}
|
||||
|
||||
.GOLD_A {
|
||||
background: #F7C797;
|
||||
color:#F7C797;
|
||||
}
|
||||
|
||||
.GOLD_B {
|
||||
background: #F9B775;
|
||||
color:#F9B775;
|
||||
}
|
||||
|
||||
.GOLD_C {
|
||||
background: #F0AC5F;
|
||||
color:#F0AC5F;
|
||||
}
|
||||
|
||||
.GOLD_D {
|
||||
background: #E1A158;
|
||||
color:#E1A158;
|
||||
}
|
||||
|
||||
.GOLD_E {
|
||||
background: #C78D46;
|
||||
color:#C78D46;
|
||||
}
|
||||
|
||||
.RED_A {
|
||||
background: #F7A1A3;
|
||||
color:#F7A1A3;
|
||||
}
|
||||
|
||||
.RED_B {
|
||||
background: #FF8080;
|
||||
color:#FF8080;
|
||||
}
|
||||
|
||||
.RED_C {
|
||||
background: #FC6255;
|
||||
color:#FC6255;
|
||||
}
|
||||
|
||||
.RED_D {
|
||||
background: #E65A4C;
|
||||
color:#E65A4C;
|
||||
}
|
||||
|
||||
.RED_E {
|
||||
background: #CF5044;
|
||||
color:#CF5044;
|
||||
}
|
||||
|
||||
.MAROON_A {
|
||||
background: #ECABC1;
|
||||
color: #ECABC1;
|
||||
}
|
||||
|
||||
.MAROON_B {
|
||||
background: #EC92AB;
|
||||
color: #EC92AB;
|
||||
}
|
||||
|
||||
.MAROON_C {
|
||||
background: #C55F73;
|
||||
color: #C55F73;
|
||||
}
|
||||
|
||||
.MAROON_D {
|
||||
background: #A24D61;
|
||||
color: #A24D61;
|
||||
}
|
||||
|
||||
.MAROON_E {
|
||||
background: #94424F;
|
||||
color: #94424F;
|
||||
}
|
||||
|
||||
.PURPLE_A {
|
||||
background: #CAA3E8;
|
||||
color: #CAA3E8;
|
||||
}
|
||||
|
||||
.PURPLE_B {
|
||||
background: #B189C6;
|
||||
color: #B189C6;
|
||||
}
|
||||
|
||||
.PURPLE_C {
|
||||
background: #9A72AC;
|
||||
color: #9A72AC;
|
||||
}
|
||||
|
||||
.PURPLE_D {
|
||||
background: #715582;
|
||||
color: #715582;
|
||||
}
|
||||
|
||||
.PURPLE_E {
|
||||
background: #644172;
|
||||
color: #644172;
|
||||
}
|
||||
|
||||
.GREY_A {
|
||||
background: #DDDDDD;
|
||||
color: #DDDDDD;
|
||||
}
|
||||
|
||||
.GREY_B {
|
||||
background: #BBBBBB;
|
||||
color: #BBBBBB;
|
||||
}
|
||||
|
||||
.GREY_C {
|
||||
background: #888888;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.GREY_D {
|
||||
background: #444444;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
.GREY_E {
|
||||
background: #222222;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.WHITE {
|
||||
background: #FFFFFF;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.BLACK {
|
||||
background: #000000;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.GREY_BROWN {
|
||||
background: #736357;
|
||||
color: #736357;
|
||||
}
|
||||
|
||||
.DARK_BROWN {
|
||||
background: #8B4513;
|
||||
color: #8B4513;
|
||||
}
|
||||
|
||||
.LIGHT_BROWN {
|
||||
background: #CD853F;
|
||||
color: #CD853F;
|
||||
}
|
||||
|
||||
.PINK {
|
||||
background: #D147BD;
|
||||
color: #D147BD;
|
||||
}
|
||||
|
||||
.LIGHT_PINK {
|
||||
background: #DC75CD;
|
||||
color: #DC75CD;
|
||||
}
|
||||
|
||||
.GREEN_SCREEN {
|
||||
background: #00FF00;
|
||||
color: #00FF00;
|
||||
}
|
||||
|
||||
.ORANGE {
|
||||
background: #FF862F;
|
||||
color: #FF862F;
|
||||
}
|
62
docs/source/_static/custom.css
Normal file
62
docs/source/_static/custom.css
Normal file
|
@ -0,0 +1,62 @@
|
|||
p {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
span.caption-text {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
span.pre {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
.highlight-python.notranslate {
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
.manim-video {
|
||||
width: 99.9%;
|
||||
padding: 8px 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.manim-example {
|
||||
background-color: #333333;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 2px 2px 4px #ddd;
|
||||
}
|
||||
|
||||
.manim-example .manim-video {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.manim-example img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h5.example-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding: 8px 16px;
|
||||
color: white;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
text-transform: none;
|
||||
margin-top: -0.4em;
|
||||
margin-bottom: -0.2em;
|
||||
}
|
||||
|
||||
.manim-example .highlight {
|
||||
background-color: #fafafa;
|
||||
border: 2px solid #333333;
|
||||
padding: 8px 8px 10px 8px;
|
||||
font-size: large;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.manim-example .highlight pre {
|
||||
background-color: inherit;
|
||||
border-left: none;
|
||||
margin: 0;
|
||||
padding: 0 6px 0 6px;
|
||||
}
|
BIN
docs/source/_static/example_scenes/AnimatingMethods.mp4
Normal file
BIN
docs/source/_static/example_scenes/AnimatingMethods.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/example_scenes/OpeningManimExample.mp4
Normal file
BIN
docs/source/_static/example_scenes/OpeningManimExample.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/example_scenes/SquareToCircle.mp4
Normal file
BIN
docs/source/_static/example_scenes/SquareToCircle.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/example_scenes/SurfaceExample.mp4
Normal file
BIN
docs/source/_static/example_scenes/SurfaceExample.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/example_scenes/TexTransformExample.mp4
Normal file
BIN
docs/source/_static/example_scenes/TexTransformExample.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/example_scenes/TextExample.mp4
Normal file
BIN
docs/source/_static/example_scenes/TextExample.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/example_scenes/UpdatersExample.mp4
Normal file
BIN
docs/source/_static/example_scenes/UpdatersExample.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/icon.png
Normal file
BIN
docs/source/_static/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 184 KiB |
BIN
docs/source/_static/manim_shaders_process_en.png
Normal file
BIN
docs/source/_static/manim_shaders_process_en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 603 KiB |
BIN
docs/source/_static/manim_shaders_structure.png
Normal file
BIN
docs/source/_static/manim_shaders_structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/source/_static/quickstart/SquareToCircle.mp4
Normal file
BIN
docs/source/_static/quickstart/SquareToCircle.mp4
Normal file
Binary file not shown.
BIN
docs/source/_static/quickstart/SquareToCircle.png
Normal file
BIN
docs/source/_static/quickstart/SquareToCircle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
docs/source/_static/quickstart/SquareToCircleEmbed.mp4
Normal file
BIN
docs/source/_static/quickstart/SquareToCircleEmbed.mp4
Normal file
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
About
|
||||
=====
|
||||
|
||||
Animating technical concepts is traditionally pretty tedious, since it can be
|
||||
difficult to make the animations precise enough to convey them accurately.
|
||||
``Manim`` uses Python to generate animations programmatically, which makes it
|
||||
possible to specify exactly how each one should run.
|
||||
|
||||
This project is still very much a work in progress, but I hope that the
|
||||
information here will make it easier for newcomers to get started using
|
||||
``Manim``.
|
|
@ -1,210 +0,0 @@
|
|||
Animation
|
||||
=========
|
||||
|
||||
|
||||
|
||||
The simplest of which is ``Scene.add``. The object appears on the first frame
|
||||
without any animation::
|
||||
|
||||
class NoAnimation(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.add(square))
|
||||
|
||||
Animation are used in conjunction with ``scene.Play``
|
||||
|
||||
Fade
|
||||
----
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeIn.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeIn(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
|
||||
anno = TextMobject("Fade In")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
self.play(FadeIn(square))
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeOut.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeOut(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
|
||||
anno = TextMobject("Fade Out")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
self.add(square)
|
||||
self.play(FadeOut(square))
|
||||
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeInFrom.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeInFrom(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
for label, edge in zip(
|
||||
["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN]
|
||||
):
|
||||
anno = TextMobject(f"Fade In from {label}")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
|
||||
self.play(FadeInFrom(square, edge))
|
||||
self.remove(anno, square)
|
||||
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeOutAndShift.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeOutAndShift(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
for label, edge in zip(
|
||||
["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN]
|
||||
):
|
||||
anno = TextMobject(f"Fade Out and shift {label}")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
|
||||
self.play(FadeOutAndShift(square, edge))
|
||||
self.remove(anno, square)
|
||||
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeInFromLarge.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeInFromLarge(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
|
||||
for factor in [0.1, 0.5, 0.8, 1, 2, 5]:
|
||||
anno = TextMobject(f"Fade In from large scale\_factor={factor}")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
|
||||
self.play(FadeInFromLarge(square, scale_factor=factor))
|
||||
self.remove(anno, square)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeInFromPoint.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeInFromPoint(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
for i in range(-6, 7, 2):
|
||||
anno = TextMobject(f"Fade In from point {i}")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
self.play(FadeInFromPoint(square, point=i))
|
||||
self.remove(anno, square)
|
||||
|
||||
|
||||
|
||||
Grow
|
||||
----
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationGrowFromEdge.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationGrowFromEdge(Scene):
|
||||
def construct(self):
|
||||
|
||||
for label, edge in zip(
|
||||
["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN]
|
||||
):
|
||||
anno = TextMobject(f"Grow from {label} edge")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
square = Square()
|
||||
self.play(GrowFromEdge(square, edge))
|
||||
self.remove(anno, square)
|
||||
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationGrowFromCenter.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationGrowFromCenter(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
|
||||
anno = TextMobject("Grow from center")
|
||||
anno.shift(2 * DOWN)
|
||||
self.add(anno)
|
||||
|
||||
self.play(GrowFromCenter(square))
|
||||
|
||||
|
||||
|
||||
|
||||
Diagonal Directions
|
||||
-------------------
|
||||
|
||||
You can combine cardinal directions to form diagonal animations
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="_static/AnimationFadeInFromDiagonal.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class AnimationFadeInFromDiagonal(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
for diag in [UP + LEFT, UP + RIGHT, DOWN + LEFT, DOWN + RIGHT]:
|
||||
self.play(FadeInFrom(square, diag))
|
||||
|
||||
.. note::
|
||||
You can also use the abbreviated forms like ``UL, UR, DL, DR``.
|
||||
See :ref:`ref-directions`.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
@ -1,53 +1,40 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath("."))
|
||||
sys.path.insert(0, os.path.abspath('../../'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
project = 'manim'
|
||||
copyright = '- This document has been placed in the public domain.'
|
||||
author = 'TonyCrane'
|
||||
|
||||
project = 'Manim'
|
||||
copyright = '2019, EulerTour'
|
||||
author = 'EulerTour'
|
||||
release = ''
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
master_doc = 'index'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.githubpages',
|
||||
'sphinx.ext.mathjax',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinx_copybutton',
|
||||
'manim_example_ext'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
autoclass_content = 'both'
|
||||
mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
|
||||
|
||||
templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
pygments_style = 'default'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['assets']
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = ["custom.css", "colors.css"]
|
||||
html_theme = 'furo' # pip install furo==2020.10.5b9
|
||||
html_favicon = '_static/icon.png'
|
||||
html_logo = '../../logo/transparent_graph.png'
|
||||
html_theme_options = {
|
||||
"sidebar_hide_name": True,
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
Manim Constants
|
||||
===============
|
||||
|
||||
The ``constants.py`` under ``manimlib/`` contains variables that are used
|
||||
during setup and running manim. Some variables are not documented here as they are
|
||||
only used internally by manim.
|
||||
|
||||
Directories
|
||||
-----------
|
||||
|
||||
MEDIA_DIR
|
||||
The directory where ``VIDEO_DIR`` and ``TEX_DIR`` will be created,
|
||||
if they aren't specified via flags.
|
||||
VIDEO_DIR
|
||||
Used to store the scenes rendered by Manim. When a scene is
|
||||
finished rendering, it will be stored under
|
||||
``VIDEO_DIR/module_name/scene_name/quality/scene_name.mp4``.
|
||||
Created under ``MEDIA_DIR`` by default.
|
||||
TEX_DIR
|
||||
Files written by Latex are stored here. It also acts as a cache
|
||||
so that the files aren't rewritten each time Latex is needed.
|
||||
|
||||
Those directories are created if they don't exist.
|
||||
|
||||
Tex
|
||||
---
|
||||
TEX_USE_CTEX
|
||||
A boolean value. Change it to True if you need to use Chinese typesetting.
|
||||
TEX_TEXT_TO_REPLACE
|
||||
Placeholder text used by manim when generating tex files
|
||||
TEMPLATE_TEX_FILE
|
||||
By default ``manimlib/tex_template.tex`` is used. If ``TEX_USE_CTEX``
|
||||
is set to True then ``manimlib/ctex_template.tex`` is used.
|
||||
|
||||
Numerical Constants
|
||||
-------------------
|
||||
|
||||
PI
|
||||
alias to ``numpy.pi``
|
||||
TAU
|
||||
PI * 2
|
||||
|
||||
DEGREES
|
||||
TAU / 360
|
||||
|
||||
Camera Configuration
|
||||
--------------------
|
||||
|
||||
Render setting presets
|
||||
|
||||
PRODUCTION_QUALITY_CAMERA_CONFIG
|
||||
2560x1440 @ 60fps # This is the default when rendering a scene
|
||||
HIGH_QUALITY_CAMERA_CONFIG
|
||||
1920x1080 @ 60fps. # Used when the ``-h`` or ``--high_quality`` flag
|
||||
is passed.
|
||||
MEDIUM_QUALITY_CAMERA_CONFIG
|
||||
1280x720 @ 30fps. # Used when the ``-m`` or ``--medium_quality``
|
||||
flag is passed.
|
||||
LOW_QUALITY_CAMERA_CONFIG
|
||||
854x480 @ 15fps. # Used when the ``-l`` or ``--low_quality`` flag is
|
||||
passed.
|
||||
|
||||
.. _ref-directions:
|
||||
|
||||
Coordinates
|
||||
-----------
|
||||
|
||||
Used for 2d/3d animations and placements::
|
||||
|
||||
ORIGIN
|
||||
UP
|
||||
DOWN
|
||||
RIGHT
|
||||
LEFT
|
||||
IN # 3d camera only, away from camera
|
||||
OUT # 3d camera only, close to camera
|
||||
|
||||
UL = UP + LEFT # diagonal abbreviations. You can use either one
|
||||
UR = UP + RIGHT
|
||||
DL = DOWN + LEFT
|
||||
DR = DOWN + RIGHT
|
||||
|
||||
TOP
|
||||
BOTTOM
|
||||
LEFT_SIDE
|
||||
RIGHT_SIDE``
|
||||
|
||||
Colors
|
||||
------
|
||||
|
||||
COLOR_MAP
|
||||
A predefined color maps
|
||||
PALETTE
|
||||
A list of color hex strings, derived from COLOR_MAP
|
|
@ -1,178 +0,0 @@
|
|||
Coordinate
|
||||
==========
|
||||
|
||||
By default, the scene in manim is made up by 8 x 14 grid. The grid is addressed using a numpy
|
||||
array in the form of [x, y, z]. For 2D animations only the x and y axes are used.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class DotMap(Scene):
|
||||
def construct(self):
|
||||
dots = dict()
|
||||
annos = dict()
|
||||
var_index = 0
|
||||
for x in range(-7, 8):
|
||||
for y in range(-4, 5):
|
||||
annos[f"{x}{y}"] = TexMobject(f"({x}, {y})")
|
||||
dots[f"{var_index}"] = Dot(np.array([x, y, 0]))
|
||||
var_index = var_index + 1
|
||||
for anno, dot in zip(annos.values(), dots.values()):
|
||||
self.add(anno)
|
||||
self.add(dot)
|
||||
self.wait(0.2)
|
||||
self.remove(anno)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="700" height="394" controls>
|
||||
<source src="_static/coordinate/DotMap.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
.. note::
|
||||
You can place objects outside this boundary, but it won't show up in the render.
|
||||
|
||||
Using Coordinates
|
||||
-----------------
|
||||
|
||||
Coordinates are used for creating geometries (`VMobject` in manim) and animations.
|
||||
|
||||
Here coordinates are used to create this Polygon
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class CoorPolygon(Scene):
|
||||
def construct(self):
|
||||
for x in range(-7, 8):
|
||||
for y in range(-4, 5):
|
||||
self.add(Dot(np.array([x, y, 0]), color=DARK_GREY))
|
||||
polygon = Polygon(
|
||||
np.array([3, 2, 0]),
|
||||
np.array([1, -1, 0]),
|
||||
np.array([-5, -4, 0]),
|
||||
np.array([-4, 4, 0]))
|
||||
self.add(polygon)
|
||||
|
||||
|
||||
.. Image:: assets/coordinate/CoorPolygon.png
|
||||
:width: 700px
|
||||
|
||||
Coordinate Aliasing
|
||||
-------------------
|
||||
|
||||
From some animations typing a ``np.array`` everytime you need a coordinate can be tedious.
|
||||
Manim provides aliases to the most common coordinates::
|
||||
|
||||
UP == np.array([0, 1, 0])
|
||||
DOWN == np.array([0, -1, 0])
|
||||
LEFT == np.array([-1, 0, 0])
|
||||
RIGHT == np.array([1, 0, 0])
|
||||
UL == np.array([-1, 1, 0])
|
||||
DL == np.array([-1, -1, 0])
|
||||
UR == np.array([1, 1, 0])
|
||||
DR == np.array([1, -1, 0])
|
||||
|
||||
Here coordinates are used for animations
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class CoorAlias(Scene):
|
||||
def construct(self):
|
||||
for x in range(-7, 8):
|
||||
for y in range(-4, 5):
|
||||
self.add(Dot(np.array([x, y, 0]), color=DARK_GREY))
|
||||
|
||||
aliases = {
|
||||
"UP": UP,
|
||||
"np.array([0,1,0])": np.array([0, 1, 0]),
|
||||
"DOWN": DOWN,
|
||||
"np.array([0,-1,0])": np.array([0, -1, 0]),
|
||||
"LEFT": LEFT,
|
||||
"np.array([-1,0,0])": np.array([-1, 0, 0]),
|
||||
"RIGHT": RIGHT,
|
||||
"np.array([1,0,0])": np.array([1, 0, 0]),
|
||||
"UL": UL,
|
||||
"np.array([-1,1,0])": np.array([-1, 1, 0]),
|
||||
"DL": DL,
|
||||
"np.array([-1,-1,0])": np.array([-1, -1, 0]),
|
||||
"UR": UR,
|
||||
"np.array([1,1,0])": np.array([1, 1, 0]),
|
||||
"DR": DR,
|
||||
"np.array([1,-1,0])": np.array([1, -1, 0])}
|
||||
circle = Circle(color=RED, radius=0.5)
|
||||
self.add(circle)
|
||||
self.wait(0.5)
|
||||
|
||||
for text, aliase in aliases.items():
|
||||
anno = TexMobject(f"\\texttt{{{text}}}")
|
||||
self.play(Write(anno, run_time=0.2))
|
||||
self.play(ApplyMethod(circle.shift, aliase))
|
||||
self.wait(0.2)
|
||||
self.play(FadeOut(anno, run_time=0.2))
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="700" height="394" controls>
|
||||
<source src="_static/coordinate/CoorAlias.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
Coordinate Arithmetic
|
||||
---------------------
|
||||
|
||||
Numpy array allows arithmetic operations::
|
||||
|
||||
>>> numpy.array([2,2,0]) + 4
|
||||
array([6, 6, 4])
|
||||
|
||||
>>> np.array([1, -3, 0]) + np.array([-4, 2, 0])
|
||||
array([-3, -1, 0])
|
||||
|
||||
>>> np.array([2, 2, 0]) - np.array([3,6, 0])
|
||||
array([-1, -4, 0])
|
||||
|
||||
>>> numpy.array([2,2,0]) - 3
|
||||
array([-1, -1, -3])
|
||||
|
||||
>>> np.array([1, -3, 0]) * 3
|
||||
array([ 3, -9, 0])
|
||||
|
||||
>>> numpy.array([2,2,0]) / 2
|
||||
array([1., 1., 0.])
|
||||
|
||||
>>> numpy.array([2,2,0]) / numpy.array([1, 4, 0])
|
||||
__main__:1: RuntimeWarning: invalid value encountered in true_divide
|
||||
array([2. , 0.5, nan])
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class CoorArithmetic(Scene):
|
||||
def construct(self):
|
||||
for x in range(-7, 8):
|
||||
for y in range(-4, 5):
|
||||
self.add(Dot(np.array([x, y, 0]), color=DARK_GREY))
|
||||
|
||||
circle = Circle(color=RED, radius=0.5)
|
||||
self.add(circle)
|
||||
self.wait(0.5)
|
||||
|
||||
aliases = {
|
||||
"LEFT * 3": LEFT * 3,
|
||||
"UP + RIGHT / 2": UP + RIGHT / 2,
|
||||
"DOWN + LEFT * 2": DOWN + LEFT * 2,
|
||||
"RIGHT * 3.75 * DOWN": RIGHT * 3.75 * DOWN,
|
||||
# certain arithmetic won't work as you expected
|
||||
# In [4]: RIGHT * 3.75 * DOWN
|
||||
# Out[4]: array([ 0., -0., 0.])
|
||||
"RIGHT * 3.75 + DOWN": RIGHT * 3.75 + DOWN}
|
||||
|
||||
for text, aliase in aliases.items():
|
||||
anno = TexMobject(f"\\texttt{{{text}}}")
|
||||
self.play(Write(anno, run_time=0.2))
|
||||
self.play(ApplyMethod(circle.shift, aliase))
|
||||
self.wait(0.2)
|
||||
self.play(FadeOut(anno, run_time=0.2))
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="700" height="394" controls>
|
||||
<source src="_static/coordinate/CoorArithmetic.mp4" type="video/mp4">
|
||||
</video>
|
31
docs/source/development/about.rst
Normal file
31
docs/source/development/about.rst
Normal file
|
@ -0,0 +1,31 @@
|
|||
About
|
||||
=====
|
||||
|
||||
About Manim
|
||||
-----------
|
||||
|
||||
Manim is an animation engine for explanatory math videos.
|
||||
You can use it to make math videos (or other fields) like 3Blue1Brown.
|
||||
|
||||
There are mainly two versions here:
|
||||
|
||||
- `3b1b/manim <https://github.com/3b1b/manim>`_ : Maintained by Grant Sanderson of 3Blue1Brown.
|
||||
|
||||
Using OpenGL and its GLSL language to use GPU for rendering. It has higher efficiency,
|
||||
faster rendering speed, and supports real-time rendering and interaction.
|
||||
|
||||
- `ManimCommunity/manim <https://github.com/ManimCommunity/manim>`_ : Maintained by Manim Community Dev Team.
|
||||
|
||||
Using cairo to use CPU for rendering. There is better documentation and
|
||||
a more open contribution community.
|
||||
|
||||
About this documentation
|
||||
------------------------
|
||||
|
||||
This documentation is based on the version in `3b1b/manim <https://github.com/3b1b/manim>`_.
|
||||
Created by `TonyCrane <https://github.com/TonyCrane>`_ ("鹤翔万里" in Chinese) and in production.
|
||||
|
||||
Among them, the ``manim_example_ext`` extension for Sphinx refers to
|
||||
`the documentation of ManimCommunity <https://docs.manim.community/>`_.
|
||||
|
||||
If you want to contribute to manim or this document, please see: :doc:`contributing`
|
4
docs/source/development/changelog.rst
Normal file
4
docs/source/development/changelog.rst
Normal file
|
@ -0,0 +1,4 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
No changes now.
|
59
docs/source/development/contributing.rst
Normal file
59
docs/source/development/contributing.rst
Normal file
|
@ -0,0 +1,59 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
Accept any contribution you make :)
|
||||
|
||||
- **Contribute to the manim source code**:
|
||||
|
||||
Please fork to your own repository and make changes, submit a pull request, and fill in
|
||||
the motivation for the change following the instructions in the template. We will check
|
||||
your pull request in detail (this usually takes a while, please be patient)
|
||||
|
||||
- **Contribute to the documentation**:
|
||||
|
||||
Also submit a pull request and write down the main changes.
|
||||
|
||||
- **If you find a bug in the code**:
|
||||
|
||||
Please open an issue and fill in the found problem and your environment according
|
||||
to the template. (But please note that if you think this problem is just a problem
|
||||
of yourself, rather than a problem of source code, it is recommended that you ask a
|
||||
question in the `Q&A category <https://github.com/3b1b/manim/discussions/categories/q-a>`_
|
||||
of the discussion page)
|
||||
|
||||
- **You are welcome to share the content you made with manim**:
|
||||
|
||||
Post it in the `show and tell category <https://github.com/3b1b/manim/discussions/categories/show-and-tell>`_
|
||||
of the discussion page.
|
||||
|
||||
- **You are also welcome to share some of your suggestions and ideas**:
|
||||
|
||||
Post them in the `ideas category <https://github.com/3b1b/manim/discussions/categories/ideas>`_
|
||||
of the discussion page.
|
||||
|
||||
How to build this documentation
|
||||
-------------------------------
|
||||
|
||||
- Clone the 3b1b/manim repository
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
# or your own repo
|
||||
# git clone https://github.com/<your user name>/manim.git
|
||||
cd manim
|
||||
|
||||
- Install python package dependencies
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install -r docs/requirements.txt
|
||||
|
||||
- Go to the ``docs/`` folder and build
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cd docs/
|
||||
make html
|
||||
|
||||
- The output document is located in ``docs/build/html/``
|
2
docs/source/documentation/animation/index.rst
Normal file
2
docs/source/documentation/animation/index.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Animation (TODO)
|
||||
================
|
2
docs/source/documentation/camera/index.rst
Normal file
2
docs/source/documentation/camera/index.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Camera (TODO)
|
||||
=============
|
192
docs/source/documentation/constants.rst
Normal file
192
docs/source/documentation/constants.rst
Normal file
|
@ -0,0 +1,192 @@
|
|||
constants
|
||||
=========
|
||||
|
||||
The ``constants.py`` in the ``manimlib`` folder defines the constants
|
||||
needed when running manim. Some constants are not explained here because
|
||||
they are only used inside manim.
|
||||
|
||||
Frame and pixel shape
|
||||
---------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ASPECT_RATIO = 16.0 / 9.0
|
||||
FRAME_HEIGHT = 8.0
|
||||
FRAME_WIDTH = FRAME_HEIGHT * ASPECT_RATIO
|
||||
FRAME_Y_RADIUS = FRAME_HEIGHT / 2
|
||||
FRAME_X_RADIUS = FRAME_WIDTH / 2
|
||||
|
||||
DEFAULT_PIXEL_HEIGHT = 1080
|
||||
DEFAULT_PIXEL_WIDTH = 1920
|
||||
DEFAULT_FRAME_RATE = 30
|
||||
|
||||
Buffs
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
SMALL_BUFF = 0.1
|
||||
MED_SMALL_BUFF = 0.25
|
||||
MED_LARGE_BUFF = 0.5
|
||||
LARGE_BUFF = 1
|
||||
|
||||
DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_LARGE_BUFF # Distance between object and edge
|
||||
DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF # Distance between objects
|
||||
|
||||
Run times
|
||||
---------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0
|
||||
DEFAULT_WAIT_TIME = 1.0
|
||||
|
||||
Coordinates
|
||||
-----------
|
||||
|
||||
manim uses three-dimensional coordinates and uses the type of ``ndarray``
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ORIGIN = np.array((0., 0., 0.))
|
||||
UP = np.array((0., 1., 0.))
|
||||
DOWN = np.array((0., -1., 0.))
|
||||
RIGHT = np.array((1., 0., 0.))
|
||||
LEFT = np.array((-1., 0., 0.))
|
||||
IN = np.array((0., 0., -1.))
|
||||
OUT = np.array((0., 0., 1.))
|
||||
X_AXIS = np.array((1., 0., 0.))
|
||||
Y_AXIS = np.array((0., 1., 0.))
|
||||
Z_AXIS = np.array((0., 0., 1.))
|
||||
|
||||
# Useful abbreviations for diagonals
|
||||
UL = UP + LEFT
|
||||
UR = UP + RIGHT
|
||||
DL = DOWN + LEFT
|
||||
DR = DOWN + RIGHT
|
||||
|
||||
TOP = FRAME_Y_RADIUS * UP
|
||||
BOTTOM = FRAME_Y_RADIUS * DOWN
|
||||
LEFT_SIDE = FRAME_X_RADIUS * LEFT
|
||||
RIGHT_SIDE = FRAME_X_RADIUS * RIGHT
|
||||
|
||||
Mathematical constant
|
||||
---------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
PI = np.pi
|
||||
TAU = 2 * PI
|
||||
DEGREES = TAU / 360
|
||||
|
||||
Text
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
START_X = 30
|
||||
START_Y = 20
|
||||
NORMAL = "NORMAL"
|
||||
ITALIC = "ITALIC"
|
||||
OBLIQUE = "OBLIQUE"
|
||||
BOLD = "BOLD"
|
||||
|
||||
Stroke width
|
||||
------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
DEFAULT_STROKE_WIDTH = 4
|
||||
|
||||
Colours
|
||||
-------
|
||||
|
||||
Here are the preview of default colours. (Modified from
|
||||
`elteoremadebeethoven <https://elteoremadebeethoven.github.io/manim_3feb_docs.github.io/html/_static/colors/colors.html>`_)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="float: left;">
|
||||
<h3>BLUE</h3>
|
||||
<div class="colors BLUE_E"><p class="color-text">BLUE_E</p></div>
|
||||
<div class="colors BLUE_D"><p class="color-text">BLUE_D</p></div>
|
||||
<div class="colors BLUE_C"><p class="color-text">BLUE_C</p></div>
|
||||
<div class="colors BLUE_B"><p class="color-text">BLUE_B</p></div>
|
||||
<div class="colors BLUE_A"><p class="color-text">BLUE_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>TEAL</h3>
|
||||
<div class="colors TEAL_E"><p class="color-text">TEAL_E</p></div>
|
||||
<div class="colors TEAL_D"><p class="color-text">TEAL_D</p></div>
|
||||
<div class="colors TEAL_C"><p class="color-text">TEAL_C</p></div>
|
||||
<div class="colors TEAL_B"><p class="color-text">TEAL_B</p></div>
|
||||
<div class="colors TEAL_A"><p class="color-text">TEAL_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>GREEN</h3>
|
||||
<div class="colors GREEN_E"><p class="color-text">GREEN_E</p></div>
|
||||
<div class="colors GREEN_D"><p class="color-text">GREEN_D</p></div>
|
||||
<div class="colors GREEN_C"><p class="color-text">GREEN_C</p></div>
|
||||
<div class="colors GREEN_B"><p class="color-text">GREEN_B</p></div>
|
||||
<div class="colors GREEN_A"><p class="color-text">GREEN_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>YELLOW</h3>
|
||||
<div class="colors YELLOW_E"><p class="color-text">YELLOW_E</p></div>
|
||||
<div class="colors YELLOW_D"><p class="color-text">YELLOW_D</p></div>
|
||||
<div class="colors YELLOW_C"><p class="color-text">YELLOW_C</p></div>
|
||||
<div class="colors YELLOW_B"><p class="color-text">YELLOW_B</p></div>
|
||||
<div class="colors YELLOW_A"><p class="color-text">YELLOW_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>GOLD</h3>
|
||||
<div class="colors GOLD_E"><p class="color-text">GOLD_E</p></div>
|
||||
<div class="colors GOLD_D"><p class="color-text">GOLD_D</p></div>
|
||||
<div class="colors GOLD_C"><p class="color-text">GOLD_C</p></div>
|
||||
<div class="colors GOLD_B"><p class="color-text">GOLD_B</p></div>
|
||||
<div class="colors GOLD_A"><p class="color-text">GOLD_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>RED</h3>
|
||||
<div class="colors RED_E"><p class="color-text">RED_E</p></div>
|
||||
<div class="colors RED_D"><p class="color-text">RED_D</p></div>
|
||||
<div class="colors RED_C"><p class="color-text">RED_C</p></div>
|
||||
<div class="colors RED_B"><p class="color-text">RED_B</p></div>
|
||||
<div class="colors RED_A"><p class="color-text">RED_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>MAROON</h3>
|
||||
<div class="colors MAROON_E"><p class="color-text">MAROON_E</p></div>
|
||||
<div class="colors MAROON_D"><p class="color-text">MAROON_D</p></div>
|
||||
<div class="colors MAROON_C"><p class="color-text">MAROON_C</p></div>
|
||||
<div class="colors MAROON_B"><p class="color-text">MAROON_B</p></div>
|
||||
<div class="colors MAROON_A"><p class="color-text">MAROON_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>PURPLE</h3>
|
||||
<div class="colors PURPLE_E"><p class="color-text">PURPLE_E</p></div>
|
||||
<div class="colors PURPLE_D"><p class="color-text">PURPLE_D</p></div>
|
||||
<div class="colors PURPLE_C"><p class="color-text">PURPLE_C</p></div>
|
||||
<div class="colors PURPLE_B"><p class="color-text">PURPLE_B</p></div>
|
||||
<div class="colors PURPLE_A"><p class="color-text">PURPLE_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>GREY</h3>
|
||||
<div class="colors GREY_E"><p class="color-text">GREY_E</p></div>
|
||||
<div class="colors GREY_D"><p class="color-text">GREY_D</p></div>
|
||||
<div class="colors GREY_C"><p class="color-text">GREY_C</p></div>
|
||||
<div class="colors GREY_B"><p class="color-text">GREY_B</p></div>
|
||||
<div class="colors GREY_A"><p class="color-text">GREY_A</p></div>
|
||||
</div>
|
||||
<div style="float: left;">
|
||||
<h3>Others</h3>
|
||||
<div class="colors WHITE"><p class="color-text" style="color: BLACK">WHITE</p></div>
|
||||
<div class="colors BLACK"><p class="color-text">BLACK</p></div>
|
||||
<div class="colors GREY_BROWN"><p class="color-text-small">GREY_BROWN</p></div>
|
||||
<div class="colors DARK_BROWN"><p class="color-text-small">DARK_BROWN</p></div>
|
||||
<div class="colors LIGHT_BROWN"><p class="color-text-small">LIGHT_BROWN</p></div>
|
||||
<div class="colors PINK"><p class="color-text">PINK</p></div>
|
||||
<div class="colors LIGHT_PINK"><p class="color-text-small">LIGHT_PINK</p></div>
|
||||
<div class="colors GREEN_SCREEN"><p class="color-text-small">GREEN_SCREEN</p></div>
|
||||
<div class="colors ORANGE"><p class="color-text">ORANGE</p></div>
|
||||
</div>
|
139
docs/source/documentation/custom_default.rst
Normal file
139
docs/source/documentation/custom_default.rst
Normal file
|
@ -0,0 +1,139 @@
|
|||
custom_default
|
||||
==============
|
||||
|
||||
``directories``
|
||||
---------------
|
||||
|
||||
- ``mirror_module_path``
|
||||
(``True`` or ``False``) Whether to create a folder named the name of the
|
||||
running file under the ``output`` path, and save the output (``images/``
|
||||
or ``videos/``) in it.
|
||||
|
||||
- ``output``
|
||||
Output file path, the videos will be saved in the ``videos/`` folder under it,
|
||||
and the pictures will be saved in the ``images/`` folder under it.
|
||||
|
||||
For example, if you set ``output`` to ``"/.../manim/output"`` and
|
||||
``mirror_module_path`` to ``False``, then you exported ``Scene1`` in the code
|
||||
file and saved the last frame, then the final directory structure will be like:
|
||||
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 8, 10
|
||||
|
||||
manim/
|
||||
├── manimlib/
|
||||
│ ├── animation/
|
||||
│ ├── ...
|
||||
│ └── window.py
|
||||
├── output/
|
||||
│ ├── images
|
||||
│ │ └── Scene1.png
|
||||
│ └── videos
|
||||
│ └── Scene1.mp4
|
||||
├── code.py
|
||||
├── custom_default.yml
|
||||
└── manim.py
|
||||
|
||||
But if you set ``mirror_module_path`` to ``True``, the directory structure will be:
|
||||
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 7
|
||||
|
||||
manim/
|
||||
├── manimlib/
|
||||
│ ├── animation/
|
||||
│ ├── ...
|
||||
│ └── window.py
|
||||
├── output/
|
||||
│ └── code/
|
||||
│ ├── images
|
||||
│ │ └── Scene1.png
|
||||
│ └── videos
|
||||
│ └── Scene1.mp4
|
||||
├── code.py
|
||||
├── custom_default.yml
|
||||
└── manim.py
|
||||
|
||||
- ``raster_images``
|
||||
The directory for storing raster images to be used in the code (including
|
||||
``.jpg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.
|
||||
|
||||
- ``vector_images``
|
||||
The directory for storing vector images to be used in the code (including
|
||||
``.svg`` and ``.xdv``), which will be read by ``SVGMobject``.
|
||||
|
||||
- ``sounds``
|
||||
The directory for storing sound files to be used in ``Scene.add_sound()`` (
|
||||
including ``.wav`` and ``.mp3``).
|
||||
|
||||
- ``temporary_storage``
|
||||
The directory for storing temporarily generated cache files, including
|
||||
``Tex`` cache, ``Text`` cache and storage of object points.
|
||||
|
||||
``tex``
|
||||
-------
|
||||
|
||||
- ``executable``
|
||||
The executable program used to compile LaTeX (``latex`` or ``xelatex -no-pdf``
|
||||
is recommended)
|
||||
|
||||
- ``template_file``
|
||||
LaTeX template used, in ``manimlib/tex_templates``
|
||||
|
||||
- ``intermediate_filetype``
|
||||
The type of intermediate vector file generated after compilation (``dvi`` if
|
||||
``latex`` is used, ``xdv`` if ``xelatex`` is used)
|
||||
|
||||
- ``text_to_replace``
|
||||
The text to be replaced in the template (needn't to change)
|
||||
|
||||
``universal_import_line``
|
||||
-------------------------
|
||||
|
||||
Import line that need to execute when entering interactive mode directly.
|
||||
|
||||
``style``
|
||||
---------
|
||||
|
||||
- ``font``
|
||||
Default font of Text
|
||||
|
||||
- ``background_color``
|
||||
Default background color
|
||||
|
||||
``window_position``
|
||||
-------------------
|
||||
|
||||
The relative position of the playback window on the display (two characters,
|
||||
the first character means upper(U) / middle(O) / lower(D), the second character
|
||||
means left(L) / middle(O) / right(R)).
|
||||
|
||||
``break_into_partial_movies``
|
||||
-----------------------------
|
||||
|
||||
If this is set to ``True``, then many small files will be written corresponding
|
||||
to each ``Scene.play`` and ``Scene.wait`` call, and these files will then be combined
|
||||
to form the full scene.
|
||||
|
||||
Sometimes video-editing is made easier when working with the broken up scene, which
|
||||
effectively has cuts at all the places you might want.
|
||||
|
||||
``camera_qualities``
|
||||
--------------------
|
||||
|
||||
Export quality
|
||||
|
||||
- ``low``
|
||||
Low quality (default is 480p15)
|
||||
|
||||
- ``medium``
|
||||
Medium quality (default is 720p30)
|
||||
|
||||
- ``high``
|
||||
High quality (default is 1080p30)
|
||||
|
||||
- ``ultra_high``
|
||||
Ultra high quality (default is 4K60)
|
||||
|
||||
- ``default_quality``
|
||||
Default quality (one of the above four)
|
2
docs/source/documentation/mobject/index.rst
Normal file
2
docs/source/documentation/mobject/index.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Mobject (TODO)
|
||||
==============
|
2
docs/source/documentation/scene/index.rst
Normal file
2
docs/source/documentation/scene/index.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Scene (TODO)
|
||||
============
|
2
docs/source/documentation/shaders/index.rst
Normal file
2
docs/source/documentation/shaders/index.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Shaders (TODO)
|
||||
==============
|
2
docs/source/documentation/utils/index.rst
Normal file
2
docs/source/documentation/utils/index.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Utils (TODO)
|
||||
============
|
|
@ -1,4 +0,0 @@
|
|||
Animating Mobjects
|
||||
==================
|
||||
|
||||
Learn about animations.
|
104
docs/source/getting_started/config.rst
Normal file
104
docs/source/getting_started/config.rst
Normal file
|
@ -0,0 +1,104 @@
|
|||
CONFIG dictionary
|
||||
=================
|
||||
|
||||
What's CONFIG
|
||||
-------------
|
||||
|
||||
``CONFIG`` dictionary is a feature of manim, which facilitates the inheritance
|
||||
and modification of parameters between parent and child classes.
|
||||
|
||||
| ``CONFIG`` dictionary 's processing is in ``manimlib/utils/config_ops.py``
|
||||
| It can convert the key-value pairs in the ``CONFIG`` dictionary into class attributes and values
|
||||
|
||||
Generally, the first line of the ``.__init__()`` method in some basic class (``Mobject``, ``Animation``,
|
||||
etc.) will call this function ``digest_config(self, kwargs)`` to convert both
|
||||
the ``CONFIG`` dictionary and ``kwargs`` into attributes. Then it can be accessed
|
||||
directly through ``self.``, which simplifies the handling of inheritance between classes.
|
||||
|
||||
**An example**:
|
||||
|
||||
There are many class inheritance relationships in ``manimlib/mobject/geometry.py``
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Line 279
|
||||
class Circle(Arc):
|
||||
CONFIG = {
|
||||
"color": RED,
|
||||
"close_new_points": True,
|
||||
"anchors_span_full_range": False
|
||||
}
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Line 304
|
||||
class Dot(Circle):
|
||||
CONFIG = {
|
||||
"radius": DEFAULT_DOT_RADIUS,
|
||||
"stroke_width": 0,
|
||||
"fill_opacity": 1.0,
|
||||
"color": WHITE
|
||||
}
|
||||
|
||||
The ``Circle`` class uses the key-value pair ``"color": RED`` in the ``CONFIG``
|
||||
dictionary to add the attribute ``self.color``.
|
||||
|
||||
At the same time, the ``Dot`` class also contains the key ``color`` in the
|
||||
``CONFIG`` dictionary, but the value is different. At this time, the priority will
|
||||
modify the attribute ``self.color`` to ``WHITE``.
|
||||
|
||||
CONFIG nesting
|
||||
--------------
|
||||
|
||||
The ``CONFIG`` dictionary supports nesting, that is, the value of the key is also
|
||||
a dictionary, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Camera(object):
|
||||
CONFIG = {
|
||||
# configs
|
||||
}
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Scene(object):
|
||||
CONFIG = {
|
||||
"window_config": {},
|
||||
"camera_class": Camera,
|
||||
"camera_config": {},
|
||||
"file_writer_config": {},
|
||||
# other configs
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
# some lines
|
||||
self.camera = self.camera_class(**self.camera_config)
|
||||
|
||||
The ``CONFIG`` dictionary of the ``Camera`` class contains many key-value pairs,
|
||||
and this class needs to be instantiated in the ``Scene`` class. For more convenient
|
||||
control, there is a special key-value pair in the Scene class ``"camera_config": {}``,
|
||||
Its value is a dictionary, passed in as ``kwargs`` when initializing the ``Camera`` class
|
||||
to modify the value of the properties of the ``Camera`` class.
|
||||
|
||||
So the nesting of the ``CONFIG`` dictionary **essentially** passes in the value as ``kwargs``.
|
||||
|
||||
Common usage
|
||||
------------
|
||||
|
||||
When writing a class by yourself, you can add attributes or modify the attributes
|
||||
of the parent class through ``CONFIG``.
|
||||
|
||||
The most commonly used is to modify the properties of the camera when writing a ``Scene``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class YourScene(Scene):
|
||||
CONFIG = {
|
||||
"camera_config": {
|
||||
"background_color": WHITE,
|
||||
},
|
||||
}
|
||||
|
||||
For example, the above dictionary will change the background color to white, etc.
|
86
docs/source/getting_started/configuration.rst
Normal file
86
docs/source/getting_started/configuration.rst
Normal file
|
@ -0,0 +1,86 @@
|
|||
CLI flags and configuration
|
||||
===========================
|
||||
|
||||
Command Line Interface
|
||||
----------------------
|
||||
|
||||
To run manim, you need to enter the directory at the same level as ``manim.py``
|
||||
and enter the command in the following format into terminal:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python manim.py <code>.py <Scene> <flags>
|
||||
|
||||
- ``<code>.py`` : The python file you wrote. Needs to be at the same level as ``manim.py``, otherwise you need to use an absolute path or a relative path.
|
||||
- ``<Scene>`` : The scene you want to render here. If it is not written or written incorrectly, it will list all for you to choose. And if there is only one ``Scene`` in the file, this class will be rendered directly.
|
||||
- ``<flags>`` : CLI flags.
|
||||
|
||||
Some useful flags
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
- ``-w`` to write the scene to a file.
|
||||
- ``-o`` to write the scene to a file and open the result.
|
||||
- ``-s`` to skip to the end and just show the final frame.
|
||||
|
||||
- ``-so`` will save the final frame to an image and show it.
|
||||
|
||||
- ``-n <number>`` to skip ahead to the ``n``\ ’th animation of a scene.
|
||||
- ``-f`` to make the playback window fullscreen.
|
||||
|
||||
All supported flags
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
flag abbr function
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
``--help`` ``-h`` Show the help message and exit
|
||||
``--write_file`` ``-w`` Render the scene as a movie file
|
||||
``--skip_animations`` ``-s`` Skip to the last frame
|
||||
``--low_quality`` ``-l`` Render at a low quality (for faster rendering)
|
||||
``--medium_quality`` ``-m`` Render at a medium quality
|
||||
``--hd`` Render at a 1080p quality
|
||||
``--uhd`` Render at a 4k quality
|
||||
``--full_screen`` ``-f`` Show window in full screen
|
||||
``--save_pngs`` ``-g`` Save each frame as a png
|
||||
``--save_as_gif`` ``-i`` Save the video as gif
|
||||
``--transparent`` ``-t`` Render to a movie file with an alpha channel
|
||||
``--quiet`` ``-q``
|
||||
``--write_all`` ``-a`` Write all the scenes from a file
|
||||
``--open`` ``-o`` Automatically open the saved file once its done
|
||||
``--finder`` Show the output file in finder
|
||||
``--file_name FILE_NAME`` Name for the movie or image file
|
||||
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passin two comma separated values, e.g. "3,6", it will end the rendering at the second value.
|
||||
``--resolution RESOLUTION`` ``-r`` Resolution, passed as "WxH", e.g. "1920x1080"
|
||||
``--frame_rate FRAME_RATE`` Frame rate, as an integer
|
||||
``--color COLOR`` ``-c`` Background color
|
||||
``--leave_progress_bars`` Leave progress bars displayed in terminal
|
||||
``--video_dir VIDEO_DIR`` directory to write video
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
|
||||
custom_default
|
||||
--------------
|
||||
|
||||
In order to perform more configuration (about directories, etc.) and permanently
|
||||
change the default value (you don't have to add flags to the command every time),
|
||||
you can modify ``custom_default.yml``. The meaning of each option is in
|
||||
page :doc:`../documentation/custom_default`.
|
||||
|
||||
You can also use different ``custom_default.yml`` for different directories, such as
|
||||
following the directory structure:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
manim/
|
||||
├── manimlib/
|
||||
│ ├── animation/
|
||||
│ ├── ...
|
||||
│ └── window.py
|
||||
├── project/
|
||||
│ ├── code.py
|
||||
│ └── custom_default.yml
|
||||
├── custom_default.yml
|
||||
└── manim.py
|
||||
|
||||
When you enter the ``project/`` folder and run ``python ../manim.py code.py <Scene>``,
|
||||
it will overwrite ``manim/custom_default.yml`` with ``custom_default.yml``
|
||||
in the ``project`` folder.
|
531
docs/source/getting_started/example_scenes.rst
Normal file
531
docs/source/getting_started/example_scenes.rst
Normal file
|
@ -0,0 +1,531 @@
|
|||
Example Scenes
|
||||
==============
|
||||
|
||||
After understanding the previous knowledge, we can understand more scenes.
|
||||
Many example scenes are given in ``example_scenes.py``, let's start with
|
||||
the simplest and one by one.
|
||||
|
||||
SquareToCircle
|
||||
--------------
|
||||
|
||||
.. manim-example:: SquareToCircle
|
||||
:media: ../_static/example_scenes/SquareToCircle.mp4
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
square = Square()
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(square, circle))
|
||||
self.wait()
|
||||
|
||||
This scene is what we wrote in :doc:`quickstart`.
|
||||
No more explanation here
|
||||
|
||||
AnimatingMethods
|
||||
----------------
|
||||
|
||||
.. manim-example:: AnimatingMethods
|
||||
:media: ../_static/example_scenes/AnimatingMethods.mp4
|
||||
|
||||
class AnimatingMethods(Scene):
|
||||
def construct(self):
|
||||
grid = Tex(r"\pi").get_grid(10, 10, height=4)
|
||||
self.add(grid)
|
||||
|
||||
# If you pass in a mobject method to the scene's "play" function,
|
||||
# it will apply an animation interpolating between the mobject's
|
||||
# initial state and whatever happens when you apply that method.
|
||||
# For example, calling grid.shift(2 * LEFT) would shift it two units
|
||||
# to the left, but the following line animates that motion.
|
||||
self.play(grid.shift, 2 * LEFT)
|
||||
# The same applies for any method, including those setting colors.
|
||||
self.play(grid.set_submobject_colors_by_gradient, BLUE, GREEN)
|
||||
self.play(grid.set_height, TAU - MED_SMALL_BUFF)
|
||||
self.wait()
|
||||
|
||||
# The method Mobject.apply_complex_function lets you apply arbitrary
|
||||
# complex functions, treating the points defining the mobject as
|
||||
# complex numbers.
|
||||
self.play(grid.apply_complex_function, np.exp, run_time=5)
|
||||
self.wait()
|
||||
|
||||
# Even more generally, you could apply Mobject.apply_function,
|
||||
# which takes in functions form R^3 to R^3
|
||||
self.play(
|
||||
grid.apply_function,
|
||||
lambda p: [
|
||||
p[0] + 0.5 * math.sin(p[1]),
|
||||
p[1] + 0.5 * math.sin(p[0]),
|
||||
p[2]
|
||||
],
|
||||
run_time=5,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
The new usage in this scene is ``.get_grid()`` and ``self.play(mob.method, args)``.
|
||||
|
||||
- ``.get_grid()`` method will return a new mobject containing multiple copies of this one arranged in a grid.
|
||||
- ``self.play(mob.method, args)`` animate the method, and the details are in the comments above.
|
||||
|
||||
TextExample
|
||||
-----------
|
||||
|
||||
.. manim-example:: TextExample
|
||||
:media: ../_static/example_scenes/TextExample.mp4
|
||||
|
||||
class TextExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Here is a text", font="Consolas", font_size=90)
|
||||
difference = Text(
|
||||
"""
|
||||
The most important difference between Text and TexText is that\n
|
||||
you can change the font more easily, but can't use the LaTeX grammar
|
||||
""",
|
||||
font="Arial", font_size=24,
|
||||
t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE}
|
||||
)
|
||||
VGroup(text, difference).arrange(DOWN, buff=1)
|
||||
self.play(Write(text))
|
||||
self.play(FadeIn(difference, UP))
|
||||
self.wait(3)
|
||||
|
||||
fonts = Text(
|
||||
"And you can also set the font according to different words",
|
||||
font="Arial",
|
||||
t2f={"font": "Consolas", "words": "Consolas"},
|
||||
t2c={"font": BLUE, "words": GREEN}
|
||||
)
|
||||
slant = Text(
|
||||
"And the same as slant and weight",
|
||||
font="Consolas",
|
||||
t2s={"slant": ITALIC},
|
||||
t2w={"weight": BOLD},
|
||||
t2c={"slant": ORANGE, "weight": RED}
|
||||
)
|
||||
VGroup(fonts, slant).arrange(DOWN, buff=0.8)
|
||||
self.play(FadeOut(text), FadeOut(difference, shift=DOWN))
|
||||
self.play(Write(fonts))
|
||||
self.wait()
|
||||
self.play(Write(slant))
|
||||
self.wait()
|
||||
|
||||
The new classes in this scene are ``Text``, ``VGroup``, ``Write``, ``FadeIn`` and ``FadeOut``.
|
||||
|
||||
- ``Text`` can create text, define fonts, etc. The usage ais clearly reflected in the above examples.
|
||||
- ``VGroup`` can put multiple ``VMobject`` together as a whole. In the example, the ``.arrange()`` method is called to arrange the sub-mobjects in sequence downward (``DOWN``), and the spacing is ``buff``.
|
||||
- ``Write`` is an animation that shows similar writing effects.
|
||||
- ``FadeIn`` fades the object in, the second parameter indicates the direction of the fade in.
|
||||
- ``FadeOut`` fades out the object, the second parameter indicates the direction of the fade out.
|
||||
|
||||
TexTransformExample
|
||||
-------------------
|
||||
|
||||
.. manim-example:: TexTransformExample
|
||||
:media: ../_static/example_scenes/TexTransformExample.mp4
|
||||
|
||||
class TexTransformExample(Scene):
|
||||
def construct(self):
|
||||
to_isolate = ["B", "C", "=", "(", ")"]
|
||||
lines = VGroup(
|
||||
# Surrounding substrings with double braces
|
||||
# will ensure that those parts are separated
|
||||
# out in the Tex. For example, here the
|
||||
# Tex will have 5 submobjects, corresponding
|
||||
# to the strings [A^2, +, B^2, =, C^2]
|
||||
Tex("{{A^2}} + {{B^2}} = {{C^2}}"),
|
||||
Tex("{{A^2}} = {{C^2}} - {{B^2}}"),
|
||||
# Alternatively, you can pass in the keyword argument
|
||||
# "isolate" with a list of strings that should be out as
|
||||
# their own submobject. So both lines below are equivalent
|
||||
# to what you'd get by wrapping every instance of "B", "C"
|
||||
# "=", "(" and ")" with double braces
|
||||
Tex("{{A^2}} = (C + B)(C - B)", isolate=to_isolate),
|
||||
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=to_isolate)
|
||||
)
|
||||
lines.arrange(DOWN, buff=LARGE_BUFF)
|
||||
for line in lines:
|
||||
line.set_color_by_tex_to_color_map({
|
||||
"A": BLUE,
|
||||
"B": TEAL,
|
||||
"C": GREEN,
|
||||
})
|
||||
|
||||
play_kw = {"run_time": 2}
|
||||
self.add(lines[0])
|
||||
# The animation TransformMatchingTex will line up parts
|
||||
# of the source and target which have matching tex strings.
|
||||
# Here, giving it a little path_arc makes each part sort of
|
||||
# rotate into their final positions, which feels appropriate
|
||||
# for the idea of rearranging an equation
|
||||
self.play(
|
||||
TransformMatchingTex(
|
||||
lines[0].copy(), lines[1],
|
||||
path_arc=90 * DEGREES,
|
||||
),
|
||||
**play_kw
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Now, we could try this again on the next line...
|
||||
self.play(
|
||||
TransformMatchingTex(lines[1].copy(), lines[2]),
|
||||
**play_kw
|
||||
)
|
||||
self.wait()
|
||||
# ...and this looks nice enough, but since there's no tex
|
||||
# in lines[2] which matches "C^2" or "B^2", those terms fade
|
||||
# out to nothing while the C and B terms fade in from nothing.
|
||||
# If, however, we want the C^2 to go to C, and B^2 to go to B,
|
||||
# we can specify that with a key map.
|
||||
self.play(FadeOut(lines[2]))
|
||||
self.play(
|
||||
TransformMatchingTex(
|
||||
lines[1].copy(), lines[2],
|
||||
key_map={
|
||||
"C^2": "C",
|
||||
"B^2": "B",
|
||||
}
|
||||
),
|
||||
**play_kw
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# And to finish off, a simple TransformMatchingShapes would work
|
||||
# just fine. But perhaps we want that exponent on A^2 to transform into
|
||||
# the square root symbol. At the moment, lines[2] treats the expression
|
||||
# A^2 as a unit, so we might create a new version of the same line which
|
||||
# separates out just the A. This way, when TransformMatchingTex lines up
|
||||
# all matching parts, the only mismatch will be between the "^2" from
|
||||
# new_line2 and the "\sqrt" from the final line. By passing in,
|
||||
# transform_mismatches=True, it will transform this "^2" part into
|
||||
# the "\sqrt" part.
|
||||
new_line2 = Tex("{{A}}^2 = (C + B)(C - B)", isolate=to_isolate)
|
||||
new_line2.replace(lines[2])
|
||||
new_line2.match_style(lines[2])
|
||||
|
||||
self.play(
|
||||
TransformMatchingTex(
|
||||
new_line2, lines[3],
|
||||
transform_mismatches=True,
|
||||
),
|
||||
**play_kw
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(FadeOut(lines, RIGHT))
|
||||
|
||||
# Alternatively, if you don't want to think about breaking up
|
||||
# the tex strings deliberately, you can TransformMatchingShapes,
|
||||
# which will try to line up all pieces of a source mobject with
|
||||
# those of a target, regardless of the submobject hierarchy in
|
||||
# each one, according to whether those pieces have the same
|
||||
# shape (as best it can).
|
||||
source = Text("the morse code", height=1)
|
||||
target = Text("here come dots", height=1)
|
||||
|
||||
self.play(Write(source))
|
||||
self.wait()
|
||||
kw = {"run_time": 3, "path_arc": PI / 2}
|
||||
self.play(TransformMatchingShapes(source, target, **kw))
|
||||
self.wait()
|
||||
self.play(TransformMatchingShapes(target, source, **kw))
|
||||
self.wait()
|
||||
|
||||
The new classes in this scene are ``Tex``, ``TexText``, ``TransformMatchingTex``
|
||||
and ``TransformMatchingShapes``.
|
||||
|
||||
- ``Tex`` uses LaTeX to create mathematical formulas.
|
||||
- ``TexText`` uses LaTeX to create text.
|
||||
- ``TransformMatchingTeX`` automatically transforms sub-objects according to the similarities and differences of tex in ``Tex``.
|
||||
- ``TransformMatchingShapes`` automatically transform sub-objects directly based on the similarities and differences of the object point sets.
|
||||
|
||||
UpdatersExample
|
||||
---------------
|
||||
|
||||
.. manim-example:: UpdatersExample
|
||||
:media: ../_static/example_scenes/UpdatersExample.mp4
|
||||
|
||||
class UpdatersExample(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
square.set_fill(BLUE_E, 1)
|
||||
|
||||
# On all all frames, the constructor Brace(square, UP) will
|
||||
# be called, and the mobject brace will set its data to match
|
||||
# that of the newly constructed object
|
||||
brace = always_redraw(Brace, square, UP)
|
||||
|
||||
text, number = label = VGroup(
|
||||
Text("Width = "),
|
||||
DecimalNumber(
|
||||
0,
|
||||
show_ellipsis=True,
|
||||
num_decimal_places=2,
|
||||
include_sign=True,
|
||||
)
|
||||
)
|
||||
label.arrange(RIGHT)
|
||||
|
||||
# This ensures that the method deicmal.next_to(square)
|
||||
# is called on every frame
|
||||
always(label.next_to, brace, UP)
|
||||
# You could also write the following equivalent line
|
||||
# label.add_updater(lambda m: m.next_to(brace, UP))
|
||||
|
||||
# If the argument itself might change, you can use f_always,
|
||||
# for which the arguments following the initial Mobject method
|
||||
# should be functions returning arguments to that method.
|
||||
# The following line ensures thst decimal.set_value(square.get_y())
|
||||
# is called every frame
|
||||
f_always(number.set_value, square.get_width)
|
||||
# You could also write the following equivalent line
|
||||
# number.add_updater(lambda m: m.set_value(square.get_width()))
|
||||
|
||||
self.add(square, brace, label)
|
||||
|
||||
# Notice that the brace and label track with the square
|
||||
self.play(
|
||||
square.scale, 2,
|
||||
rate_func=there_and_back,
|
||||
run_time=2,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
square.set_width, 5, {"stretch": True},
|
||||
run_time=3,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
square.set_width, 2,
|
||||
run_time=3
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# In general, you can alway call Mobject.add_updater, and pass in
|
||||
# a function that you want to be called on every frame. The function
|
||||
# should take in either one argument, the mobject, or two arguments,
|
||||
# the mobject and the amount of time since the last frame.
|
||||
now = self.time
|
||||
w0 = square.get_width()
|
||||
square.add_updater(
|
||||
lambda m: m.set_width(w0 * math.cos(self.time - now))
|
||||
)
|
||||
self.wait(4 * PI)
|
||||
|
||||
The new classes and usage in this scene are ``always_redraw()``, ``DecimalNumber``, ``.to_edge()``,
|
||||
``.center()``, ``always()``, ``f_always()``, ``.set_y()`` and ``.add_updater()``.
|
||||
|
||||
- ``always_redraw()`` function create a new mobject every frame.
|
||||
- ``DecimalNumber`` is a variable number, speed it up by breaking it into ``Text`` characters.
|
||||
- ``.to_edge()`` means to place the object on the edge of the screen.
|
||||
- ``.center()`` means to place the object in the center of the screen.
|
||||
- ``always(f, x)`` means that a certain function (``f(x)``) is executed every frame.
|
||||
- ``f_always(f, g)`` is similar to ``always``, executed ``f(g())`` every frame.
|
||||
- ``.set_y()`` means to set the ordinate of the object on the screen.
|
||||
- ``.add_updater()`` sets an update function for the object. For example: ``mob1.add_updater(lambda mob: mob.next_to(mob2))`` means ``mob1.next_to(mob2)`` is executed every frame.
|
||||
|
||||
SurfaceExample
|
||||
--------------
|
||||
|
||||
.. manim-example:: SurfaceExample
|
||||
:media: ../_static/example_scenes/SurfaceExample.mp4
|
||||
|
||||
class SurfaceExample(Scene):
|
||||
CONFIG = {
|
||||
"camera_class": ThreeDCamera,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
surface_text = Text("For 3d scenes, try using surfaces")
|
||||
surface_text.fix_in_frame()
|
||||
surface_text.to_edge(UP)
|
||||
self.add(surface_text)
|
||||
self.wait(0.1)
|
||||
|
||||
torus1 = Torus(r1=1, r2=1)
|
||||
torus2 = Torus(r1=3, r2=1)
|
||||
sphere = Sphere(radius=3, resolution=torus1.resolution)
|
||||
# You can texture a surface with up to two images, which will
|
||||
# be interpreted as the side towards the light, and away from
|
||||
# the light. These can be either urls, or paths to a local file
|
||||
# in whatever you've set as the image directory in
|
||||
# the custom_defaults.yml file
|
||||
|
||||
# day_texture = "EarthTextureMap"
|
||||
# night_texture = "NightEarthTextureMap"
|
||||
day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
|
||||
night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg"
|
||||
|
||||
surfaces = [
|
||||
TexturedSurface(surface, day_texture, night_texture)
|
||||
for surface in [sphere, torus1, torus2]
|
||||
]
|
||||
|
||||
for mob in surfaces:
|
||||
mob.shift(IN)
|
||||
mob.mesh = SurfaceMesh(mob)
|
||||
mob.mesh.set_stroke(BLUE, 1, opacity=0.5)
|
||||
|
||||
# Set perspective
|
||||
frame = self.camera.frame
|
||||
frame.set_euler_angles(
|
||||
theta=-30 * DEGREES,
|
||||
phi=70 * DEGREES,
|
||||
)
|
||||
|
||||
surface = surfaces[0]
|
||||
|
||||
self.play(
|
||||
FadeIn(surface),
|
||||
ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3),
|
||||
)
|
||||
for mob in surfaces:
|
||||
mob.add(mob.mesh)
|
||||
surface.save_state()
|
||||
self.play(Rotate(surface, PI / 2), run_time=2)
|
||||
for mob in surfaces[1:]:
|
||||
mob.rotate(PI / 2)
|
||||
|
||||
self.play(
|
||||
Transform(surface, surfaces[1]),
|
||||
run_time=3
|
||||
)
|
||||
|
||||
self.play(
|
||||
Transform(surface, surfaces[2]),
|
||||
# Move camera frame during the transition
|
||||
frame.increment_phi, -10 * DEGREES,
|
||||
frame.increment_theta, -20 * DEGREES,
|
||||
run_time=3
|
||||
)
|
||||
# Add ambient rotation
|
||||
frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
|
||||
|
||||
# Play around with where the light is
|
||||
light_text = Text("You can move around the light source")
|
||||
light_text.move_to(surface_text)
|
||||
light_text.fix_in_frame()
|
||||
|
||||
self.play(FadeTransform(surface_text, light_text))
|
||||
light = self.camera.light_source
|
||||
self.add(light)
|
||||
light.save_state()
|
||||
self.play(light.move_to, 3 * IN, run_time=5)
|
||||
self.play(light.shift, 10 * OUT, run_time=5)
|
||||
|
||||
drag_text = Text("Try moving the mouse while pressing d or s")
|
||||
drag_text.move_to(light_text)
|
||||
drag_text.fix_in_frame()
|
||||
|
||||
self.play(FadeTransform(light_text, drag_text))
|
||||
self.wait()
|
||||
|
||||
This scene shows an example of using a three-dimensional surface, and
|
||||
the related usage has been briefly described in the notes.
|
||||
|
||||
- ``.fix_in_frame()`` makes the object not change with the view angle of the screen, and is always displayed at a fixed position on the screen.
|
||||
|
||||
OpeningManimExample
|
||||
-------------------
|
||||
|
||||
.. manim-example:: OpeningManimExample
|
||||
:media: ../_static/example_scenes/OpeningManimExample.mp4
|
||||
|
||||
class OpeningManimExample(Scene):
|
||||
def construct(self):
|
||||
title = TexText("This is some \\LaTeX")
|
||||
basel = Tex(
|
||||
"\\sum_{n=1}^\\infty "
|
||||
"\\frac{1}{n^2} = \\frac{\\pi^2}{6}"
|
||||
)
|
||||
VGroup(title, basel).arrange(DOWN)
|
||||
self.play(
|
||||
Write(title),
|
||||
FadeIn(basel, UP),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
transform_title = Text("That was a transform")
|
||||
transform_title.to_corner(UL)
|
||||
self.play(
|
||||
Transform(title, transform_title),
|
||||
LaggedStartMap(FadeOut, basel, shift=DOWN),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
fade_comment = Text(
|
||||
"""
|
||||
You probably don't want to overuse
|
||||
Transforms, though, a simple fade often
|
||||
looks nicer.
|
||||
""",
|
||||
font_size=36,
|
||||
color=GREY_B,
|
||||
)
|
||||
fade_comment.next_to(
|
||||
transform_title, DOWN,
|
||||
buff=LARGE_BUFF,
|
||||
aligned_edge=LEFT
|
||||
)
|
||||
self.play(FadeIn(fade_comment, shift=DOWN))
|
||||
self.wait(3)
|
||||
|
||||
grid = NumberPlane((-10, 10), (-5, 5))
|
||||
grid_title = Text(
|
||||
"But manim is for illustrating math, not text",
|
||||
)
|
||||
grid_title.to_edge(UP)
|
||||
grid_title.add_background_rectangle()
|
||||
|
||||
self.add(grid, grid_title) # Make sure title is on top of grid
|
||||
self.play(
|
||||
FadeOut(title, shift=LEFT),
|
||||
FadeOut(fade_comment, shift=LEFT),
|
||||
FadeIn(grid_title),
|
||||
ShowCreation(grid, run_time=3, lag_ratio=0.1),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
matrix = [[1, 1], [0, 1]]
|
||||
linear_transform_title = VGroup(
|
||||
Text("This is what the matrix"),
|
||||
IntegerMatrix(matrix, include_background_rectangle=True),
|
||||
Text("looks like")
|
||||
)
|
||||
linear_transform_title.arrange(RIGHT)
|
||||
linear_transform_title.to_edge(UP)
|
||||
|
||||
self.play(
|
||||
FadeOut(grid_title),
|
||||
FadeIn(linear_transform_title),
|
||||
)
|
||||
self.play(grid.apply_matrix, matrix, run_time=3)
|
||||
self.wait()
|
||||
|
||||
grid_transform_title = Text(
|
||||
"And this is a nonlinear transformation"
|
||||
)
|
||||
grid_transform_title.set_stroke(BLACK, 5, background=True)
|
||||
grid_transform_title.to_edge(UP)
|
||||
grid.prepare_for_nonlinear_transform(100)
|
||||
self.play(
|
||||
ApplyPointwiseFunction(
|
||||
lambda p: p + np.array([np.sin(p[1]), np.sin(p[0]), 0]),
|
||||
grid,
|
||||
run_time=5,
|
||||
),
|
||||
FadeOut(linear_transform_title),
|
||||
FadeIn(grid_transform_title),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
This scene is a comprehensive application of a two-dimensional scene.
|
||||
|
||||
After seeing these scenes, you have already understood part of the
|
||||
usage of manim. For more examples, see `the video code of 3b1b <https://github.com/3b1b/videos>`_.
|
|
@ -1,18 +0,0 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
Todd Zimmerman put together `a very nice tutorial`_ on getting started with
|
||||
``manim``, which has been updated to run on python 3.7. Note that you'll want
|
||||
to change `from big_ol_pile_of_manim_imports import *` to `from
|
||||
manimlib.imports import *` to work with the current codebase.
|
||||
|
||||
.. _a very nice tutorial: https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/
|
||||
|
||||
.. toctree::
|
||||
:caption: Contents
|
||||
:maxdepth: 2
|
||||
|
||||
learning_by_example
|
||||
mathematical_objects
|
||||
animating_mobjects
|
||||
making_a_scene
|
67
docs/source/getting_started/installation.rst
Normal file
67
docs/source/getting_started/installation.rst
Normal file
|
@ -0,0 +1,67 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Manim runs on Python 3.8.
|
||||
|
||||
System requirements are:
|
||||
|
||||
- `FFmpeg <https://ffmpeg.org/>`__
|
||||
- `OpenGL <https://www.opengl.org//>`__ (included in python package ``PyOpenGL``)
|
||||
- `LaTeX <https://www.latex-project.org>`__ (optional, if you want to use LaTeX)
|
||||
- `cairo <https://www.cairographics.org/>`_ (included in python package ``pycairo``. optional, if you want to use ``Text`` in manim)
|
||||
|
||||
Directly
|
||||
--------
|
||||
|
||||
If you want to hack on manimlib itself, clone this repository and in
|
||||
that directory execute:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# Install python requirements
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Try it out
|
||||
python -m manim example_scenes.py OpeningManimExample
|
||||
|
||||
If you run the above command and no error message appears,
|
||||
then you have successfully installed all the environments required by manim.
|
||||
|
||||
Directly (Windows)
|
||||
------------------
|
||||
|
||||
1. `Install
|
||||
FFmpeg <https://www.wikihow.com/Install-FFmpeg-on-Windows>`__, and make sure that its path is in the PATH environment variable.
|
||||
2. Install a LaTeX distribution.
|
||||
`TeXLive-full <http://tug.org/texlive/>`__ is recommended.
|
||||
3. Install the remaining Python packages.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
cd manim
|
||||
pip install -r requirements.txt
|
||||
python manim.py example_scenes.py OpeningManimExample
|
||||
|
||||
For Anaconda
|
||||
------------
|
||||
|
||||
- Install FFmpeg and LaTeX as above.
|
||||
- Create a conda environment using
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
cd manim
|
||||
conda env create -f environment.yml
|
||||
|
||||
Using virtualenv and virtualenvwrapper
|
||||
--------------------------------------
|
||||
|
||||
After installing ``virtualenv`` and ``virtualenvwrapper``
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
mkvirtualenv -a manim -r requirements.txt manim
|
||||
python -m manim example_scenes.py OpeningManimExample
|
|
@ -1,131 +0,0 @@
|
|||
Learning by Example
|
||||
===================
|
||||
|
||||
SquareToCircle
|
||||
--------------
|
||||
|
||||
``example_scenes.py`` contains simple examples that we can use to learn about manim.
|
||||
|
||||
Go ahead and try out the ``SquareToCircle`` scene by running it with ``$ manim example_scenes.py SquareToCircle -p``
|
||||
in manim directory.
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
square = Square()
|
||||
square.flip(RIGHT)
|
||||
square.rotate(-3 * TAU / 8)
|
||||
circle.set_fill(PINK, opacity=0.5)
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.play(Transform(square, circle))
|
||||
self.play(FadeOut(square))
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video width="560" height="315" controls>
|
||||
<source src="../_static/SquareToCircle.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The flag ``-p`` plays the rendered video with default video player.
|
||||
|
||||
Other frequently used flags are:
|
||||
|
||||
* ``-l`` for rendering video in lower resolution (which renders faster)
|
||||
* ``-s`` to show the last frame of the video.
|
||||
|
||||
Run ``manim -h`` all the available flags (``python -m manim -h`` if you installed it to a venv)
|
||||
|
||||
|
||||
Let's step through each line of ``SquareToCircle``
|
||||
|
||||
.. code-block:: python
|
||||
:lineno-start: 3
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
|
||||
You create videos in manim by writing :class:`~scene.scene.Scene` classes.
|
||||
|
||||
Each :class:`~scene.scene.Scene` in manim is self-contained. That means everything
|
||||
you created under this scene does not exist outside the class.
|
||||
|
||||
.. code-block:: python
|
||||
:lineno-start: 4
|
||||
|
||||
def construct(self):
|
||||
|
||||
:meth:`~scene.scene.Scene.construct` specifies what is displayed on the screen
|
||||
when the :class:`~scene.scene.Scene` is rendered to video.
|
||||
|
||||
.. code-block:: python
|
||||
:lineno-start: 5
|
||||
|
||||
circle = Circle()
|
||||
square = Square()
|
||||
|
||||
``Circle()`` and ``Square()`` create :class:`~mobject.geometry.Circle` and :class:`~mobject.geometry.Square`.
|
||||
|
||||
Both of these are instances of :class:`~mobject.mobject.Mobject` subclasses, the base class for objects in manim. Note
|
||||
that instantiating a :class:`~mobject.mobject.Mobject` does not add it to the
|
||||
:class:`~scene.scene.Scene`, so you wouldn't see anything if you were to render
|
||||
the :class:`~scene.scene.Scene` at this point.
|
||||
|
||||
.. code-block:: python
|
||||
:lineno-start: 7
|
||||
|
||||
square.flip(RIGHT)
|
||||
square.rotate(-3 * TAU / 8)
|
||||
circle.set_fill(PINK, opacity=0.5)
|
||||
|
||||
``flip()`` ``rotate()`` ``set_fill()`` apply various modifications to the mobjects before animating
|
||||
them. The call to :meth:`~mobject.mobject.Mobject.flip` flips the
|
||||
:class:`~mobject.geometry.Square` across the RIGHT vector. This is equivalent
|
||||
to a refection across the x-axis.
|
||||
|
||||
The call to :meth:`~mobject.mobject.Mobject.rotate` rotates the
|
||||
:class:`~mobject.geometry.Square` 3/8ths of a full rotation counterclockwise.
|
||||
|
||||
The call to :meth:`~mobject.mobject.Mobject.set_fill` sets
|
||||
the fill color for the :class:`~mobject.geometry.Circle` to pink, and its opacity to 0.5.
|
||||
|
||||
.. code-block:: python
|
||||
:lineno-start: 11
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.play(Transform(square, circle))
|
||||
self.play(FadeOut(square))
|
||||
|
||||
To generated animation, :class:`~animation.animation.Animation` classes are used.
|
||||
|
||||
Each :class:`~animation.animation.Animation` takes one or more :class:`~mobject.mobject.Mobject` instances as arguments, which it animates
|
||||
when passed to :meth:`~scene.scene.Scene.play`. This is how video is typically
|
||||
created in manim.
|
||||
|
||||
:class:`~mobject.mobject.Mobject` instances are automatically
|
||||
added to the :class:`~scene.scene.Scene` when they are animated. You can add a
|
||||
:class:`~mobject.mobject.Mobject` to the :class:`~scene.scene.Scene` manually
|
||||
by passing it as an argument to :meth:`~scene.scene.Scene.add`.
|
||||
|
||||
|
||||
:class:`~animation.creation.ShowCreation` draws a :class:`~mobject.mobject.Mobject` to the screen.
|
||||
|
||||
:class:`~animation.transform.Transform` morphs one :class:`~mobject.mobject.Mobject` into another.
|
||||
|
||||
:class:`~animation.creation.FadeOut` fades a :class:`~mobject.mobject.Mobject` out of the :class:`~scene.scene.Scene`.
|
||||
|
||||
.. note::
|
||||
|
||||
Only the first argument to :class:`~animation.transform.Transform` is modified,
|
||||
the second is not added to the :class:`~scene.scene.Scene`. :class:`~animation.tranform.Transform`
|
||||
only changes the appearance but not the underlying properties.
|
||||
|
||||
After the call to ``transform()`` ``square`` is still a :class:`~mobject.geometry.Square` instance
|
||||
but with the shape of :class:`~mobject.geometry.Circle`.
|
|
@ -1,15 +0,0 @@
|
|||
Making a Scene
|
||||
==============
|
||||
|
||||
A scene is what renders when manim is executed. Each scene contains mobjects, which can then be animated as
|
||||
previously explained. In code, a scene is a class that extends ``Scene`` and implements the ``construct``
|
||||
function, like so. Manim will execute this function to render the scene.
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
class ExampleScene(Scene):
|
||||
def construct(self):
|
||||
# Add and animate mobjects here
|
|
@ -1,13 +0,0 @@
|
|||
Mathematical Objects
|
||||
====================
|
||||
|
||||
Everything that appears on screen in a manim video is a
|
||||
:class:`~mobject.mobject.Mobject`, or Mathematical Object. A
|
||||
:class:`~mobject.mobject.Mobject`'s appearance is determined by 3
|
||||
factors:
|
||||
|
||||
* ``m.points``, an Nx3 ``numpy.array`` specifying how to draw ``m``
|
||||
* ``m``'s style attributes, such as ``m.color``, ``m.stroke_width``, and
|
||||
``m.fill_opacity``
|
||||
* ``m.submobjects``, a list of :class:`~mobject.mobject.Mobject` instances that
|
||||
are considered part of ``m``
|
256
docs/source/getting_started/quickstart.rst
Normal file
256
docs/source/getting_started/quickstart.rst
Normal file
|
@ -0,0 +1,256 @@
|
|||
Quick Start
|
||||
===========
|
||||
|
||||
After installing the manim environment according to the instructions on the
|
||||
:doc:`installation` page, you can try to make a scene yourself from scratch.
|
||||
|
||||
First, create a new ``.py`` file (such as ``start.py``) according to the following
|
||||
directory structure:
|
||||
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 8
|
||||
|
||||
manim/
|
||||
├── manimlib/
|
||||
│ ├── animation/
|
||||
│ ├── ...
|
||||
│ └── window.py
|
||||
├── custom_default.yml
|
||||
├── manim.py
|
||||
└── start.py
|
||||
|
||||
And paste the following code (I will explain the function of each line in detail later):
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
|
||||
self.add(circle)
|
||||
|
||||
And run this command:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python manim.py start.py SquareToCircle
|
||||
|
||||
A window will pop up on the screen. And then you can :
|
||||
|
||||
- scroll the middle mouse button to move the screen up and down
|
||||
- hold down the :kbd:`z` on the keyboard while scrolling the middle mouse button to zoom the screen
|
||||
- hold down the :kbd:`s` key on the keyboard and move the mouse to pan the screen
|
||||
- hold down the :kbd:`d` key on the keyboard and move the mouse to change the three-dimensional perspective.
|
||||
|
||||
Finally, you can close the window and exit the program by pressing :kbd:`q`.
|
||||
|
||||
Run this command again:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python manim.py start.py SquareToCircle -os
|
||||
|
||||
At this time, no window will pop up. When the program is finished, this rendered
|
||||
image will be automatically opened (saved in the subdirectory ``images/`` of the same
|
||||
level directory of ``start.py`` by default):
|
||||
|
||||
.. image:: ../_static/quickstart/SquareToCircle.png
|
||||
:align: center
|
||||
|
||||
Make an image
|
||||
-------------
|
||||
|
||||
Next, let's take a detailed look at what each row does.
|
||||
|
||||
**Line 1**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
This will import all the classes that may be used when using manim.
|
||||
|
||||
**Line 3**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
|
||||
Create a :class:`Scene` subclass ``SquareToCircle``, which will be
|
||||
the scene you write and render.
|
||||
|
||||
**Line 4**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def construct(self):
|
||||
|
||||
Write the ``construct()`` method, the content of which will determine
|
||||
how to create the mobjects in the screen and what operations need to be performed.
|
||||
|
||||
**Line 5**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
circle = Circle()
|
||||
|
||||
Create a circle (an instance of the :class:`Circle` class), called ``circle``
|
||||
|
||||
**Line 6~7**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
|
||||
Set the circle style by calling the circle's method.
|
||||
|
||||
- The ``.set_fill()`` method sets the fill color of this circle to blue (``BLUE``, defined in :doc:`../documentation/constants`), and the fill transparency to 0.5.
|
||||
- The ``.set_stroke()`` method sets the stroke color of this circle to dark blue (``BLUE_E``, defined in :doc:`../documentation/constants`), and the stroke width to 4.
|
||||
|
||||
**Line 9**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.add(circle)
|
||||
|
||||
Add this circle to the screen through the ``.add()`` method of :class:`Scene`.
|
||||
|
||||
Add animations
|
||||
--------------
|
||||
|
||||
Let's change some codes and add some animations to make videos instead of just pictures.
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
class SquareToCircle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
square = Square()
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(square, circle))
|
||||
self.wait()
|
||||
|
||||
Run this command this time:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python manim.py start.py SquareToCircle
|
||||
|
||||
The pop-up window will play animations of drawing a square and transforming
|
||||
it into a circle. If you want to save this video, run:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python manim.py start.py SquareToCircle -ow
|
||||
|
||||
This time there will be no pop-up window, but the video file (saved in the subdirectory
|
||||
``videos/`` of the same level directory of ``start.py`` by default) will be automatically
|
||||
opened after the operation is over:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircle.mp4"></video>
|
||||
|
||||
Let's take a look at the code this time. The first 7 lines are the same as the previous
|
||||
ones, and the 8th line is similar to the 5th line, which creates an instance of the
|
||||
:class:`Square` class and named it ``square``.
|
||||
|
||||
**Line 10**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
|
||||
An animation is played through :class:`Scene`'s ``.play()`` method. :class:`ShowCreation`
|
||||
is an animation that shows the process of creating a given mobject.
|
||||
``self.play(ShowCreation(square))`` is to play the animation of creating ``square``.
|
||||
|
||||
**Line 11**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.wait()
|
||||
|
||||
Use :class:`Scene`'s ``.wait()`` method to pause (default 1s), you can pass in
|
||||
parameters to indicate the pause time (for example, ``self.wait(3)`` means pause for 3s).
|
||||
|
||||
**Line 12**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.play(ReplacementTransform(square, circle))
|
||||
|
||||
Play the animation that transforms ``square`` into ``circle``.
|
||||
``ReplacementTransform(A, B)`` means to transform A into B's pattern and replace A with B.
|
||||
|
||||
**Line 13**: Same as line 11, pause for 1s.
|
||||
|
||||
|
||||
Enable interaction
|
||||
------------------
|
||||
|
||||
Interaction is a new feature of the new version. You can add the following line
|
||||
at the end of the code to enable interaction:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.embed()
|
||||
|
||||
Then run ``python manim.py start.py SquareToCircle``.
|
||||
|
||||
After the previous animation is executed, the ipython terminal will be opened on
|
||||
the command line. After that, you can continue to write code in it, and the statement
|
||||
you entered will be executed immediately after pressing :kbd:`Enter`.
|
||||
|
||||
For example: input the following lines (without comment lines) into it respectively
|
||||
(``self.play`` can be abbreviated as ``play`` in this mode):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Stretched 4 times in the vertical direction
|
||||
play(circle.stretch, 4, {"dim": 0})
|
||||
# Rotate the ellipse 90°
|
||||
play(Rotate(circle, TAU / 4))
|
||||
# Move 2 units to the right and shrink to 1/4 of the original
|
||||
play(circle.shift, 2 * RIGHT, circle.scale, 0.25)
|
||||
# Insert 10 curves into circle for non-linear transformation (no animation will play)
|
||||
circle.insert_n_curves(10)
|
||||
# Apply a complex transformation of f(z)=z^2 to all points on the circle
|
||||
play(circle.apply_complex_function, lambda z: z**2)
|
||||
# Close the window and exit the program
|
||||
exit()
|
||||
|
||||
You will get an animation similar to the following:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircleEmbed.mp4"></video>
|
||||
|
||||
If you want to enter the interactive mode directly, you don't have to write an
|
||||
empty scene containing only ``self.embed()``, you can directly run the following command
|
||||
(this will enter the ipython terminal while the window pops up):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python manim.py
|
||||
|
||||
You succeeded!
|
||||
--------------
|
||||
|
||||
After reading the above content, you already know how to use manim.
|
||||
Below you can see some examples, in the :doc:`example_scenes` page.
|
||||
But before that, you'd better have a look at the :doc:`configuration` of manim.
|
||||
|
129
docs/source/getting_started/structure.rst
Normal file
129
docs/source/getting_started/structure.rst
Normal file
|
@ -0,0 +1,129 @@
|
|||
Manim's structure
|
||||
=================
|
||||
|
||||
|
||||
Manim's directory structure
|
||||
---------------------------
|
||||
|
||||
The manim directory looks very complicated, with a lot of files,
|
||||
but the structure is clear.
|
||||
|
||||
Below is the directory structure of manim:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
├── manim.py # Manim command entry
|
||||
├── custom_default.yml # Default configuration
|
||||
└── manimlib/ # manim library
|
||||
├── __init__.py # run from here
|
||||
├── config.py # Process CLI flags
|
||||
├── constants.py # Defined some constants
|
||||
├── extract_scene.py # Extract and run the scene
|
||||
├── imports.py # Import all required files in manimlib
|
||||
├── shader_wrapper.py # Shaders' Wrapper for convenient control
|
||||
├── window.py # Playback window
|
||||
├── tex_templates/ # Templates preset for LaTeX
|
||||
│ ├── tex_templates.tex # Tex template (will be compiled with latex, default)
|
||||
│ └── ctex_templates.tex # Tex template that support Chinese (will be compiled with xelatex)
|
||||
├── camera/
|
||||
│ └── camera.py # Including Camera and CameraFrame
|
||||
├── scene/
|
||||
│ ├── scene_file_writer.py # Used to write scene to video file
|
||||
│ ├── scene.py # The basic Scene class
|
||||
│ ├── three_d_scene.py # Three-dimensional scene
|
||||
│ ├── graph_scene.py # GraphScene (with coordinate axis)
|
||||
│ ├── reconfigurable_scene.py
|
||||
│ ├── sample_space_scene.py # Probability related sample space scene
|
||||
│ └── vector_space_scene.py # Vector field scene
|
||||
├── animation/
|
||||
│ ├── animation.py # The basic class of animation
|
||||
│ ├── composition.py # Animation group
|
||||
│ ├── creation.py # Animation related to Create
|
||||
│ ├── fading.py # Fade related animation
|
||||
│ ├── growing.py # Animation related to Grow
|
||||
│ ├── indication.py # Some animations for emphasis
|
||||
│ ├── movement.py # Animation related to movement
|
||||
│ ├── numbers.py # Realize changes to DecimalNumber
|
||||
│ ├── rotation.py # Animation related to rotation
|
||||
│ ├── specialized.py # Some uncommon animations for special projects
|
||||
│ ├── transform_matching_parts.py # Transform which can automatically match parts
|
||||
│ ├── transform.py # Some Transforms
|
||||
│ └── update.py # Realize update from function
|
||||
├── mobject/
|
||||
│ ├── mobject.py # The basic class of all math object
|
||||
│ ├── types/ # 4 types of mobject
|
||||
│ │ ├── dot_cloud.py # Dot cloud (an subclass of PMobject)
|
||||
│ │ ├── image_mobject.py # Insert pictures
|
||||
│ │ ├── point_cloud_mobject.py # PMobject (mobject composed of points)
|
||||
│ │ ├── surface.py # ParametricSurface
|
||||
│ │ └── vectorized_mobject.py # VMobject (vectorized mobject)
|
||||
│ ├── svg/ # mobject related to svg
|
||||
│ │ ├── svg_mobject.py # SVGMobject
|
||||
│ │ ├── brace.py # Brace
|
||||
│ │ ├── drawings.py # Some special mobject of svg image
|
||||
│ │ ├── tex_mobject.py # Tex and TexText implemented by LaTeX
|
||||
│ │ └── text_mobject.py # Text implemented by cairo
|
||||
│ ├── changing.py # Dynamically changing mobject
|
||||
│ ├── coordinate_systems.py # coordinate system
|
||||
│ ├── frame.py # mobject related to frame
|
||||
│ ├── functions.py # ParametricFunction
|
||||
│ ├── geometry.py # geometry mobjects
|
||||
│ ├── matrix.py # matrix
|
||||
│ ├── mobject_update_utils.py # some defined updater
|
||||
│ ├── number_line.py # Number line
|
||||
│ ├── numbers.py # Numbers that can be changed
|
||||
│ ├── probability.py # mobject related to probability
|
||||
│ ├── shape_matchers.py # mobject adapted to the size of other objects
|
||||
│ ├── three_dimensions.py # Three-dimensional objects
|
||||
│ ├── value_tracker.py # ValueTracker which storage number
|
||||
│ └── vector_field.py # VectorField
|
||||
├── once_useful_constructs/ # 3b1b's Common scenes written for some videos
|
||||
│ └── ...
|
||||
├── shaders/ # GLSL scripts for rendering
|
||||
│ ├── simple_vert.glsl # a simple glsl script for position
|
||||
│ ├── insert/ # glsl scripts to be inserted in other glsl scripts
|
||||
│ │ ├── NOTE.md # explain how to insert glsl scripts
|
||||
│ │ └── ... # useful scripts
|
||||
│ ├── image/ # glsl for images
|
||||
│ │ └── ... # containing shaders for vertex and fragment
|
||||
│ ├── quadratic_bezier_fill/ # glsl for the fill of quadratic bezier curve
|
||||
│ │ └── ... # containing shaders for vertex, fragment and geometry
|
||||
│ ├── quadratic_bezier_stroke/ # glsl for the stroke of quadratic bezier curve
|
||||
│ │ └── ... # containing shaders for vertex, fragment and geometry
|
||||
│ ├── surface/ # glsl for surfaces
|
||||
│ │ └── ... # containing shaders for vertex and fragment
|
||||
│ ├── textured_surface/ # glsl for textured_surface
|
||||
│ │ └── ... # containing shaders for vertex and fragment
|
||||
│ └── true_dot/ # glsl for a dot
|
||||
│ └── ... # containing shaders for vertex, fragment and geometry
|
||||
└── utils/ # Some useful utility functions
|
||||
├── bezier.py # For bezier curve
|
||||
├── color.py # For color
|
||||
├── config_ops.py # Process CONFIG
|
||||
├── customization.py # Read from custom_default.yml
|
||||
├── debug.py # Utilities for debugging in program
|
||||
├── family_ops.py # Process family members
|
||||
├── file_ops.py # Process files and directories
|
||||
├── images.py # Read image
|
||||
├── iterables.py # Functions related to list/dictionary processing
|
||||
├── paths.py # Curve path
|
||||
├── rate_functions.py # Some defined rate_functions
|
||||
├── simple_functions.py # Some commonly used functions
|
||||
├── sounds.py # Process sounds
|
||||
├── space_ops.py # Space coordinate calculation
|
||||
├── strings.py # Process strings
|
||||
└── tex_file_writing.py # Use LaTeX to write strings as svg
|
||||
|
||||
Inheritance structure of manim's classes
|
||||
----------------------------------------
|
||||
|
||||
`Here <https://github.com/3b1b/manim/files/5824383/manim_shaders_structure.pdf>`_
|
||||
is a pdf showed inheritance structure of manim's classes, large,
|
||||
but basically all classes have included:
|
||||
|
||||
.. image:: ../_static/manim_shaders_structure.png
|
||||
|
||||
Manim execution process
|
||||
-----------------------
|
||||
|
||||
.. image:: ../_static/manim_shaders_process_en.png
|
|
@ -1,30 +1,35 @@
|
|||
.. Manim documentation master file, created by
|
||||
sphinx-quickstart on Mon May 27 14:19:19 2019.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
Manim's documentation
|
||||
=====================
|
||||
|
||||
Welcome to Manim's documentation!
|
||||
=================================
|
||||
.. image:: ../../logo/white_with_name.png
|
||||
|
||||
These docs are generated from the master branch of the
|
||||
`Manim repo <https://github.com/3b1b/manim>`_. You can contribute by submitting
|
||||
a pull request there.
|
||||
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos
|
||||
at `3Blue1Brown <https://www.3blue1brown.com/>`_.
|
||||
|
||||
And here is a Chinese version of this documentation: https://manim.ml/shaders
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents
|
||||
:maxdepth: 2
|
||||
:caption: Getting Started
|
||||
|
||||
about
|
||||
installation/index
|
||||
getting_started/index
|
||||
coordinate
|
||||
animation
|
||||
constants
|
||||
getting_started/installation
|
||||
getting_started/quickstart
|
||||
getting_started/configuration
|
||||
getting_started/example_scenes
|
||||
getting_started/config
|
||||
getting_started/structure
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Documentation
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
documentation/constants
|
||||
documentation/custom_default
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Development
|
||||
|
||||
development/changelog
|
||||
development/contributing
|
||||
development/about
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Instructions on installing Manim
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents
|
||||
|
||||
linux
|
||||
mac
|
||||
windows
|
|
@ -1,64 +0,0 @@
|
|||
Linux
|
||||
=====
|
||||
|
||||
Ubuntu
|
||||
------
|
||||
|
||||
Install system libraries::
|
||||
|
||||
# apt install sox ffmpeg libcairo2 libcairo2-dev
|
||||
|
||||
Install Latex distribution::
|
||||
|
||||
# apt install texlive-full
|
||||
|
||||
Install manim via pypi::
|
||||
|
||||
# pip3 install manimlib
|
||||
|
||||
OR Install manim via the git repository with venv::
|
||||
|
||||
$ git clone https://github.com/3b1b/manim
|
||||
$ cd manim
|
||||
$ python3 -m venv ./
|
||||
$ source bin/activate
|
||||
$ pip3 install -r requirement.txt
|
||||
|
||||
To use manim in virtual environment you need to activate the environment with
|
||||
the ``activate`` binary by doing ``source bin/activate``, to exit use the ``deactivate`` command.
|
||||
|
||||
.. note:: The git repository is updated first before the one on pypi. The git repository also
|
||||
includes project files used to produce 3b1b videos. Some of the old projects might not
|
||||
work as due to api changes.
|
||||
|
||||
|
||||
.. note:: The required latex packages are dictated by
|
||||
``manimlib/tex_template.tex`` which ``texlive-full`` will satisfy. The download size
|
||||
can be quite large. If you wish to install only the packages required to use
|
||||
manim, substitude ``texlive-full`` with::
|
||||
|
||||
texlive texlive-latex-extra texlive-fonts-extra
|
||||
texlive-latex-recommended texlive-science texlive-fonts-extra tipa
|
||||
|
||||
Arch Linux
|
||||
----------
|
||||
Install system libraries::
|
||||
|
||||
# pacman -S cairo ffmpeg opencv sox
|
||||
|
||||
Install Latex distribution::
|
||||
|
||||
# pacman -S texlive-most
|
||||
|
||||
OR install python-manimlib_:sup:`AUR` package::
|
||||
|
||||
$ git clone https://aur.archlinux.org/python-manimlib.git
|
||||
$ cd python-manimlib
|
||||
$ makepkg -si
|
||||
|
||||
You can use AUR helpers such as yay_:sup:`AUR`::
|
||||
|
||||
$ yay -S python-manimlib
|
||||
|
||||
.. _python-manimlib: https://aur.archlinux.org/packages/python-manimlib/
|
||||
.. _yay: https://aur.archlinux.org/packages/yay/
|
|
@ -1,12 +0,0 @@
|
|||
Mac
|
||||
===
|
||||
|
||||
The simplest way to install the system dependencies on Mac OS X is with Homebrew.
|
||||
Mac come preinstalled with python2, but to use manim, python3 is required
|
||||
|
||||
1. Install python3 https://docs.python.org/3/using/mac.html
|
||||
2. Install Cairo: ``brew install cairo``
|
||||
3. Install Sox: ``brew install sox``
|
||||
4. Install ffmpeg: ``brew install ffmpeg``
|
||||
5. Install latex (MacTeX): ``brew cask install mactex``
|
||||
6. Install manimlib ``pip install manimlib`` (or ``pip install --user manimlib`` to just yourself)
|
|
@ -1,60 +0,0 @@
|
|||
Windows
|
||||
=======
|
||||
|
||||
Install System Libraries
|
||||
------------------------
|
||||
|
||||
Make sure you have *Python 3* for Windows installed first:
|
||||
|
||||
https://www.python.org/downloads/windows/
|
||||
|
||||
Install ffmpeg:
|
||||
|
||||
https://ffmpeg.org/download.html#build-windows
|
||||
|
||||
Install sox:
|
||||
|
||||
http://sox.sourceforge.net/Main/HomePage
|
||||
|
||||
Install a latex distribution. On Windows MikTex is commonly used:
|
||||
|
||||
https://miktex.org/howto/install-miktex
|
||||
|
||||
Path configuration
|
||||
------------------
|
||||
|
||||
To invoke commandline without supplying path to the binary
|
||||
the PATH environment needs to be configured. Below are template examples, please change
|
||||
the path according to your username and specific python version. Assuming all the
|
||||
softwares are installed with no alteration to the installation paths::
|
||||
|
||||
C:\Users\$username\AppData\local\Programs\Python\Python$version\
|
||||
C:\Users\$username\AppData\local\Programs\Python\Python$version\Scripts\
|
||||
C:\MikTex\miktex\bin\x64\
|
||||
C:\ffmpeg\bin\
|
||||
|
||||
The path entries should be separated by semicolon.
|
||||
|
||||
Installing python packages and manim
|
||||
------------------------------------
|
||||
|
||||
Make sure you can start pip using ``pip`` in your commandline. Then do
|
||||
``pip install pyreadline`` for the ``readline`` package.
|
||||
|
||||
Grab the pycairo wheel binary ``pycairo‑1.18.0‑cp37‑cp37m‑win32.whl`` from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo
|
||||
and install it via ``python -m pip install C:\absolute\path\to\the\whl\file``
|
||||
|
||||
clone the manim repository if you have git ``git clone https://github.com/3b1b/manim`` or download the zip file from
|
||||
the repository page with ``Clone or download`` button and unzip it.
|
||||
|
||||
Open the commandline within the manim directory with ``Shift + Right click`` on an empty space in the folder and select ``open command window here``
|
||||
|
||||
Install manim python dependencies with ``pip install -r requirements.txt``
|
||||
|
||||
Test the installation
|
||||
---------------------
|
||||
|
||||
Type in ``python -m manim -h`` and if nothing went wrong during the installation process you should see the help text.
|
||||
|
||||
Use ``python -m manim example_scenes.py SquareToCircle -pl`` to render the example scene and the file should play after rendering. The movie file should be
|
||||
in ``media/videos/example_scenes/480p15``
|
108
docs/source/manim_example_ext.py
Normal file
108
docs/source/manim_example_ext.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives, Directive
|
||||
|
||||
import jinja2
|
||||
import os
|
||||
|
||||
|
||||
class skip_manim_node(nodes.Admonition, nodes.Element):
|
||||
pass
|
||||
|
||||
|
||||
def visit(self, node, name=""):
|
||||
self.visit_admonition(node, name)
|
||||
|
||||
|
||||
def depart(self, node):
|
||||
self.depart_admonition(node)
|
||||
|
||||
|
||||
class ManimExampleDirective(Directive):
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
option_spec = {
|
||||
"hide_code": bool,
|
||||
"media": str,
|
||||
}
|
||||
final_argument_whitespace = True
|
||||
|
||||
def run(self):
|
||||
hide_code = "hide_code" in self.options
|
||||
scene_name = self.arguments[0]
|
||||
media_file_name = self.options["media"]
|
||||
|
||||
source_block = [
|
||||
".. code-block:: python",
|
||||
"",
|
||||
*[" " + line for line in self.content],
|
||||
]
|
||||
source_block = "\n".join(source_block)
|
||||
|
||||
state_machine = self.state_machine
|
||||
document = state_machine.document
|
||||
|
||||
if any(media_file_name.endswith(ext) for ext in [".png", ".jpg", ".gif"]):
|
||||
is_video = False
|
||||
else:
|
||||
is_video = True
|
||||
|
||||
rendered_template = jinja2.Template(TEMPLATE).render(
|
||||
scene_name=scene_name,
|
||||
scene_name_lowercase=scene_name.lower(),
|
||||
hide_code=hide_code,
|
||||
is_video=is_video,
|
||||
media_file_name=media_file_name,
|
||||
source_block=source_block,
|
||||
)
|
||||
state_machine.insert_input(
|
||||
rendered_template.split("\n"), source=document.attributes["source"]
|
||||
)
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_node(skip_manim_node, html=(visit, depart))
|
||||
|
||||
setup.app = app
|
||||
setup.config = app.config
|
||||
setup.confdir = app.confdir
|
||||
|
||||
app.add_directive("manim-example", ManimExampleDirective)
|
||||
|
||||
metadata = {"parallel_read_safe": False, "parallel_write_safe": True}
|
||||
return metadata
|
||||
|
||||
|
||||
TEMPLATE = r"""
|
||||
{% if not hide_code %}
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="manim-example">
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if is_video %}
|
||||
.. raw:: html
|
||||
|
||||
<video id="{{ scene_name_lowercase }}" class="manim-video" controls loop autoplay src="{{ media_file_name }}"></video>
|
||||
{% else %}
|
||||
.. image:: {{ media_file_name }}
|
||||
:align: center
|
||||
:name: {{ scene_name_lowercase }}
|
||||
{% endif %}
|
||||
|
||||
{% if not hide_code %}
|
||||
.. raw:: html
|
||||
|
||||
<h5 class="example-header">{{ scene_name }}<a class="headerlink" href="#{{ scene_name_lowercase }}">¶</a></h5>
|
||||
|
||||
{{ source_block }}
|
||||
{% endif %}
|
||||
|
||||
.. raw:: html
|
||||
|
||||
</div>
|
||||
"""
|
|
@ -1,20 +1,29 @@
|
|||
name: manim
|
||||
name: manim_shaders
|
||||
channels:
|
||||
- defaults
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- python=3.7
|
||||
- cairo
|
||||
- ffmpeg
|
||||
- colour==0.1.5
|
||||
- numpy==1.15.0
|
||||
- pillow==5.2.0
|
||||
- scipy==1.1.0
|
||||
- tqdm==4.24.0
|
||||
- opencv==3.4.2
|
||||
- pycairo==1.18.0
|
||||
- pydub==0.23.0
|
||||
- ffmpeg
|
||||
- pygments==2.6.1
|
||||
- pip
|
||||
- pip:
|
||||
- pyreadline
|
||||
- pyreadline
|
||||
- matplotlib
|
||||
- mapbox-earcut
|
||||
- moderngl_window
|
||||
- screeninfo
|
||||
- argparse
|
||||
- colour
|
||||
- numpy
|
||||
- Pillow
|
||||
- progressbar
|
||||
- scipy
|
||||
- sympy
|
||||
- tqdm
|
||||
- moderngl
|
||||
- pydub
|
||||
- pyyaml
|
||||
- validators
|
||||
- ipython
|
||||
- PyOpenGL
|
||||
- pycairo
|
||||
|
||||
|
|
|
@ -1,73 +1,98 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from manimlib.imports import *
|
||||
|
||||
# To watch one of these scenes, run the following:
|
||||
# python -m manim example_scenes.py SquareToCircle -pl
|
||||
#
|
||||
# Use the flag -l for a faster rendering at a lower
|
||||
# quality.
|
||||
# python -m manim example_scenes.py SquareToCircle
|
||||
# Use -s to skip to the end and just save the final frame
|
||||
# Use the -p to have the animation (or image, if -s was
|
||||
# used) pop up once done.
|
||||
# Use -w to write the animation to a file
|
||||
# Use -o to write it to a file and open it once done
|
||||
# Use -n <number> to skip ahead to the n'th animation of a scene.
|
||||
# Use -r <number> to specify a resolution (for example, -r 1080
|
||||
# for a 1920x1080 video)
|
||||
|
||||
|
||||
class OpeningManimExample(Scene):
|
||||
def construct(self):
|
||||
title = TextMobject("This is some \\LaTeX")
|
||||
basel = TexMobject(
|
||||
title = TexText("This is some \\LaTeX")
|
||||
basel = Tex(
|
||||
"\\sum_{n=1}^\\infty "
|
||||
"\\frac{1}{n^2} = \\frac{\\pi^2}{6}"
|
||||
)
|
||||
VGroup(title, basel).arrange(DOWN)
|
||||
self.play(
|
||||
Write(title),
|
||||
FadeInFrom(basel, UP),
|
||||
FadeIn(basel, UP),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
transform_title = TextMobject("That was a transform")
|
||||
transform_title.to_corner(UP + LEFT)
|
||||
transform_title = Text("That was a transform")
|
||||
transform_title.to_corner(UL)
|
||||
self.play(
|
||||
Transform(title, transform_title),
|
||||
LaggedStart(*map(FadeOutAndShiftDown, basel)),
|
||||
LaggedStartMap(FadeOut, basel, shift=DOWN),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
grid = NumberPlane()
|
||||
grid_title = TextMobject("This is a grid")
|
||||
grid_title.scale(1.5)
|
||||
grid_title.move_to(transform_title)
|
||||
fade_comment = Text(
|
||||
"""
|
||||
You probably don't want to overuse
|
||||
Transforms, though, a simple fade often
|
||||
looks nicer.
|
||||
""",
|
||||
font_size=36,
|
||||
color=GREY_B,
|
||||
)
|
||||
fade_comment.next_to(
|
||||
transform_title, DOWN,
|
||||
buff=LARGE_BUFF,
|
||||
aligned_edge=LEFT
|
||||
)
|
||||
self.play(FadeIn(fade_comment, shift=DOWN))
|
||||
self.wait(3)
|
||||
|
||||
grid = NumberPlane((-10, 10), (-5, 5))
|
||||
grid_title = Text(
|
||||
"But manim is for illustrating math, not text",
|
||||
)
|
||||
grid_title.to_edge(UP)
|
||||
grid_title.add_background_rectangle()
|
||||
|
||||
self.add(grid, grid_title) # Make sure title is on top of grid
|
||||
self.play(
|
||||
FadeOut(title),
|
||||
FadeInFromDown(grid_title),
|
||||
FadeOut(title, shift=LEFT),
|
||||
FadeOut(fade_comment, shift=LEFT),
|
||||
FadeIn(grid_title),
|
||||
ShowCreation(grid, run_time=3, lag_ratio=0.1),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
grid_transform_title = TextMobject(
|
||||
"That was a non-linear function \\\\"
|
||||
"applied to the grid"
|
||||
matrix = [[1, 1], [0, 1]]
|
||||
linear_transform_title = VGroup(
|
||||
Text("This is what the matrix"),
|
||||
IntegerMatrix(matrix, include_background_rectangle=True),
|
||||
Text("looks like")
|
||||
)
|
||||
grid_transform_title.move_to(grid_title, UL)
|
||||
grid.prepare_for_nonlinear_transform()
|
||||
linear_transform_title.arrange(RIGHT)
|
||||
linear_transform_title.to_edge(UP)
|
||||
|
||||
self.play(
|
||||
grid.apply_function,
|
||||
lambda p: p + np.array([
|
||||
np.sin(p[1]),
|
||||
np.sin(p[0]),
|
||||
0,
|
||||
]),
|
||||
run_time=3,
|
||||
FadeOut(grid_title),
|
||||
FadeIn(linear_transform_title),
|
||||
)
|
||||
self.play(grid.apply_matrix, matrix, run_time=3)
|
||||
self.wait()
|
||||
|
||||
grid_transform_title = Text(
|
||||
"And this is a nonlinear transformation"
|
||||
)
|
||||
grid_transform_title.set_stroke(BLACK, 5, background=True)
|
||||
grid_transform_title.to_edge(UP)
|
||||
grid.prepare_for_nonlinear_transform(100)
|
||||
self.play(
|
||||
Transform(grid_title, grid_transform_title)
|
||||
ApplyPointwiseFunction(
|
||||
lambda p: p + np.array([np.sin(p[1]), np.sin(p[0]), 0]),
|
||||
grid,
|
||||
run_time=5,
|
||||
),
|
||||
FadeOut(linear_transform_title),
|
||||
FadeIn(grid_transform_title),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
@ -75,62 +100,404 @@ class OpeningManimExample(Scene):
|
|||
class SquareToCircle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
circle.set_fill(BLUE, opacity=0.5)
|
||||
circle.set_stroke(BLUE_E, width=4)
|
||||
square = Square()
|
||||
square.flip(RIGHT)
|
||||
square.rotate(-3 * TAU / 8)
|
||||
circle.set_fill(PINK, opacity=0.5)
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.play(Transform(square, circle))
|
||||
self.play(FadeOut(square))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(square, circle))
|
||||
self.wait()
|
||||
|
||||
# This opens an iPython termnial where you can keep writing
|
||||
# lines as if they were part of this construct method
|
||||
self.embed()
|
||||
# Try typing the following lines
|
||||
# self.play(circle.stretch, 4, {"dim": 0})
|
||||
# self.play(Rotate(circle, TAU / 4))
|
||||
# self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25)
|
||||
# circle.insert_n_curves(10)
|
||||
# self.play(circle.apply_complex_function, lambda z: z**2)
|
||||
|
||||
|
||||
class WarpSquare(Scene):
|
||||
class AnimatingMethods(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.play(ApplyPointwiseFunction(
|
||||
lambda point: complex_to_R3(np.exp(R3_to_complex(point))),
|
||||
square
|
||||
))
|
||||
grid = Tex(r"\pi").get_grid(10, 10, height=4)
|
||||
self.add(grid)
|
||||
|
||||
# If you pass in a mobject method to the scene's "play" function,
|
||||
# it will apply an animation interpolating between the mobject's
|
||||
# initial state and whatever happens when you apply that method.
|
||||
# For example, calling grid.shift(2 * LEFT) would shift it two units
|
||||
# to the left, but the following line animates that motion.
|
||||
self.play(grid.shift, 2 * LEFT)
|
||||
# The same applies for any method, including those setting colors.
|
||||
self.play(grid.set_submobject_colors_by_gradient, BLUE, GREEN)
|
||||
self.play(grid.set_height, TAU - MED_SMALL_BUFF)
|
||||
self.wait()
|
||||
|
||||
# The method Mobject.apply_complex_function lets you apply arbitrary
|
||||
# complex functions, treating the points defining the mobject as
|
||||
# complex numbers.
|
||||
self.play(grid.apply_complex_function, np.exp, run_time=5)
|
||||
self.wait()
|
||||
|
||||
# Even more generally, you could apply Mobject.apply_function,
|
||||
# which takes in functions form R^3 to R^3
|
||||
self.play(
|
||||
grid.apply_function,
|
||||
lambda p: [
|
||||
p[0] + 0.5 * math.sin(p[1]),
|
||||
p[1] + 0.5 * math.sin(p[0]),
|
||||
p[2]
|
||||
],
|
||||
run_time=5,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class WriteStuff(Scene):
|
||||
class TextExample(Scene):
|
||||
def construct(self):
|
||||
example_text = TextMobject(
|
||||
"This is some text",
|
||||
tex_to_color_map={"text": YELLOW}
|
||||
# To run this scene properly, you should have "Consolas" font in your computer
|
||||
# for full usage, you can see https://github.com/3b1b/manim/pull/680
|
||||
text = Text("Here is a text", font="Consolas", font_size=90)
|
||||
difference = Text(
|
||||
"""
|
||||
The most important difference between Text and TexText is that\n
|
||||
you can change the font more easily, but can't use the LaTeX grammar
|
||||
""",
|
||||
font="Arial", font_size=24,
|
||||
# t2c is a dict that you can choose color for different text
|
||||
t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE}
|
||||
)
|
||||
example_tex = TexMobject(
|
||||
"\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}",
|
||||
)
|
||||
group = VGroup(example_text, example_tex)
|
||||
group.arrange(DOWN)
|
||||
group.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
|
||||
VGroup(text, difference).arrange(DOWN, buff=1)
|
||||
self.play(Write(text))
|
||||
self.play(FadeIn(difference, UP))
|
||||
self.wait(3)
|
||||
|
||||
self.play(Write(example_text))
|
||||
self.play(Write(example_tex))
|
||||
fonts = Text(
|
||||
"And you can also set the font according to different words",
|
||||
font="Arial",
|
||||
t2f={"font": "Consolas", "words": "Consolas"},
|
||||
t2c={"font": BLUE, "words": GREEN}
|
||||
)
|
||||
slant = Text(
|
||||
"And the same as slant and weight",
|
||||
font="Consolas",
|
||||
t2s={"slant": ITALIC},
|
||||
t2w={"weight": BOLD},
|
||||
t2c={"slant": ORANGE, "weight": RED}
|
||||
)
|
||||
VGroup(fonts, slant).arrange(DOWN, buff=0.8)
|
||||
self.play(FadeOut(text), FadeOut(difference, shift=DOWN))
|
||||
self.play(Write(fonts))
|
||||
self.wait()
|
||||
self.play(Write(slant))
|
||||
self.wait()
|
||||
|
||||
|
||||
class TexTransformExample(Scene):
|
||||
def construct(self):
|
||||
to_isolate = ["B", "C", "=", "(", ")"]
|
||||
lines = VGroup(
|
||||
# Surrounding substrings with double braces
|
||||
# will ensure that those parts are separated
|
||||
# out in the Tex. For example, here the
|
||||
# Tex will have 5 submobjects, corresponding
|
||||
# to the strings [A^2, +, B^2, =, C^2]
|
||||
Tex("{{A^2}} + {{B^2}} = {{C^2}}"),
|
||||
Tex("{{A^2}} = {{C^2}} - {{B^2}}"),
|
||||
# Alternatively, you can pass in the keyword argument
|
||||
# "isolate" with a list of strings that should be out as
|
||||
# their own submobject. So both lines below are equivalent
|
||||
# to what you'd get by wrapping every instance of "B", "C"
|
||||
# "=", "(" and ")" with double braces
|
||||
Tex("{{A^2}} = (C + B)(C - B)", isolate=to_isolate),
|
||||
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=to_isolate)
|
||||
)
|
||||
lines.arrange(DOWN, buff=LARGE_BUFF)
|
||||
for line in lines:
|
||||
line.set_color_by_tex_to_color_map({
|
||||
"A": BLUE,
|
||||
"B": TEAL,
|
||||
"C": GREEN,
|
||||
})
|
||||
|
||||
play_kw = {"run_time": 2}
|
||||
self.add(lines[0])
|
||||
# The animation TransformMatchingTex will line up parts
|
||||
# of the source and target which have matching tex strings.
|
||||
# Here, giving it a little path_arc makes each part sort of
|
||||
# rotate into their final positions, which feels appropriate
|
||||
# for the idea of rearranging an equation
|
||||
self.play(
|
||||
TransformMatchingTex(
|
||||
lines[0].copy(), lines[1],
|
||||
path_arc=90 * DEGREES,
|
||||
),
|
||||
**play_kw
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Now, we could try this again on the next line...
|
||||
self.play(
|
||||
TransformMatchingTex(lines[1].copy(), lines[2]),
|
||||
**play_kw
|
||||
)
|
||||
self.wait()
|
||||
# ...and this looks nice enough, but since there's no tex
|
||||
# in lines[2] which matches "C^2" or "B^2", those terms fade
|
||||
# out to nothing while the C and B terms fade in from nothing.
|
||||
# If, however, we want the C^2 to go to C, and B^2 to go to B,
|
||||
# we can specify that with a key map.
|
||||
self.play(FadeOut(lines[2]))
|
||||
self.play(
|
||||
TransformMatchingTex(
|
||||
lines[1].copy(), lines[2],
|
||||
key_map={
|
||||
"C^2": "C",
|
||||
"B^2": "B",
|
||||
}
|
||||
),
|
||||
**play_kw
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# And to finish off, a simple TransformMatchingShapes would work
|
||||
# just fine. But perhaps we want that exponent on A^2 to transform into
|
||||
# the square root symbol. At the moment, lines[2] treats the expression
|
||||
# A^2 as a unit, so we might create a new version of the same line which
|
||||
# separates out just the A. This way, when TransformMatchingTex lines up
|
||||
# all matching parts, the only mismatch will be between the "^2" from
|
||||
# new_line2 and the "\sqrt" from the final line. By passing in,
|
||||
# transform_mismatches=True, it will transform this "^2" part into
|
||||
# the "\sqrt" part.
|
||||
new_line2 = Tex("{{A}}^2 = (C + B)(C - B)", isolate=to_isolate)
|
||||
new_line2.replace(lines[2])
|
||||
new_line2.match_style(lines[2])
|
||||
|
||||
self.play(
|
||||
TransformMatchingTex(
|
||||
new_line2, lines[3],
|
||||
transform_mismatches=True,
|
||||
),
|
||||
**play_kw
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(FadeOut(lines, RIGHT))
|
||||
|
||||
# Alternatively, if you don't want to think about breaking up
|
||||
# the tex strings deliberately, you can TransformMatchingShapes,
|
||||
# which will try to line up all pieces of a source mobject with
|
||||
# those of a target, regardless of the submobject hierarchy in
|
||||
# each one, according to whether those pieces have the same
|
||||
# shape (as best it can).
|
||||
source = Text("the morse code", height=1)
|
||||
target = Text("here come dots", height=1)
|
||||
|
||||
self.play(Write(source))
|
||||
self.wait()
|
||||
kw = {"run_time": 3, "path_arc": PI / 2}
|
||||
self.play(TransformMatchingShapes(source, target, **kw))
|
||||
self.wait()
|
||||
self.play(TransformMatchingShapes(target, source, **kw))
|
||||
self.wait()
|
||||
|
||||
|
||||
class UpdatersExample(Scene):
|
||||
def construct(self):
|
||||
decimal = DecimalNumber(
|
||||
0,
|
||||
show_ellipsis=True,
|
||||
num_decimal_places=3,
|
||||
include_sign=True,
|
||||
)
|
||||
square = Square().to_edge(UP)
|
||||
square = Square()
|
||||
square.set_fill(BLUE_E, 1)
|
||||
|
||||
decimal.add_updater(lambda d: d.next_to(square, RIGHT))
|
||||
decimal.add_updater(lambda d: d.set_value(square.get_center()[1]))
|
||||
self.add(square, decimal)
|
||||
# On all all frames, the constructor Brace(square, UP) will
|
||||
# be called, and the mobject brace will set its data to match
|
||||
# that of the newly constructed object
|
||||
brace = always_redraw(Brace, square, UP)
|
||||
|
||||
text, number = label = VGroup(
|
||||
Text("Width = "),
|
||||
DecimalNumber(
|
||||
0,
|
||||
show_ellipsis=True,
|
||||
num_decimal_places=2,
|
||||
include_sign=True,
|
||||
)
|
||||
)
|
||||
label.arrange(RIGHT)
|
||||
|
||||
# This ensures that the method deicmal.next_to(square)
|
||||
# is called on every frame
|
||||
always(label.next_to, brace, UP)
|
||||
# You could also write the following equivalent line
|
||||
# label.add_updater(lambda m: m.next_to(brace, UP))
|
||||
|
||||
# If the argument itself might change, you can use f_always,
|
||||
# for which the arguments following the initial Mobject method
|
||||
# should be functions returning arguments to that method.
|
||||
# The following line ensures thst decimal.set_value(square.get_y())
|
||||
# is called every frame
|
||||
f_always(number.set_value, square.get_width)
|
||||
# You could also write the following equivalent line
|
||||
# number.add_updater(lambda m: m.set_value(square.get_width()))
|
||||
|
||||
self.add(square, brace, label)
|
||||
|
||||
# Notice that the brace and label track with the square
|
||||
self.play(
|
||||
square.to_edge, DOWN,
|
||||
square.scale, 2,
|
||||
rate_func=there_and_back,
|
||||
run_time=5,
|
||||
run_time=2,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
square.set_width, 5, {"stretch": True},
|
||||
run_time=3,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
square.set_width, 2,
|
||||
run_time=3
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# See old_projects folder for many, many more
|
||||
# In general, you can alway call Mobject.add_updater, and pass in
|
||||
# a function that you want to be called on every frame. The function
|
||||
# should take in either one argument, the mobject, or two arguments,
|
||||
# the mobject and the amount of time since the last frame.
|
||||
now = self.time
|
||||
w0 = square.get_width()
|
||||
square.add_updater(
|
||||
lambda m: m.set_width(w0 * math.cos(self.time - now))
|
||||
)
|
||||
self.wait(4 * PI)
|
||||
|
||||
|
||||
class SurfaceExample(Scene):
|
||||
CONFIG = {
|
||||
"camera_class": ThreeDCamera,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
surface_text = Text("For 3d scenes, try using surfaces")
|
||||
surface_text.fix_in_frame()
|
||||
surface_text.to_edge(UP)
|
||||
self.add(surface_text)
|
||||
self.wait(0.1)
|
||||
|
||||
torus1 = Torus(r1=1, r2=1)
|
||||
torus2 = Torus(r1=3, r2=1)
|
||||
sphere = Sphere(radius=3, resolution=torus1.resolution)
|
||||
# You can texture a surface with up to two images, which will
|
||||
# be interpreted as the side towards the light, and away from
|
||||
# the light. These can be either urls, or paths to a local file
|
||||
# in whatever you've set as the image directory in
|
||||
# the custom_defaults.yml file
|
||||
|
||||
# day_texture = "EarthTextureMap"
|
||||
# night_texture = "NightEarthTextureMap"
|
||||
day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
|
||||
night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg"
|
||||
|
||||
surfaces = [
|
||||
TexturedSurface(surface, day_texture, night_texture)
|
||||
for surface in [sphere, torus1, torus2]
|
||||
]
|
||||
|
||||
for mob in surfaces:
|
||||
mob.shift(IN)
|
||||
mob.mesh = SurfaceMesh(mob)
|
||||
mob.mesh.set_stroke(BLUE, 1, opacity=0.5)
|
||||
|
||||
# Set perspective
|
||||
frame = self.camera.frame
|
||||
frame.set_euler_angles(
|
||||
theta=-30 * DEGREES,
|
||||
phi=70 * DEGREES,
|
||||
)
|
||||
|
||||
surface = surfaces[0]
|
||||
|
||||
self.play(
|
||||
FadeIn(surface),
|
||||
ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3),
|
||||
)
|
||||
for mob in surfaces:
|
||||
mob.add(mob.mesh)
|
||||
surface.save_state()
|
||||
self.play(Rotate(surface, PI / 2), run_time=2)
|
||||
for mob in surfaces[1:]:
|
||||
mob.rotate(PI / 2)
|
||||
|
||||
self.play(
|
||||
Transform(surface, surfaces[1]),
|
||||
run_time=3
|
||||
)
|
||||
|
||||
self.play(
|
||||
Transform(surface, surfaces[2]),
|
||||
# Move camera frame during the transition
|
||||
frame.increment_phi, -10 * DEGREES,
|
||||
frame.increment_theta, -20 * DEGREES,
|
||||
run_time=3
|
||||
)
|
||||
# Add ambient rotation
|
||||
frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
|
||||
|
||||
# Play around with where the light is
|
||||
light_text = Text("You can move around the light source")
|
||||
light_text.move_to(surface_text)
|
||||
light_text.fix_in_frame()
|
||||
|
||||
self.play(FadeTransform(surface_text, light_text))
|
||||
light = self.camera.light_source
|
||||
self.add(light)
|
||||
light.save_state()
|
||||
self.play(light.move_to, 3 * IN, run_time=5)
|
||||
self.play(light.shift, 10 * OUT, run_time=5)
|
||||
|
||||
drag_text = Text("Try moving the mouse while pressing d or s")
|
||||
drag_text.move_to(light_text)
|
||||
drag_text.fix_in_frame()
|
||||
|
||||
self.play(FadeTransform(light_text, drag_text))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ControlsExample(Scene):
|
||||
def setup(self):
|
||||
self.textbox = Textbox()
|
||||
self.checkbox = Checkbox()
|
||||
self.color_picker = ColorSliders()
|
||||
self.panel = ControlPanel(
|
||||
Text("Text", size=0.5), self.textbox, Line(),
|
||||
Text("Show/Hide Text", size=0.5), self.checkbox, Line(),
|
||||
Text("Color of Text", size=0.5), self.color_picker
|
||||
)
|
||||
self.add(self.panel)
|
||||
|
||||
def construct(self):
|
||||
text = Text("", size=2)
|
||||
|
||||
def text_updater(old_text):
|
||||
assert(isinstance(old_text, Text))
|
||||
new_text = Text(self.textbox.get_value(), size=old_text.size)
|
||||
# new_text.align_data_and_family(old_text)
|
||||
new_text.move_to(old_text)
|
||||
if self.checkbox.get_value():
|
||||
new_text.set_fill(
|
||||
color=self.color_picker.get_picked_color(),
|
||||
opacity=self.color_picker.get_picked_opacity()
|
||||
)
|
||||
else:
|
||||
new_text.set_opacity(0)
|
||||
old_text.become(new_text)
|
||||
|
||||
text.add_updater(text_updater)
|
||||
|
||||
self.add(MotionMobject(text))
|
||||
|
||||
self.textbox.set_value("Manim")
|
||||
self.embed()
|
||||
|
||||
|
||||
# See https://github.com/3b1b/videos for many, many more
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,107 +0,0 @@
|
|||
from active_projects.diffyq.part1.pendulum import *
|
||||
from active_projects.diffyq.part1.staging import *
|
||||
from active_projects.diffyq.part1.pi_scenes import *
|
||||
from active_projects.diffyq.part1.phase_space import *
|
||||
from active_projects.diffyq.part1.wordy_scenes import *
|
||||
|
||||
OUTPUT_DIRECTORY = "diffyq/part1"
|
||||
SCENES_IN_ORDER = [
|
||||
WhenChangeIsEasier,
|
||||
VectorFieldTest,
|
||||
IntroducePendulum,
|
||||
MultiplePendulumsOverlayed,
|
||||
PeriodFormula,
|
||||
FormulasAreLies,
|
||||
MediumAnglePendulum,
|
||||
MediumHighAnglePendulum,
|
||||
HighAnglePendulum,
|
||||
LowAnglePendulum,
|
||||
SomeOfYouWatching,
|
||||
SmallAngleApproximationTex,
|
||||
VeryLowAnglePendulum,
|
||||
FormulasAreLies,
|
||||
TourOfDifferentialEquations,
|
||||
WherePendulumLeads,
|
||||
LongDoublePendulum,
|
||||
# FollowThisThread,
|
||||
StrogatzQuote,
|
||||
ShowHorizontalDashedLine,
|
||||
RabbitFoxPopulations,
|
||||
RabbitFoxEquation,
|
||||
# Something...
|
||||
ShowSimpleTrajectory,
|
||||
SimpleProjectileEquation,
|
||||
SimpleProjectileEquationVGraphFreedom,
|
||||
ShowGravityAcceleration,
|
||||
UniversalGravityLawSymbols,
|
||||
ExampleTypicalODE,
|
||||
AnalyzePendulumForce,
|
||||
ShowSineValues,
|
||||
BuildUpEquation,
|
||||
AirResistanceBrace,
|
||||
ShowDerivativeVideo,
|
||||
SubtleAirCurrents,
|
||||
SimpleDampenedPendulum,
|
||||
DefineODE,
|
||||
SecondOrderEquationExample,
|
||||
ODEvsPDEinFrames,
|
||||
ProveTeacherWrong,
|
||||
SetAsideSeekingSolution,
|
||||
#
|
||||
WriteInRadians,
|
||||
XEqLThetaToCorner,
|
||||
ComingUp,
|
||||
InputLabel,
|
||||
SoWhatIsThetaThen,
|
||||
ReallyHardToSolve,
|
||||
ReasonForSolution,
|
||||
PhysicistPhaseSpace,
|
||||
GleickQuote,
|
||||
SpectrumOfStartingStates,
|
||||
WritePhaseFlow,
|
||||
AskAboutStability,
|
||||
LoveExample,
|
||||
PassageOfTime,
|
||||
LovePhaseSpace,
|
||||
ComparePhysicsToLove,
|
||||
FramesComparingPhysicsToLove,
|
||||
SetupToTakingManyTinySteps,
|
||||
ShowClutterPrevention,
|
||||
# VisualizeHeightSlopeCurvature,
|
||||
VisualizeStates,
|
||||
ReferencePiCollisionStateSpaces,
|
||||
IntroduceVectorField,
|
||||
XComponentArrows,
|
||||
BreakingSecondOrderIntoTwoFirstOrder,
|
||||
ShowPendulumPhaseFlow,
|
||||
ShowHighVelocityCase,
|
||||
TweakMuInFormula,
|
||||
TweakMuInVectorField,
|
||||
FromODEToVectorField,
|
||||
LorenzVectorField,
|
||||
ThreeBodiesInSpace,
|
||||
AltThreeBodiesInSpace,
|
||||
TwoBodiesInSpace,
|
||||
TwoBodiesWithZPart,
|
||||
ThreeBodyTitle,
|
||||
ThreeBodySymbols,
|
||||
#
|
||||
HighAmplitudePendulum,
|
||||
WritePhaseSpace,
|
||||
#
|
||||
AskAboutActuallySolving,
|
||||
WriteODESolvingCode,
|
||||
TakeManyTinySteps,
|
||||
ManyStepsFromDifferentStartingPoints,
|
||||
InaccurateComputation,
|
||||
HungerForExactness,
|
||||
ShowRect,
|
||||
ShowSquare,
|
||||
JumpToThisPoint,
|
||||
ThreeBodyEquation,
|
||||
ItGetsWorse,
|
||||
ChaosTitle,
|
||||
RevisitQuote,
|
||||
EndScreen,
|
||||
Thumbnail,
|
||||
]
|
|
@ -1,41 +0,0 @@
|
|||
from active_projects.diffyq.part2.staging import *
|
||||
from active_projects.diffyq.part2.fourier_series import *
|
||||
from active_projects.diffyq.part2.heat_equation import *
|
||||
from active_projects.diffyq.part2.pi_scenes import *
|
||||
from active_projects.diffyq.part2.wordy_scenes import *
|
||||
|
||||
OUTPUT_DIRECTORY = "diffyq/part2"
|
||||
SCENES_IN_ORDER = [
|
||||
PartTwoOfTour,
|
||||
HeatEquationIntroTitle,
|
||||
BrownianMotion,
|
||||
BlackScholes,
|
||||
ContrastChapters1And2,
|
||||
FourierSeriesIntro,
|
||||
FourierSeriesIntroBackground20,
|
||||
ExplainCircleAnimations,
|
||||
# FourierSeriesIntroBackground4,
|
||||
# FourierSeriesIntroBackground8,
|
||||
# FourierSeriesIntroBackground12,
|
||||
TwoDBodyWithManyTemperatures,
|
||||
TwoDBodyWithManyTemperaturesGraph,
|
||||
TwoDBodyWithManyTemperaturesContour,
|
||||
BringTwoRodsTogether,
|
||||
ShowEvolvingTempGraphWithArrows,
|
||||
# TodaysTargetWrapper,
|
||||
WriteHeatEquation,
|
||||
ReactionsToInitialHeatEquation,
|
||||
TalkThrough1DHeatGraph,
|
||||
ShowCubeFormation,
|
||||
CompareInputsOfGeneralCaseTo1D,
|
||||
ContrastXChangesToTChanges,
|
||||
ShowPartialDerivativeSymbols,
|
||||
WriteHeatEquation,
|
||||
ShowCurvatureToRateOfChangeIntuition,
|
||||
ContrastPDEToODE,
|
||||
TransitionToTempVsTime,
|
||||
Show1DAnd3DEquations,
|
||||
#
|
||||
AskAboutWhereEquationComesFrom,
|
||||
DiscreteSetup,
|
||||
]
|
|
@ -1,70 +0,0 @@
|
|||
from active_projects.diffyq.part3.staging import *
|
||||
from active_projects.diffyq.part3.temperature_graphs import *
|
||||
from active_projects.diffyq.part3.pi_creature_scenes import *
|
||||
from active_projects.diffyq.part3.wordy_scenes import *
|
||||
from active_projects.diffyq.part3.discrete_case import *
|
||||
|
||||
|
||||
OUTPUT_DIRECTORY = "diffyq/part3"
|
||||
SCENES_IN_ORDER = [
|
||||
LastChapterWrapper,
|
||||
ThreeConstraints,
|
||||
OceanOfPossibilities,
|
||||
ThreeMainObservations,
|
||||
SimpleCosExpGraph,
|
||||
AddMultipleSolutions,
|
||||
FourierSeriesIllustraiton,
|
||||
BreakDownAFunction,
|
||||
SineCurveIsUnrealistic,
|
||||
AnalyzeSineCurve,
|
||||
EquationAboveSineAnalysis,
|
||||
ExponentialDecay,
|
||||
InvestmentGrowth,
|
||||
GrowingPileOfMoney,
|
||||
CarbonDecayCurve,
|
||||
CarbonDecayingInMammoth,
|
||||
SineWaveScaledByExp,
|
||||
ShowSinExpDerivatives,
|
||||
IfOnly,
|
||||
BoundaryConditionInterlude,
|
||||
BoundaryConditionReference,
|
||||
GiantCross,
|
||||
SimulateRealSineCurve,
|
||||
DerivativesOfLinearFunction,
|
||||
StraightLine3DGraph,
|
||||
SimulateLinearGraph,
|
||||
EmphasizeBoundaryPoints,
|
||||
ShowNewRuleAtDiscreteBoundary,
|
||||
DiscreteEvolutionPoint25,
|
||||
DiscreteEvolutionPoint1,
|
||||
FlatEdgesForDiscreteEvolution,
|
||||
FlatEdgesForDiscreteEvolutionTinySteps,
|
||||
FlatEdgesContinuousEvolution,
|
||||
FlatAtBoundaryWords,
|
||||
SlopeToHeatFlow,
|
||||
CloserLookAtStraightLine,
|
||||
WriteOutBoundaryCondition,
|
||||
SoWeGotNowhere,
|
||||
ManipulateSinExpSurface,
|
||||
HeatEquationFrame,
|
||||
ShowFreq1CosExpDecay,
|
||||
ShowFreq2CosExpDecay,
|
||||
ShowFreq4CosExpDecay,
|
||||
CompareFreqDecays1to2,
|
||||
CompareFreqDecays1to4,
|
||||
CompareFreqDecays2to4,
|
||||
ShowHarmonics,
|
||||
ShowHarmonicSurfaces,
|
||||
|
||||
# SimpleCosExpGraph,
|
||||
# AddMultipleSolutions,
|
||||
# IveHeardOfThis,
|
||||
# FourierSeriesOfLineIllustration,
|
||||
# InFouriersShoes,
|
||||
]
|
||||
|
||||
PART_4_SCENES = [
|
||||
FourierSeriesIllustraiton,
|
||||
FourierNameIntro,
|
||||
CircleAnimationOfF,
|
||||
]
|
|
@ -1,65 +0,0 @@
|
|||
from active_projects.diffyq.part4.staging import *
|
||||
from active_projects.diffyq.part4.fourier_series_scenes import *
|
||||
from active_projects.diffyq.part4.pi_creature_scenes import *
|
||||
from active_projects.diffyq.part4.three_d_graphs import *
|
||||
from active_projects.diffyq.part4.temperature_scenes import *
|
||||
from active_projects.diffyq.part4.complex_functions import *
|
||||
from active_projects.diffyq.part4.long_fourier_scenes import *
|
||||
|
||||
from active_projects.diffyq.part3.staging import *
|
||||
|
||||
OUTPUT_DIRECTORY = "diffyq/part4"
|
||||
SCENES_IN_ORDER = [
|
||||
ComplexFourierSeriesExample,
|
||||
FourierOfFourier,
|
||||
FourierOfFourierZoomedIn,
|
||||
FourierOfFourier100xZoom,
|
||||
FourierSeriesFormula,
|
||||
RelationToOtherVideos,
|
||||
WhyWouldYouCare,
|
||||
ShowLinearity,
|
||||
CombineSeveralSolutions,
|
||||
FourierGainsImmortality,
|
||||
SolveForWavesNothingElse,
|
||||
CycleThroughManyLinearCombinations,
|
||||
StepFunctionExample,
|
||||
WhichWavesAreAvailable,
|
||||
AlternateBoundaryConditions,
|
||||
AskQuestionOfGraph,
|
||||
CommentOnFouriersImmortality,
|
||||
HangOnThere,
|
||||
ShowInfiniteSum,
|
||||
TechnicalNuances,
|
||||
BreakDownStepFunction,
|
||||
StepFunctionSolutionFormla,
|
||||
# How to compute
|
||||
FourierSeriesOfLineIllustration,
|
||||
GeneralizeToComplexFunctions,
|
||||
ClarifyInputAndOutput,
|
||||
GraphForFlattenedPi,
|
||||
PiFourierSeries,
|
||||
RealValuedFunctionFourierSeries,
|
||||
YouSaidThisWasEasier,
|
||||
AskAboutComplexNotVector,
|
||||
SimpleComplexExponentExample,
|
||||
LooseWithLanguage,
|
||||
DemonstrateAddingArrows,
|
||||
TRangingFrom0To1,
|
||||
LabelRotatingVectors,
|
||||
IntegralTrick,
|
||||
SwapIntegralAndSum,
|
||||
FootnoteOnSwappingIntegralAndSum,
|
||||
FormulaOutOfContext,
|
||||
ShowRangeOfCnFormulas,
|
||||
DescribeSVG,
|
||||
# TODO
|
||||
IncreaseOrderOfApproximation,
|
||||
ShowStepFunctionIn2dView,
|
||||
StepFunctionIntegral,
|
||||
GeneralChallenge,
|
||||
|
||||
# Oldies
|
||||
# FourierSeriesIllustraiton,
|
||||
# FourierNameIntro,
|
||||
# CircleAnimationOfF,
|
||||
]
|
|
@ -1,5 +0,0 @@
|
|||
from active_projects.diffyq.part5.staging import *
|
||||
|
||||
OUTPUT_DIRECTORY = "diffyq/part5"
|
||||
SCENES_IN_ORDER = [
|
||||
]
|
|
@ -1,23 +0,0 @@
|
|||
from active_projects.diffyq.part4.long_fourier_scenes import *
|
||||
|
||||
OUTPUT_DIRECTORY = "diffyq/part4"
|
||||
SCENES_IN_ORDER = [
|
||||
ZoomedInFourierSeriesExample,
|
||||
FourierSeriesExampleWithRectForZoom,
|
||||
ZoomedInFourierSeriesExample100x,
|
||||
FourierOfFourier100xZoom,
|
||||
FourierOfFourierZoomedIn,
|
||||
FourierOfFourier,
|
||||
SigmaZoomedInFourierSeriesExample,
|
||||
SigmaFourierSeriesExampleWithRectForZoom,
|
||||
NailAndGearZoomedInFourierSeriesExample,
|
||||
NailAndGearFourierSeriesExampleWithRectForZoom,
|
||||
TrebleClefZoomedInFourierSeriesExample,
|
||||
TrebleClefFourierSeriesExampleWithRectForZoom,
|
||||
FourierOfSeattle,
|
||||
FourierOfSeattleZoomedIn,
|
||||
FourierOfBritain,
|
||||
FourierOfBritainZoomedIn,
|
||||
FourierOfHilbert,
|
||||
FourierOfHilbertZoomedIn,
|
||||
]
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,515 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
from active_projects.diffyq.part1.shared_constructs import *
|
||||
|
||||
|
||||
class SomeOfYouWatching(TeacherStudentsScene):
|
||||
CONFIG = {
|
||||
"camera_config": {
|
||||
"background_color": DARKER_GREY,
|
||||
}
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
screen = self.screen
|
||||
screen.scale(1.25, about_edge=UL)
|
||||
screen.set_fill(BLACK, 1)
|
||||
self.add(screen)
|
||||
|
||||
self.teacher.change("raise_right_hand")
|
||||
for student in self.students:
|
||||
student.change("pondering", screen)
|
||||
|
||||
self.student_says(
|
||||
"Well...yeah",
|
||||
target_mode="tease"
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class FormulasAreLies(PiCreatureScene):
|
||||
def construct(self):
|
||||
you = self.pi_creature
|
||||
t2c = {
|
||||
"{L}": BLUE,
|
||||
"{g}": YELLOW,
|
||||
"\\theta_0": WHITE,
|
||||
"\\sqrt{\\,": WHITE,
|
||||
}
|
||||
kwargs = {"tex_to_color_map": t2c}
|
||||
period_eq = TexMobject(
|
||||
"\\text{Period} = 2\\pi \\sqrt{\\,{L} / {g}}",
|
||||
**kwargs
|
||||
)
|
||||
theta_eq = TexMobject(
|
||||
"\\theta(t) = \\theta_0 \\cos\\left("
|
||||
"\\sqrt{\\,{L} / {g}} \\cdot t"
|
||||
"\\right)",
|
||||
**kwargs
|
||||
)
|
||||
equations = VGroup(theta_eq, period_eq)
|
||||
equations.arrange(DOWN, buff=LARGE_BUFF)
|
||||
|
||||
for eq in period_eq, theta_eq:
|
||||
i = eq.index_of_part_by_tex("\\sqrt")
|
||||
eq.sqrt_part = eq[i:i + 4]
|
||||
|
||||
theta0 = theta_eq.get_part_by_tex("\\theta_0")
|
||||
theta0_words = TextMobject("Starting angle")
|
||||
theta0_words.next_to(theta0, UL)
|
||||
theta0_words.shift(UP + 0.5 * RIGHT)
|
||||
arrow = Arrow(
|
||||
theta0_words.get_bottom(),
|
||||
theta0,
|
||||
color=WHITE,
|
||||
tip_length=0.25,
|
||||
)
|
||||
|
||||
bubble = SpeechBubble()
|
||||
bubble.pin_to(you)
|
||||
bubble.write("Lies!")
|
||||
bubble.content.scale(2)
|
||||
bubble.resize_to_content()
|
||||
|
||||
self.add(period_eq)
|
||||
you.change("pondering", period_eq)
|
||||
self.wait()
|
||||
theta_eq.remove(*theta_eq.sqrt_part)
|
||||
self.play(
|
||||
TransformFromCopy(
|
||||
period_eq.sqrt_part,
|
||||
theta_eq.sqrt_part,
|
||||
),
|
||||
FadeIn(theta_eq)
|
||||
)
|
||||
theta_eq.add(*theta_eq.sqrt_part)
|
||||
self.play(
|
||||
FadeInFrom(theta0_words, LEFT),
|
||||
GrowArrow(arrow),
|
||||
)
|
||||
self.wait()
|
||||
self.play(you.change, "confused")
|
||||
self.wait()
|
||||
self.play(
|
||||
you.change, "angry",
|
||||
ShowCreation(bubble),
|
||||
FadeInFromPoint(bubble.content, you.mouth),
|
||||
equations.to_edge, LEFT,
|
||||
FadeOut(arrow),
|
||||
FadeOut(theta0_words),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
def create_pi_creature(self):
|
||||
return You().flip().to_corner(DR)
|
||||
|
||||
|
||||
# class TourOfDifferentialEquations(Scene):
|
||||
# def construct(self):
|
||||
# pass
|
||||
|
||||
|
||||
class SoWhatIsThetaThen(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
ode = get_ode()
|
||||
ode.to_corner(UL)
|
||||
self.add(ode)
|
||||
|
||||
self.student_says(
|
||||
"Okay, but then\\\\"
|
||||
"what \\emph{is} $\\theta(t)$?"
|
||||
)
|
||||
self.wait()
|
||||
self.play(self.teacher.change, "happy")
|
||||
self.wait(2)
|
||||
self.teacher_says(
|
||||
"First, you must appreciate\\\\"
|
||||
"a deep truth...",
|
||||
added_anims=[self.get_student_changes(
|
||||
*3 * ["confused"]
|
||||
)]
|
||||
)
|
||||
self.wait(4)
|
||||
|
||||
|
||||
class ProveTeacherWrong(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
tex_config = {
|
||||
"tex_to_color_map": {
|
||||
"{\\theta}": BLUE,
|
||||
"{\\dot\\theta}": YELLOW,
|
||||
"{\\ddot\\theta}": RED,
|
||||
}
|
||||
}
|
||||
func = TexMobject(
|
||||
"{\\theta}(t)", "=",
|
||||
"\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)",
|
||||
**tex_config,
|
||||
)
|
||||
d_func = TexMobject(
|
||||
"{\\dot\\theta}(t)", "=",
|
||||
"-\\left(\\sqrt{g / L}\\right)",
|
||||
"\\theta_0", "\\sin(\\sqrt{g / L} \\cdot t)",
|
||||
**tex_config,
|
||||
)
|
||||
dd_func = TexMobject(
|
||||
"{\\ddot\\theta}(t)", "=",
|
||||
"-\\left(g / L\\right)",
|
||||
"\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)",
|
||||
**tex_config,
|
||||
)
|
||||
# ode = TexMobject(
|
||||
# "\\ddot {\\theta}({t})", "=",
|
||||
# "-\\mu \\dot {\\theta}({t})",
|
||||
# "-{g \\over L} \\sin\\big({\\theta}({t})\\big)",
|
||||
# **tex_config,
|
||||
# )
|
||||
ode = get_ode()
|
||||
arrows = [TexMobject("\\Downarrow") for x in range(2)]
|
||||
|
||||
VGroup(func, d_func, dd_func, ode, *arrows).scale(0.7)
|
||||
|
||||
teacher = self.teacher
|
||||
you = self.students[2]
|
||||
|
||||
self.student_thinks(ode)
|
||||
you.add_updater(lambda m: m.look_at(func))
|
||||
self.teacher_holds_up(func)
|
||||
self.wait()
|
||||
|
||||
group = VGroup(arrows[0], d_func, arrows[1], dd_func)
|
||||
group.arrange(DOWN)
|
||||
group.move_to(func, DOWN)
|
||||
|
||||
arrow = Arrow(
|
||||
group.get_corner(UL),
|
||||
ode.get_top(),
|
||||
path_arc=PI / 2,
|
||||
)
|
||||
q_marks = VGroup(*[
|
||||
TexMobject("?").scale(1.5).next_to(
|
||||
arrow.point_from_proportion(a),
|
||||
UP
|
||||
)
|
||||
for a in np.linspace(0.2, 0.8, 5)
|
||||
])
|
||||
cycle_animation(VFadeInThenOut(
|
||||
q_marks,
|
||||
lag_ratio=0.2,
|
||||
run_time=4,
|
||||
rate_func=squish_rate_func(smooth, 0, 0.5)
|
||||
))
|
||||
|
||||
self.play(
|
||||
func.next_to, group, UP,
|
||||
LaggedStartMap(
|
||||
FadeInFrom, group,
|
||||
lambda m: (m, UP)
|
||||
),
|
||||
teacher.change, "guilty",
|
||||
you.change, "sassy",
|
||||
)
|
||||
|
||||
rect = SurroundingRectangle(
|
||||
VGroup(group, func)
|
||||
)
|
||||
dashed_rect = DashedVMobject(rect, num_dashes=75)
|
||||
animated_rect = AnimatedBoundary(dashed_rect, cycle_rate=1)
|
||||
|
||||
self.wait()
|
||||
self.add(animated_rect, q_marks)
|
||||
self.play(
|
||||
ShowCreation(arrow),
|
||||
# FadeInFromDown(q_mark),
|
||||
self.get_student_changes("confused", "confused")
|
||||
)
|
||||
self.wait(4)
|
||||
self.change_student_modes(
|
||||
*3 * ["pondering"],
|
||||
self.teacher.change, "maybe"
|
||||
)
|
||||
self.wait(8)
|
||||
|
||||
|
||||
class PhysicistPhaseSpace(PiCreatureScene):
|
||||
def construct(self):
|
||||
physy = self.pi_creature
|
||||
name = TextMobject("Physicist")
|
||||
name.scale(1.5)
|
||||
name.to_corner(DL, buff=MED_SMALL_BUFF)
|
||||
physy.next_to(name, UP, SMALL_BUFF)
|
||||
VGroup(name, physy).shift_onto_screen()
|
||||
|
||||
axes = Axes(
|
||||
x_min=-1,
|
||||
x_max=10,
|
||||
y_min=-1,
|
||||
y_max=7,
|
||||
)
|
||||
axes.set_height(6)
|
||||
axes.next_to(physy, RIGHT)
|
||||
axes.to_edge(UP)
|
||||
axes.set_stroke(width=1)
|
||||
x_label = TextMobject("Position")
|
||||
x_label.next_to(axes.x_axis.get_right(), UP)
|
||||
y_label = TextMobject("Momentum")
|
||||
y_label.next_to(axes.y_axis.get_top(), RIGHT)
|
||||
|
||||
title = TextMobject("Phase space")
|
||||
title.scale(1.5)
|
||||
title.set_color(YELLOW)
|
||||
title.move_to(axes)
|
||||
|
||||
self.add(name, physy)
|
||||
|
||||
self.play(
|
||||
physy.change, "angry",
|
||||
Write(axes),
|
||||
FadeInFromDown(title)
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
GrowFromPoint(x_label, physy.get_corner(UR)),
|
||||
physy.change, "raise_right_hand",
|
||||
axes.x_axis.get_right()
|
||||
)
|
||||
self.play(
|
||||
GrowFromPoint(y_label, physy.get_corner(UR)),
|
||||
physy.look_at, axes.y_axis.get_top(),
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
def create_pi_creature(self):
|
||||
return PiCreature(color=GREY).to_corner(DL)
|
||||
|
||||
|
||||
class AskAboutActuallySolving(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
ode = get_ode()
|
||||
ode.to_corner(UL)
|
||||
self.add(ode)
|
||||
morty = self.teacher
|
||||
|
||||
self.student_says(
|
||||
"Yeah yeah, but how do\\\\"
|
||||
"you actually \\emph{solve} it?",
|
||||
student_index=1,
|
||||
target_mode="sassy",
|
||||
added_anims=[morty.change, "thinking"],
|
||||
)
|
||||
self.change_student_modes(
|
||||
"confused", "sassy", "confused",
|
||||
look_at_arg=ode,
|
||||
)
|
||||
self.wait()
|
||||
self.teacher_says(
|
||||
"What do you mean\\\\ by ``solve''?",
|
||||
target_mode="speaking",
|
||||
added_anims=[self.get_student_changes(
|
||||
*3 * ["erm"]
|
||||
)]
|
||||
)
|
||||
self.play(self.students[1].change, "angry")
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class HungerForExactness(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
students = self.students
|
||||
you = students[2]
|
||||
teacher = self.teacher
|
||||
|
||||
ode = get_ode()
|
||||
ode.to_corner(UL)
|
||||
left_part = ode[:5]
|
||||
friction_part = ode[5:11]
|
||||
self.add(ode)
|
||||
|
||||
proposed_solution = TexMobject(
|
||||
"\\theta_0\\cos((\\sqrt{g/L})t)e^{-\\mu t}"
|
||||
)
|
||||
proposed_solution.next_to(
|
||||
you.get_corner(UL), UP, buff=0.7
|
||||
)
|
||||
proposed_solution_rect = SurroundingRectangle(
|
||||
proposed_solution, buff=MED_SMALL_BUFF,
|
||||
)
|
||||
proposed_solution_rect.set_color(BLUE)
|
||||
proposed_solution_rect.round_corners()
|
||||
|
||||
solution_p1 = TexMobject(
|
||||
"""
|
||||
\\theta(t) = 2\\text{am}\\left(
|
||||
\\frac{\\sqrt{2g + Lc_1} (t + c_2)}{2\\sqrt{L}},
|
||||
\\frac{4g}{2g + Lc_1}
|
||||
\\right)
|
||||
""",
|
||||
)
|
||||
solution_p1.to_corner(UL)
|
||||
solution_p2 = TexMobject(
|
||||
"c_1, c_2 = \\text{Constants depending on initial conditions}"
|
||||
)
|
||||
solution_p2.set_color(LIGHT_GREY)
|
||||
solution_p2.scale(0.75)
|
||||
solution_p3 = TexMobject(
|
||||
"""
|
||||
\\text{am}(u, k) =
|
||||
\\int_0^u \\text{dn}(v, k)\\,dv
|
||||
"""
|
||||
)
|
||||
solution_p3.name = TextMobject(
|
||||
"(Jacobi amplitude function)"
|
||||
)
|
||||
solution_p4 = TexMobject(
|
||||
"""
|
||||
\\text{dn}(u, k) =
|
||||
\\sqrt{1 - k^2 \\sin^2(\\phi)}
|
||||
"""
|
||||
)
|
||||
solution_p4.name = TextMobject(
|
||||
"(Jacobi elliptic function)"
|
||||
)
|
||||
solution_p5 = TextMobject("Where $\\phi$ satisfies")
|
||||
solution_p6 = TexMobject(
|
||||
"""
|
||||
u = \\int_0^\\phi \\frac{dt}{\\sqrt{1 - k^2 \\sin^2(t)}}
|
||||
"""
|
||||
)
|
||||
|
||||
solution = VGroup(
|
||||
solution_p1,
|
||||
solution_p2,
|
||||
solution_p3,
|
||||
solution_p4,
|
||||
solution_p5,
|
||||
solution_p6,
|
||||
)
|
||||
solution.arrange(DOWN)
|
||||
solution.scale(0.7)
|
||||
solution.to_corner(UL, buff=MED_SMALL_BUFF)
|
||||
solution.set_stroke(width=0, background=True)
|
||||
|
||||
solution.remove(solution_p2)
|
||||
solution_p1.add(solution_p2)
|
||||
solution.remove(solution_p5)
|
||||
solution_p6.add(solution_p5)
|
||||
|
||||
for part in [solution_p3, solution_p4]:
|
||||
part.name.scale(0.7 * 0.7)
|
||||
part.name.set_color(LIGHT_GREY)
|
||||
part.name.next_to(part, RIGHT)
|
||||
part.add(part.name)
|
||||
|
||||
self.student_says(
|
||||
"Right, but like,\\\\"
|
||||
"what \\emph{is} $\\theta(t)$?",
|
||||
target_mode="sassy",
|
||||
added_anims=[teacher.change, "guilty"],
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeInFromDown(proposed_solution),
|
||||
RemovePiCreatureBubble(
|
||||
you,
|
||||
target_mode="raise_left_hand",
|
||||
look_at_arg=proposed_solution,
|
||||
),
|
||||
teacher.change, "pondering",
|
||||
students[0].change, "pondering",
|
||||
students[1].change, "hesitant",
|
||||
)
|
||||
self.play(ShowCreation(proposed_solution_rect))
|
||||
self.play(
|
||||
proposed_solution.shift, 3 * RIGHT,
|
||||
proposed_solution_rect.shift, 3 * RIGHT,
|
||||
you.change, "raise_right_hand", teacher.eyes,
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
self.play(
|
||||
FadeOut(proposed_solution),
|
||||
FadeOut(proposed_solution_rect),
|
||||
ode.move_to, self.hold_up_spot, DOWN,
|
||||
ode.shift, LEFT,
|
||||
teacher.change, "raise_right_hand",
|
||||
self.get_student_changes(*3 * ["pondering"])
|
||||
)
|
||||
self.wait()
|
||||
ode.save_state()
|
||||
self.play(
|
||||
left_part.move_to, friction_part, RIGHT,
|
||||
left_part.match_y, left_part,
|
||||
friction_part.to_corner, DR,
|
||||
friction_part.fade, 0.5,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
modes = ["erm", "sad", "sad", "horrified"]
|
||||
for part, mode in zip(solution, modes):
|
||||
self.play(
|
||||
FadeInFrom(part, UP),
|
||||
self.get_student_changes(
|
||||
*3 * [mode],
|
||||
look_at_arg=part,
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
self.wait(3)
|
||||
self.change_student_modes("tired", "sad", "concerned_musician")
|
||||
self.wait(4)
|
||||
self.look_at(solution)
|
||||
self.wait(5)
|
||||
self.play(
|
||||
FadeOutAndShift(solution, 2 * LEFT),
|
||||
Restore(ode),
|
||||
self.get_student_changes(
|
||||
"sick", "angry", "tired",
|
||||
)
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
mystery = TexMobject(
|
||||
"\\theta(t) = ???",
|
||||
tex_to_color_map={"\\theta": BLUE},
|
||||
)
|
||||
mystery.scale(2)
|
||||
mystery.to_edge(UP)
|
||||
mystery.set_stroke(width=0, background=True)
|
||||
mystery_boundary = AnimatedBoundary(
|
||||
mystery, stroke_width=1
|
||||
)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(mystery),
|
||||
self.teacher.change, "pondering"
|
||||
)
|
||||
self.add(mystery_boundary, mystery)
|
||||
self.change_all_student_modes("sad")
|
||||
self.look_at(mystery)
|
||||
self.wait(5)
|
||||
|
||||
# Define
|
||||
self.student_says(
|
||||
"Let $\\text{P}(\\mu, g, L; t)$ be a\\\\"
|
||||
"function satisfying this ODE.",
|
||||
student_index=0,
|
||||
target_mode="speaking",
|
||||
added_anims=[
|
||||
FadeOut(mystery),
|
||||
FadeOut(mystery_boundary),
|
||||
ode.to_corner, UR
|
||||
]
|
||||
)
|
||||
self.change_student_modes(
|
||||
"hooray", "sassy", "sassy",
|
||||
look_at_arg=students[0].eyes.get_corner(UR),
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class ItGetsWorse(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.teacher_says("It gets\\\\worse")
|
||||
self.change_student_modes(
|
||||
"hesitant", "pleading", "erm"
|
||||
)
|
||||
self.wait(5)
|
|
@ -1,118 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
|
||||
|
||||
Lg_formula_config = {
|
||||
"tex_to_color_map": {
|
||||
"\\theta_0": WHITE,
|
||||
"{L}": BLUE,
|
||||
"{g}": YELLOW,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class You(PiCreature):
|
||||
CONFIG = {
|
||||
"color": BLUE_C,
|
||||
}
|
||||
|
||||
|
||||
def get_ode():
|
||||
tex_config = {
|
||||
"tex_to_color_map": {
|
||||
"{\\theta}": BLUE,
|
||||
"{\\dot\\theta}": RED,
|
||||
"{\\ddot\\theta}": YELLOW,
|
||||
"{t}": WHITE,
|
||||
"{\\mu}": WHITE,
|
||||
}
|
||||
}
|
||||
ode = TexMobject(
|
||||
"{\\ddot\\theta}({t})", "=",
|
||||
"-{\\mu} {\\dot\\theta}({t})",
|
||||
"-{g \\over L} \\sin\\big({\\theta}({t})\\big)",
|
||||
**tex_config,
|
||||
)
|
||||
return ode
|
||||
|
||||
|
||||
def get_period_formula():
|
||||
return TexMobject(
|
||||
"2\\pi", "\\sqrt{\\,", "L", "/", "g", "}",
|
||||
tex_to_color_map={
|
||||
"L": BLUE,
|
||||
"g": YELLOW,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def pendulum_vector_field_func(point, mu=0.1, g=9.8, L=3):
|
||||
theta, omega = point[:2]
|
||||
return np.array([
|
||||
omega,
|
||||
-np.sqrt(g / L) * np.sin(theta) - mu * omega,
|
||||
0,
|
||||
])
|
||||
|
||||
|
||||
def get_vector_symbol(*texs, **kwargs):
|
||||
config = {
|
||||
"include_background_rectangle": True,
|
||||
"bracket_h_buff": SMALL_BUFF,
|
||||
"bracket_v_buff": SMALL_BUFF,
|
||||
"element_alignment_corner": ORIGIN,
|
||||
}
|
||||
config.update(kwargs)
|
||||
array = [[tex] for tex in texs]
|
||||
return Matrix(array, **config)
|
||||
|
||||
|
||||
def get_heart_var(index):
|
||||
heart = SuitSymbol("hearts")
|
||||
if index == 1:
|
||||
heart.set_color(BLUE_C)
|
||||
elif index == 2:
|
||||
heart.set_color(GREEN)
|
||||
heart.set_height(0.7)
|
||||
index = Integer(index)
|
||||
index.move_to(heart.get_corner(DR))
|
||||
heart.add(index)
|
||||
return heart
|
||||
|
||||
|
||||
def get_heart_var_deriv(index):
|
||||
heart = get_heart_var(index)
|
||||
filler_tex = "T"
|
||||
deriv = TexMobject("{d", filler_tex, "\\over", "dt}")
|
||||
deriv.scale(2)
|
||||
filler = deriv.get_part_by_tex(filler_tex)
|
||||
heart.match_height(filler)
|
||||
heart.move_to(filler)
|
||||
heart.scale(1.5, about_edge=UL)
|
||||
deriv.remove(filler)
|
||||
deriv.add(heart)
|
||||
deriv.heart = heart
|
||||
return deriv
|
||||
|
||||
|
||||
def get_love_equation1():
|
||||
equation = VGroup(
|
||||
get_heart_var_deriv(1),
|
||||
TexMobject("=").scale(2),
|
||||
TexMobject("a").scale(2),
|
||||
get_heart_var(2)
|
||||
)
|
||||
equation.arrange(RIGHT)
|
||||
equation[-1].shift(SMALL_BUFF * DL)
|
||||
return equation
|
||||
|
||||
|
||||
def get_love_equation2():
|
||||
equation = VGroup(
|
||||
get_heart_var_deriv(2),
|
||||
TexMobject("=").scale(2),
|
||||
TexMobject("-b").scale(2),
|
||||
get_heart_var(1),
|
||||
)
|
||||
equation.arrange(RIGHT)
|
||||
equation[-1].shift(SMALL_BUFF * DL)
|
||||
return equation
|
File diff suppressed because it is too large
Load diff
|
@ -1,855 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
from active_projects.diffyq.part1.shared_constructs import *
|
||||
|
||||
|
||||
class SmallAngleApproximationTex(Scene):
|
||||
def construct(self):
|
||||
approx = TexMobject(
|
||||
"\\sin", "(", "\\theta", ") \\approx \\theta",
|
||||
tex_to_color_map={"\\theta": RED},
|
||||
arg_separator="",
|
||||
)
|
||||
|
||||
implies = TexMobject("\\Downarrow")
|
||||
period = TexMobject(
|
||||
"\\text{Period}", "\\approx",
|
||||
"2\\pi \\sqrt{\\,{L} / {g}}",
|
||||
**Lg_formula_config,
|
||||
)
|
||||
group = VGroup(approx, implies, period)
|
||||
group.arrange(DOWN)
|
||||
|
||||
approx_brace = Brace(approx, UP, buff=SMALL_BUFF)
|
||||
approx_words = TextMobject(
|
||||
"For small $\\theta$",
|
||||
tex_to_color_map={"$\\theta$": RED},
|
||||
)
|
||||
approx_words.scale(0.75)
|
||||
approx_words.next_to(approx_brace, UP, SMALL_BUFF)
|
||||
|
||||
self.add(approx, approx_brace, approx_words)
|
||||
self.play(
|
||||
Write(implies),
|
||||
FadeInFrom(period, LEFT)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class StrogatzQuote(Scene):
|
||||
def construct(self):
|
||||
quote = self.get_quote()
|
||||
movers = VGroup(*quote[:-1].family_members_with_points())
|
||||
for mover in movers:
|
||||
mover.save_state()
|
||||
disc = Circle(radius=0.05)
|
||||
disc.set_stroke(width=0)
|
||||
disc.set_fill(BLACK, 0)
|
||||
disc.move_to(mover)
|
||||
mover.become(disc)
|
||||
self.play(
|
||||
FadeInFrom(quote.author_part, LEFT),
|
||||
LaggedStartMap(
|
||||
# FadeInFromLarge,
|
||||
# quote[:-1].family_members_with_points(),
|
||||
Restore, movers,
|
||||
lag_ratio=0.005,
|
||||
run_time=2,
|
||||
)
|
||||
# FadeInFromDown(quote[:-1]),
|
||||
# lag_ratio=0.01,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Write(quote.law_part.copy().set_color(YELLOW)),
|
||||
run_time=1,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Write(quote.language_part.copy().set_color(BLUE)),
|
||||
run_time=1.5,
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
def get_quote(self):
|
||||
law_words = "laws of physics"
|
||||
language_words = "language of differential equations"
|
||||
author = "-Steven Strogatz"
|
||||
quote = TextMobject(
|
||||
"""
|
||||
\\Large
|
||||
``Since Newton, mankind has come to realize
|
||||
that the laws of physics are always expressed
|
||||
in the language of differential equations.''\\\\
|
||||
""" + author,
|
||||
alignment="",
|
||||
arg_separator=" ",
|
||||
substrings_to_isolate=[law_words, language_words, author]
|
||||
)
|
||||
quote.law_part = quote.get_part_by_tex(law_words)
|
||||
quote.language_part = quote.get_part_by_tex(language_words)
|
||||
quote.author_part = quote.get_part_by_tex(author)
|
||||
quote.set_width(12)
|
||||
quote.to_edge(UP)
|
||||
quote[-2].shift(SMALL_BUFF * LEFT)
|
||||
quote.author_part.shift(RIGHT + 0.5 * DOWN)
|
||||
quote.author_part.scale(1.2, about_edge=UL)
|
||||
|
||||
return quote
|
||||
|
||||
|
||||
class WriteInRadians(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("In radians")
|
||||
words.set_color(YELLOW)
|
||||
square = SurroundingRectangle(TexMobject("\\theta"))
|
||||
square.next_to(words, UP)
|
||||
self.play(ShowCreation(square))
|
||||
self.play(Write(words), FadeOut(square))
|
||||
self.wait()
|
||||
|
||||
|
||||
class XEqLThetaToCorner(Scene):
|
||||
def construct(self):
|
||||
equation = TexMobject(
|
||||
"x = L\\theta",
|
||||
tex_to_color_map={
|
||||
"x": GREEN,
|
||||
"\\theta": BLUE,
|
||||
}
|
||||
)
|
||||
equation.move_to(DOWN + 3 * RIGHT)
|
||||
self.add(equation)
|
||||
self.play(equation.to_corner, DL, {"buff": LARGE_BUFF})
|
||||
self.wait()
|
||||
|
||||
|
||||
class ComingUp(Scene):
|
||||
CONFIG = {
|
||||
"camera_config": {"background_color": DARKER_GREY}
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
frame = ScreenRectangle(
|
||||
stroke_width=0,
|
||||
fill_color=BLACK,
|
||||
fill_opacity=1,
|
||||
height=6
|
||||
)
|
||||
title = TextMobject("Coming up")
|
||||
title.scale(1.5)
|
||||
title.to_edge(UP)
|
||||
frame.next_to(title, DOWN)
|
||||
animated_frame = AnimatedBoundary(frame)
|
||||
self.add(frame, title, animated_frame)
|
||||
self.wait(10)
|
||||
|
||||
|
||||
class InputLabel(Scene):
|
||||
def construct(self):
|
||||
label = TextMobject("Input")
|
||||
label.scale(1.25)
|
||||
arrow = Vector(UP)
|
||||
arrow.next_to(label, UP)
|
||||
self.play(
|
||||
FadeInFrom(label, UP),
|
||||
GrowArrow(arrow)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ReallyHardToSolve(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject(
|
||||
"They're", "really\\\\",
|
||||
"freaking", "hard\\\\",
|
||||
"to", "solve\\\\",
|
||||
)
|
||||
words.set_height(6)
|
||||
|
||||
self.wait()
|
||||
for word in words:
|
||||
wait_time = 0.05 * len(word)
|
||||
self.add(word)
|
||||
self.wait(wait_time)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ReasonForSolution(Scene):
|
||||
def construct(self):
|
||||
# Words
|
||||
eq_word = TextMobject("Differential\\\\Equation")
|
||||
s_word = TextMobject("Solution")
|
||||
u_word = TextMobject("Understanding")
|
||||
c_word = TextMobject("Computation")
|
||||
cu_group = VGroup(u_word, c_word)
|
||||
cu_group.arrange(DOWN, buff=2)
|
||||
group = VGroup(eq_word, s_word, cu_group)
|
||||
group.arrange(RIGHT, buff=2)
|
||||
# words = VGroup(eq_word, s_word, u_word, c_word)
|
||||
|
||||
# Arrows
|
||||
arrows = VGroup(
|
||||
Arrow(eq_word.get_right(), s_word.get_left()),
|
||||
Arrow(s_word.get_right(), u_word.get_left()),
|
||||
Arrow(s_word.get_right(), c_word.get_left()),
|
||||
)
|
||||
arrows.set_color(LIGHT_GREY)
|
||||
new_arrows = VGroup(
|
||||
Arrow(
|
||||
eq_word.get_corner(UR),
|
||||
u_word.get_left(),
|
||||
path_arc=-60 * DEGREES,
|
||||
),
|
||||
Arrow(
|
||||
eq_word.get_corner(DR),
|
||||
c_word.get_left(),
|
||||
path_arc=60 * DEGREES,
|
||||
),
|
||||
)
|
||||
new_arrows.set_color(BLUE)
|
||||
|
||||
# Define first examples
|
||||
t2c = {
|
||||
"{x}": BLUE,
|
||||
"{\\dot x}": RED,
|
||||
}
|
||||
equation = TexMobject(
|
||||
"{\\dot x}(t) = k {x}(t)",
|
||||
tex_to_color_map=t2c,
|
||||
)
|
||||
equation.next_to(eq_word, DOWN)
|
||||
solution = TexMobject(
|
||||
"{x}(t) = x_0 e^{kt}",
|
||||
tex_to_color_map=t2c,
|
||||
)
|
||||
solution.next_to(s_word, DOWN, MED_LARGE_BUFF)
|
||||
equation.align_to(solution, DOWN)
|
||||
|
||||
axes = Axes(
|
||||
x_min=-1,
|
||||
x_max=5.5,
|
||||
y_min=-1,
|
||||
y_max=4.5,
|
||||
y_axis_config={"unit_size": 0.5}
|
||||
)
|
||||
axes.set_stroke(width=2)
|
||||
graph_line = axes.get_graph(
|
||||
lambda x: np.exp(0.4 * x)
|
||||
)
|
||||
graph_line.set_stroke(width=2)
|
||||
graph = VGroup(axes, graph_line)
|
||||
graph.scale(0.5)
|
||||
graph.next_to(u_word, UP)
|
||||
|
||||
computation = TexMobject(
|
||||
# "\\displaystyle "
|
||||
"e^x = \\sum_{n=0}^\\infty "
|
||||
"\\frac{x^n}{n!}"
|
||||
)
|
||||
computation.next_to(c_word, DOWN)
|
||||
|
||||
first_examples = VGroup(
|
||||
equation, solution, graph, computation
|
||||
)
|
||||
|
||||
# Second example
|
||||
ode = get_ode()
|
||||
ode.scale(0.75)
|
||||
second_examples = VGroup(
|
||||
ode,
|
||||
TexMobject("???").set_color(LIGHT_GREY),
|
||||
ScreenRectangle(
|
||||
height=2,
|
||||
stroke_width=1,
|
||||
),
|
||||
)
|
||||
for fe, se in zip(first_examples, second_examples):
|
||||
se.move_to(fe, DOWN)
|
||||
|
||||
ode.shift(2 * SMALL_BUFF * DOWN)
|
||||
ode.add_to_back(BackgroundRectangle(ode[-4:]))
|
||||
|
||||
self.add(eq_word)
|
||||
self.add(equation)
|
||||
self.play(
|
||||
FadeInFrom(s_word, LEFT),
|
||||
GrowArrow(arrows[0]),
|
||||
TransformFromCopy(equation, solution)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeInFrom(c_word, UL),
|
||||
GrowArrow(arrows[2]),
|
||||
FadeInFrom(computation, UP)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeInFrom(u_word, DL),
|
||||
GrowArrow(arrows[1]),
|
||||
FadeInFromDown(graph)
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
self.play(
|
||||
FadeOut(first_examples),
|
||||
FadeIn(second_examples[:2])
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
arrows.fade, 0.75,
|
||||
s_word.fade, 0.75,
|
||||
second_examples[1].fade, 0.75,
|
||||
ShowCreation(new_arrows[0]),
|
||||
FadeIn(second_examples[2])
|
||||
)
|
||||
self.play(
|
||||
ShowCreation(new_arrows[1]),
|
||||
Animation(second_examples),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class WritePhaseSpace(Scene):
|
||||
def construct(self):
|
||||
word = TextMobject("Phase space")
|
||||
word.scale(2)
|
||||
word.shift(FRAME_WIDTH * LEFT / 4)
|
||||
word.to_edge(UP)
|
||||
word.add_background_rectangle()
|
||||
|
||||
lines = VGroup(*[
|
||||
Line(v, 1.3 * v)
|
||||
for v in compass_directions(50)
|
||||
])
|
||||
lines.replace(word, stretch=True)
|
||||
lines.scale(1.5)
|
||||
lines.set_stroke(YELLOW)
|
||||
lines.shuffle()
|
||||
|
||||
self.add(word)
|
||||
self.play(
|
||||
ShowPassingFlashWithThinningStrokeWidth(
|
||||
lines,
|
||||
lag_ratio=0.002,
|
||||
run_time=1.5,
|
||||
time_width=0.9,
|
||||
n_segments=5,
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class GleickQuote(Scene):
|
||||
def construct(self):
|
||||
quote = TextMobject(
|
||||
"``[Phase space is] one of the most\\\\",
|
||||
"powerful inventions", "of modern science.''\\\\",
|
||||
)
|
||||
quote.power_part = quote.get_part_by_tex("power")
|
||||
book = ImageMobject("ChaosBookCover")
|
||||
book.set_height(5)
|
||||
book.next_to(ORIGIN, LEFT)
|
||||
book.to_edge(DOWN)
|
||||
gleick = ImageMobject("JamesGleick")
|
||||
gleick.set_height(5)
|
||||
gleick.next_to(ORIGIN, RIGHT)
|
||||
gleick.to_edge(DOWN)
|
||||
quote.to_edge(UP)
|
||||
|
||||
self.play(
|
||||
FadeInFrom(book, RIGHT),
|
||||
FadeInFrom(gleick, LEFT),
|
||||
)
|
||||
self.wait()
|
||||
self.play(Write(quote))
|
||||
self.play(Write(
|
||||
quote.power_part.copy().set_color(BLUE),
|
||||
run_time=1
|
||||
))
|
||||
self.wait()
|
||||
|
||||
|
||||
class WritePhaseFlow(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("Phase flow")
|
||||
words.scale(2)
|
||||
words.shift(FRAME_WIDTH * LEFT / 4)
|
||||
words.to_edge(UP)
|
||||
words.add_background_rectangle()
|
||||
self.play(Write(words))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ShowSineValues(Scene):
|
||||
def construct(self):
|
||||
angle_tracker = ValueTracker(60 * DEGREES)
|
||||
get_angle = angle_tracker.get_value
|
||||
formula = always_redraw(
|
||||
lambda: self.get_sine_formula(get_angle())
|
||||
)
|
||||
self.add(formula)
|
||||
|
||||
self.play(
|
||||
angle_tracker.set_value, 0,
|
||||
run_time=3,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
angle_tracker.set_value, 90 * DEGREES,
|
||||
run_time=3,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
def get_sine_formula(self, angle):
|
||||
sin, lp, rp = TexMobject(
|
||||
"\\sin", "(", ") = "
|
||||
)
|
||||
input_part = Integer(
|
||||
angle / DEGREES,
|
||||
unit="^\\circ",
|
||||
)
|
||||
input_part.set_color(YELLOW)
|
||||
output_part = DecimalNumber(
|
||||
np.sin(input_part.get_value() * DEGREES),
|
||||
num_decimal_places=3,
|
||||
)
|
||||
result = VGroup(
|
||||
sin, lp, input_part, rp, output_part
|
||||
)
|
||||
result.arrange(RIGHT, buff=SMALL_BUFF)
|
||||
sin.scale(1.1, about_edge=DOWN)
|
||||
lp.align_to(rp, UP)
|
||||
return result
|
||||
|
||||
|
||||
class SetAsideSeekingSolution(Scene):
|
||||
def construct(self):
|
||||
ode = get_ode()
|
||||
ode.to_edge(UP)
|
||||
q1 = TextMobject("Find an exact solution")
|
||||
q1.set_color(YELLOW)
|
||||
q2 = TexMobject(
|
||||
"\\text{What is }", "\\theta", "(t)",
|
||||
"\\text{'s personality?}",
|
||||
tex_to_color_map={"\\theta": BLUE},
|
||||
arg_separator="",
|
||||
)
|
||||
theta = q2.get_part_by_tex("\\theta")
|
||||
|
||||
for q in q1, q2:
|
||||
q.scale(1.5)
|
||||
q.next_to(ode, DOWN, MED_LARGE_BUFF)
|
||||
eyes = Eyes(theta, height=0.1)
|
||||
|
||||
self.add(ode)
|
||||
self.add(q1)
|
||||
self.wait()
|
||||
self.play(
|
||||
q1.scale, 0.3,
|
||||
q1.to_corner, UR, MED_SMALL_BUFF,
|
||||
)
|
||||
self.play(FadeInFrom(q2, DOWN))
|
||||
self.play(
|
||||
eyes.blink,
|
||||
rate_func=lambda t: smooth(1 - t),
|
||||
)
|
||||
self.play(eyes.look_at, q2.get_left())
|
||||
self.play(eyes.look_at, q2.get_right())
|
||||
self.play(
|
||||
eyes.blink,
|
||||
rate_func=squish_rate_func(there_and_back)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
eyes.change_mode, "confused",
|
||||
eyes.look_at, ode.get_left(),
|
||||
)
|
||||
self.play(
|
||||
eyes.blink,
|
||||
rate_func=squish_rate_func(there_and_back)
|
||||
)
|
||||
|
||||
|
||||
class ThreeBodyTitle(Scene):
|
||||
def construct(self):
|
||||
title = TextMobject("Three body problem")
|
||||
title.scale(1.5)
|
||||
title.to_edge(UP)
|
||||
self.add(title)
|
||||
|
||||
|
||||
class ThreeBodySymbols(Scene):
|
||||
def construct(self):
|
||||
self.init_coord_groups()
|
||||
self.introduce_coord_groups()
|
||||
self.count_coords()
|
||||
|
||||
def init_coord_groups(self):
|
||||
kwargs = {
|
||||
"bracket_v_buff": 2 * SMALL_BUFF
|
||||
}
|
||||
positions = VGroup(*[
|
||||
get_vector_symbol(*[
|
||||
"{}_{}".format(s, i)
|
||||
for s in "xyz"
|
||||
], **kwargs)
|
||||
for i in range(1, 4)
|
||||
])
|
||||
velocities = VGroup(*[
|
||||
get_vector_symbol(*[
|
||||
"p^{}_{}".format(s, i)
|
||||
for s in "xyz"
|
||||
], **kwargs)
|
||||
for i in range(1, 4)
|
||||
])
|
||||
groups = VGroup(positions, velocities)
|
||||
colors = [GREEN, RED, BLUE]
|
||||
for group in groups:
|
||||
for matrix in group:
|
||||
matrix.coords = matrix.get_entries()
|
||||
for coord, color in zip(matrix.coords, colors):
|
||||
coord.set_color(color)
|
||||
group.arrange(RIGHT)
|
||||
groups.arrange(DOWN, buff=LARGE_BUFF)
|
||||
groups.to_edge(LEFT)
|
||||
|
||||
self.coord_groups = groups
|
||||
|
||||
def introduce_coord_groups(self):
|
||||
groups = self.coord_groups
|
||||
x_group, p_group = groups
|
||||
x_word = TextMobject("Positions")
|
||||
p_word = TextMobject("Momenta")
|
||||
words = VGroup(x_word, p_word)
|
||||
for word, group in zip(words, groups):
|
||||
word.next_to(group, UP)
|
||||
|
||||
rect_groups = VGroup()
|
||||
for group in groups:
|
||||
rect_group = VGroup(*[
|
||||
SurroundingRectangle(
|
||||
VGroup(*[
|
||||
tm.coords[i]
|
||||
for tm in group
|
||||
]),
|
||||
color=WHITE,
|
||||
stroke_width=2,
|
||||
)
|
||||
for i in range(3)
|
||||
])
|
||||
rect_groups.add(rect_group)
|
||||
|
||||
self.play(
|
||||
*[
|
||||
LaggedStartMap(
|
||||
FadeInFrom, group,
|
||||
lambda m: (m, UP),
|
||||
run_time=1,
|
||||
)
|
||||
for group in groups
|
||||
],
|
||||
*map(FadeInFromDown, words),
|
||||
)
|
||||
for rect_group in rect_groups:
|
||||
self.play(
|
||||
ShowCreationThenFadeOut(
|
||||
rect_group,
|
||||
lag_ratio=0.5,
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
def count_coords(self):
|
||||
coord_copies = VGroup()
|
||||
for group in self.coord_groups:
|
||||
for tex_mob in group:
|
||||
for coord in tex_mob.coords:
|
||||
coord_copy = coord.copy()
|
||||
coord_copy.set_stroke(
|
||||
WHITE, 2, background=True
|
||||
)
|
||||
coord_copies.add(coord_copy)
|
||||
|
||||
count = Integer()
|
||||
count_word = TextMobject("18", "degrees \\\\ of freedom")[1]
|
||||
count_group = VGroup(count, count_word)
|
||||
count_group.arrange(
|
||||
RIGHT,
|
||||
aligned_edge=DOWN,
|
||||
)
|
||||
count_group.scale(1.5)
|
||||
count_group.next_to(
|
||||
self.coord_groups, RIGHT,
|
||||
aligned_edge=DOWN,
|
||||
)
|
||||
count.add_updater(
|
||||
lambda m: m.set_value(len(coord_copies))
|
||||
)
|
||||
count.add_updater(
|
||||
lambda m: m.next_to(count_word[0][0], LEFT, aligned_edge=DOWN)
|
||||
)
|
||||
|
||||
self.add(count_group)
|
||||
self.play(
|
||||
# ChangeDecimalToValue(count, len(coord_copies)),
|
||||
ShowIncreasingSubsets(coord_copies),
|
||||
run_time=1.5,
|
||||
rate_func=linear,
|
||||
)
|
||||
self.play(FadeOut(coord_copies))
|
||||
|
||||
|
||||
class ThreeBodyEquation(Scene):
|
||||
def construct(self):
|
||||
x1 = "\\vec{\\textbf{x}}_1"
|
||||
x2 = "\\vec{\\textbf{x}}_2"
|
||||
x3 = "\\vec{\\textbf{x}}_3"
|
||||
kw = {
|
||||
"tex_to_color_map": {
|
||||
x1: RED,
|
||||
x2: GREEN,
|
||||
x3: BLUE,
|
||||
}
|
||||
}
|
||||
equations = VGroup(*[
|
||||
TexMobject(
|
||||
"{d^2", t1, "\\over dt^2}", "=",
|
||||
"G", "\\left("
|
||||
"{" + m2, "(", t2, "-", t1, ")"
|
||||
"\\over"
|
||||
"||", t2, "-", t1, "||^3}",
|
||||
"+",
|
||||
"{" + m3, "(", t3, "-", t1, ")"
|
||||
"\\over"
|
||||
"||", t3, "-", t1, "||^3}",
|
||||
"\\right)",
|
||||
**kw
|
||||
)
|
||||
for t1, t2, t3, m1, m2, m3 in [
|
||||
(x1, x2, x3, "m_1", "m_2", "m_3"),
|
||||
(x2, x3, x1, "m_2", "m_3", "m_1"),
|
||||
(x3, x1, x2, "m_3", "m_1", "m_2"),
|
||||
]
|
||||
])
|
||||
equations.arrange(DOWN, buff=LARGE_BUFF)
|
||||
|
||||
self.play(LaggedStartMap(
|
||||
FadeInFrom, equations,
|
||||
lambda m: (m, UP),
|
||||
lag_ratio=0.2,
|
||||
))
|
||||
self.wait()
|
||||
|
||||
|
||||
class JumpToThisPoint(Scene):
|
||||
def construct(self):
|
||||
dot = Dot(color=YELLOW)
|
||||
dot.scale(0.5)
|
||||
|
||||
arrow = Vector(DR, color=WHITE)
|
||||
arrow.next_to(dot, UL, SMALL_BUFF)
|
||||
words = TextMobject(
|
||||
"Jump directly to\\\\",
|
||||
"this point?",
|
||||
)
|
||||
words.add_background_rectangle_to_submobjects()
|
||||
words.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
||||
|
||||
self.play(
|
||||
FadeInFromLarge(dot, 20),
|
||||
rate_func=rush_into,
|
||||
)
|
||||
self.play(
|
||||
GrowArrow(arrow),
|
||||
FadeInFromDown(words),
|
||||
)
|
||||
|
||||
|
||||
class ChaosTitle(Scene):
|
||||
def construct(self):
|
||||
title = TextMobject("Chaos theory")
|
||||
title.scale(1.5)
|
||||
title.to_edge(UP)
|
||||
line = Line(LEFT, RIGHT)
|
||||
line.set_width(FRAME_WIDTH - 1)
|
||||
line.next_to(title, DOWN)
|
||||
|
||||
self.play(
|
||||
Write(title),
|
||||
ShowCreation(line),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class RevisitQuote(StrogatzQuote, PiCreatureScene):
|
||||
def construct(self):
|
||||
quote = self.get_quote()
|
||||
quote.law_part.set_color(YELLOW)
|
||||
quote.language_part.set_color(BLUE)
|
||||
quote.set_stroke(BLACK, 6, background=True)
|
||||
quote.scale(0.8, about_edge=UL)
|
||||
|
||||
new_langauge_part = TextMobject(
|
||||
"\\Large Language of differential equations"
|
||||
)
|
||||
new_langauge_part.to_edge(UP)
|
||||
new_langauge_part.match_style(quote.language_part)
|
||||
|
||||
randy = self.pi_creature
|
||||
|
||||
self.play(
|
||||
FadeInFrom(quote[:-1], DOWN),
|
||||
FadeInFrom(quote[-1:], LEFT),
|
||||
randy.change, "raise_right_hand",
|
||||
)
|
||||
self.play(Blink(randy))
|
||||
self.play(randy.change, "angry")
|
||||
self.play(
|
||||
Blink(randy),
|
||||
VFadeOut(randy, run_time=3)
|
||||
)
|
||||
mover = VGroup(quote.language_part)
|
||||
self.add(quote, mover)
|
||||
self.play(
|
||||
ReplacementTransform(
|
||||
mover, new_langauge_part,
|
||||
),
|
||||
*[
|
||||
FadeOut(part)
|
||||
for part in quote
|
||||
if part is not quote.language_part
|
||||
],
|
||||
run_time=2,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class EndScreen(PatreonEndScreen):
|
||||
CONFIG = {
|
||||
"specific_patrons": [
|
||||
"Juan Benet",
|
||||
"Vassili Philippov",
|
||||
"Burt Humburg",
|
||||
"Carlos Vergara Del Rio",
|
||||
"Matt Russell",
|
||||
"Scott Gray",
|
||||
"soekul",
|
||||
"Tihan Seale",
|
||||
"Ali Yahya",
|
||||
"dave nicponski",
|
||||
"Evan Phillips",
|
||||
"Graham",
|
||||
"Joseph Kelly",
|
||||
"Kaustuv DeBiswas",
|
||||
"LambdaLabs",
|
||||
"Lukas Biewald",
|
||||
"Mike Coleman",
|
||||
"Peter Mcinerney",
|
||||
"Quantopian",
|
||||
"Roy Larson",
|
||||
"Scott Walter, Ph.D.",
|
||||
"Yana Chernobilsky",
|
||||
"Yu Jun",
|
||||
"Jordan Scales",
|
||||
"Lukas -krtek.net- Novy",
|
||||
"John Shaughnessy",
|
||||
"Britt Selvitelle",
|
||||
"David Gow",
|
||||
"J",
|
||||
"Jonathan Wilson",
|
||||
"Joseph John Cox",
|
||||
"Magnus Dahlström",
|
||||
"Randy C. Will",
|
||||
"Ryan Atallah",
|
||||
"Luc Ritchie",
|
||||
"1stViewMaths",
|
||||
"Adrian Robinson",
|
||||
"Alexis Olson",
|
||||
"Andreas Benjamin Brössel",
|
||||
"Andrew Busey",
|
||||
"Ankalagon",
|
||||
"Antonio Juarez",
|
||||
"Arjun Chakroborty",
|
||||
"Art Ianuzzi",
|
||||
"Awoo",
|
||||
"Bernd Sing",
|
||||
"Boris Veselinovich",
|
||||
"Brian Staroselsky",
|
||||
"Chad Hurst",
|
||||
"Charles Southerland",
|
||||
"Chris Connett",
|
||||
"Christian Kaiser",
|
||||
"Clark Gaebel",
|
||||
"Cooper Jones",
|
||||
"Danger Dai",
|
||||
"Dave B",
|
||||
"Dave Kester",
|
||||
"David Clark",
|
||||
"DeathByShrimp",
|
||||
"Delton Ding",
|
||||
"eaglle",
|
||||
"emptymachine",
|
||||
"Eric Younge",
|
||||
"Eryq Ouithaqueue",
|
||||
"Federico Lebron",
|
||||
"Giovanni Filippi",
|
||||
"Hal Hildebrand",
|
||||
"Herman Dieset",
|
||||
"Hitoshi Yamauchi",
|
||||
"Isaac Jeffrey Lee",
|
||||
"j eduardo perez",
|
||||
"Jacob Magnuson",
|
||||
"Jameel Syed",
|
||||
"Jason Hise",
|
||||
"Jeff Linse",
|
||||
"Jeff Straathof",
|
||||
"John Griffith",
|
||||
"John Haley",
|
||||
"John V Wertheim",
|
||||
"Jonathan Eppele",
|
||||
"Kai-Siang Ang",
|
||||
"Kanan Gill",
|
||||
"L0j1k",
|
||||
"Lee Redden",
|
||||
"Linh Tran",
|
||||
"Ludwig Schubert",
|
||||
"Magister Mugit",
|
||||
"Mark B Bahu",
|
||||
"Mark Heising",
|
||||
"Martin Price",
|
||||
"Mathias Jansson",
|
||||
"Matt Langford",
|
||||
"Matt Roveto",
|
||||
"Matthew Cocke",
|
||||
"Michael Faust",
|
||||
"Michael Hardel",
|
||||
"Mirik Gogri",
|
||||
"Mustafa Mahdi",
|
||||
"Márton Vaitkus",
|
||||
"Nero Li",
|
||||
"Nikita Lesnikov",
|
||||
"Omar Zrien",
|
||||
"Owen Campbell-Moore",
|
||||
"Peter Ehrnstrom",
|
||||
"RedAgent14",
|
||||
"rehmi post",
|
||||
"Richard Burgmann",
|
||||
"Richard Comish",
|
||||
"Ripta Pasay",
|
||||
"Rish Kundalia",
|
||||
"Robert Teed",
|
||||
"Roobie",
|
||||
"Ryan Williams",
|
||||
"Samuel D. Judge",
|
||||
"Solara570",
|
||||
"Stevie Metke",
|
||||
"Tal Einav",
|
||||
"Ted Suzman",
|
||||
"Valeriy Skobelev",
|
||||
"Xavier Bernard",
|
||||
"Yavor Ivanov",
|
||||
"Yaw Etse",
|
||||
"YinYangBalance.Asia",
|
||||
"Zach Cardwell",
|
||||
]
|
||||
}
|
|
@ -1,931 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
# import scipy
|
||||
|
||||
|
||||
class FourierCirclesScene(Scene):
|
||||
CONFIG = {
|
||||
"n_vectors": 10,
|
||||
"big_radius": 2,
|
||||
"colors": [
|
||||
BLUE_D,
|
||||
BLUE_C,
|
||||
BLUE_E,
|
||||
GREY_BROWN,
|
||||
],
|
||||
"circle_style": {
|
||||
"stroke_width": 2,
|
||||
},
|
||||
"vector_config": {
|
||||
"buff": 0,
|
||||
"max_tip_length_to_length_ratio": 0.35,
|
||||
"tip_length": 0.15,
|
||||
"max_stroke_width_to_length_ratio": 10,
|
||||
"stroke_width": 2,
|
||||
},
|
||||
"circle_config": {
|
||||
"stroke_width": 1,
|
||||
},
|
||||
"base_frequency": 1,
|
||||
"slow_factor": 0.25,
|
||||
"center_point": ORIGIN,
|
||||
"parametric_function_step_size": 0.001,
|
||||
"drawn_path_color": YELLOW,
|
||||
"drawn_path_stroke_width": 2,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.slow_factor_tracker = ValueTracker(
|
||||
self.slow_factor
|
||||
)
|
||||
self.vector_clock = ValueTracker(0)
|
||||
self.vector_clock.add_updater(
|
||||
lambda m, dt: m.increment_value(
|
||||
self.get_slow_factor() * dt
|
||||
)
|
||||
)
|
||||
self.add(self.vector_clock)
|
||||
|
||||
def get_slow_factor(self):
|
||||
return self.slow_factor_tracker.get_value()
|
||||
|
||||
def get_vector_time(self):
|
||||
return self.vector_clock.get_value()
|
||||
|
||||
#
|
||||
def get_freqs(self):
|
||||
n = self.n_vectors
|
||||
all_freqs = list(range(n // 2, -n // 2, -1))
|
||||
all_freqs.sort(key=abs)
|
||||
return all_freqs
|
||||
|
||||
def get_coefficients(self):
|
||||
return [complex(0) for x in range(self.n_vectors)]
|
||||
|
||||
def get_color_iterator(self):
|
||||
return it.cycle(self.colors)
|
||||
|
||||
def get_rotating_vectors(self, freqs=None, coefficients=None):
|
||||
vectors = VGroup()
|
||||
self.center_tracker = VectorizedPoint(self.center_point)
|
||||
|
||||
if freqs is None:
|
||||
freqs = self.get_freqs()
|
||||
if coefficients is None:
|
||||
coefficients = self.get_coefficients()
|
||||
|
||||
last_vector = None
|
||||
for freq, coefficient in zip(freqs, coefficients):
|
||||
if last_vector:
|
||||
center_func = last_vector.get_end
|
||||
else:
|
||||
center_func = self.center_tracker.get_location
|
||||
vector = self.get_rotating_vector(
|
||||
coefficient=coefficient,
|
||||
freq=freq,
|
||||
center_func=center_func,
|
||||
)
|
||||
vectors.add(vector)
|
||||
last_vector = vector
|
||||
return vectors
|
||||
|
||||
def get_rotating_vector(self, coefficient, freq, center_func):
|
||||
vector = Vector(RIGHT, **self.vector_config)
|
||||
vector.scale(abs(coefficient))
|
||||
if abs(coefficient) == 0:
|
||||
phase = 0
|
||||
else:
|
||||
phase = np.log(coefficient).imag
|
||||
vector.rotate(phase, about_point=ORIGIN)
|
||||
vector.freq = freq
|
||||
vector.coefficient = coefficient
|
||||
vector.center_func = center_func
|
||||
vector.add_updater(self.update_vector)
|
||||
return vector
|
||||
|
||||
def update_vector(self, vector, dt):
|
||||
time = self.get_vector_time()
|
||||
coef = vector.coefficient
|
||||
freq = vector.freq
|
||||
phase = np.log(coef).imag
|
||||
|
||||
vector.set_length(abs(coef))
|
||||
vector.set_angle(phase + time * freq * TAU)
|
||||
vector.shift(vector.center_func() - vector.get_start())
|
||||
return vector
|
||||
|
||||
def get_circles(self, vectors):
|
||||
return VGroup(*[
|
||||
self.get_circle(
|
||||
vector,
|
||||
color=color
|
||||
)
|
||||
for vector, color in zip(
|
||||
vectors,
|
||||
self.get_color_iterator()
|
||||
)
|
||||
])
|
||||
|
||||
def get_circle(self, vector, color=BLUE):
|
||||
circle = Circle(color=color, **self.circle_config)
|
||||
circle.center_func = vector.get_start
|
||||
circle.radius_func = vector.get_length
|
||||
circle.add_updater(self.update_circle)
|
||||
return circle
|
||||
|
||||
def update_circle(self, circle):
|
||||
circle.set_width(2 * circle.radius_func())
|
||||
circle.move_to(circle.center_func())
|
||||
return circle
|
||||
|
||||
def get_vector_sum_path(self, vectors, color=YELLOW):
|
||||
coefs = [v.coefficient for v in vectors]
|
||||
freqs = [v.freq for v in vectors]
|
||||
center = vectors[0].get_start()
|
||||
|
||||
path = ParametricFunction(
|
||||
lambda t: center + reduce(op.add, [
|
||||
complex_to_R3(
|
||||
coef * np.exp(TAU * 1j * freq * t)
|
||||
)
|
||||
for coef, freq in zip(coefs, freqs)
|
||||
]),
|
||||
t_min=0,
|
||||
t_max=1,
|
||||
color=color,
|
||||
step_size=self.parametric_function_step_size,
|
||||
)
|
||||
return path
|
||||
|
||||
# TODO, this should be a general animated mobect
|
||||
def get_drawn_path_alpha(self):
|
||||
return self.get_vector_time()
|
||||
|
||||
def get_drawn_path(self, vectors, stroke_width=None, **kwargs):
|
||||
if stroke_width is None:
|
||||
stroke_width = self.drawn_path_stroke_width
|
||||
path = self.get_vector_sum_path(vectors, **kwargs)
|
||||
broken_path = CurvesAsSubmobjects(path)
|
||||
broken_path.curr_time = 0
|
||||
|
||||
def update_path(path, dt):
|
||||
# alpha = path.curr_time * self.get_slow_factor()
|
||||
alpha = self.get_drawn_path_alpha()
|
||||
n_curves = len(path)
|
||||
for a, sp in zip(np.linspace(0, 1, n_curves), path):
|
||||
b = alpha - a
|
||||
if b < 0:
|
||||
width = 0
|
||||
else:
|
||||
width = stroke_width * (1 - (b % 1))
|
||||
sp.set_stroke(width=width)
|
||||
path.curr_time += dt
|
||||
return path
|
||||
|
||||
broken_path.set_color(self.drawn_path_color)
|
||||
broken_path.add_updater(update_path)
|
||||
return broken_path
|
||||
|
||||
def get_y_component_wave(self,
|
||||
vectors,
|
||||
left_x=1,
|
||||
color=PINK,
|
||||
n_copies=2,
|
||||
right_shift_rate=5):
|
||||
path = self.get_vector_sum_path(vectors)
|
||||
wave = ParametricFunction(
|
||||
lambda t: op.add(
|
||||
right_shift_rate * t * LEFT,
|
||||
path.function(t)[1] * UP
|
||||
),
|
||||
t_min=path.t_min,
|
||||
t_max=path.t_max,
|
||||
color=color,
|
||||
)
|
||||
wave_copies = VGroup(*[
|
||||
wave.copy()
|
||||
for x in range(n_copies)
|
||||
])
|
||||
wave_copies.arrange(RIGHT, buff=0)
|
||||
top_point = wave_copies.get_top()
|
||||
wave.creation = ShowCreation(
|
||||
wave,
|
||||
run_time=(1 / self.get_slow_factor()),
|
||||
rate_func=linear,
|
||||
)
|
||||
cycle_animation(wave.creation)
|
||||
wave.add_updater(lambda m: m.shift(
|
||||
(m.get_left()[0] - left_x) * LEFT
|
||||
))
|
||||
|
||||
def update_wave_copies(wcs):
|
||||
index = int(
|
||||
wave.creation.total_time * self.get_slow_factor()
|
||||
)
|
||||
wcs[:index].match_style(wave)
|
||||
wcs[index:].set_stroke(width=0)
|
||||
wcs.next_to(wave, RIGHT, buff=0)
|
||||
wcs.align_to(top_point, UP)
|
||||
wave_copies.add_updater(update_wave_copies)
|
||||
|
||||
return VGroup(wave, wave_copies)
|
||||
|
||||
def get_wave_y_line(self, vectors, wave):
|
||||
return DashedLine(
|
||||
vectors[-1].get_end(),
|
||||
wave[0].get_end(),
|
||||
stroke_width=1,
|
||||
dash_length=DEFAULT_DASH_LENGTH * 0.5,
|
||||
)
|
||||
|
||||
# Computing Fourier series
|
||||
# i.e. where all the math happens
|
||||
def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
|
||||
if freqs is None:
|
||||
freqs = self.get_freqs()
|
||||
dt = 1 / n_samples
|
||||
ts = np.arange(0, 1, dt)
|
||||
samples = np.array([
|
||||
path.point_from_proportion(t)
|
||||
for t in ts
|
||||
])
|
||||
samples -= self.center_point
|
||||
complex_samples = samples[:, 0] + 1j * samples[:, 1]
|
||||
|
||||
result = []
|
||||
for freq in freqs:
|
||||
riemann_sum = np.array([
|
||||
np.exp(-TAU * 1j * freq * t) * cs
|
||||
for t, cs in zip(ts, complex_samples)
|
||||
]).sum() * dt
|
||||
result.append(riemann_sum)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class FourierSeriesIntroBackground4(FourierCirclesScene):
|
||||
CONFIG = {
|
||||
"n_vectors": 4,
|
||||
"center_point": 4 * LEFT,
|
||||
"run_time": 30,
|
||||
"big_radius": 1.5,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
circles = self.get_circles()
|
||||
path = self.get_drawn_path(circles)
|
||||
wave = self.get_y_component_wave(circles)
|
||||
h_line = always_redraw(
|
||||
lambda: self.get_wave_y_line(circles, wave)
|
||||
)
|
||||
|
||||
# Why?
|
||||
circles.update(-1 / self.camera.frame_rate)
|
||||
#
|
||||
self.add(circles, path, wave, h_line)
|
||||
self.wait(self.run_time)
|
||||
|
||||
def get_ks(self):
|
||||
return np.arange(1, 2 * self.n_vectors + 1, 2)
|
||||
|
||||
def get_freqs(self):
|
||||
return self.base_frequency * self.get_ks()
|
||||
|
||||
def get_coefficients(self):
|
||||
return self.big_radius / self.get_ks()
|
||||
|
||||
|
||||
class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4):
|
||||
CONFIG = {
|
||||
"n_vectors": 8,
|
||||
}
|
||||
|
||||
|
||||
class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4):
|
||||
CONFIG = {
|
||||
"n_vectors": 12,
|
||||
}
|
||||
|
||||
|
||||
class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4):
|
||||
CONFIG = {
|
||||
"n_vectors": 20,
|
||||
}
|
||||
|
||||
|
||||
class FourierOfPiSymbol(FourierCirclesScene):
|
||||
CONFIG = {
|
||||
"n_vectors": 51,
|
||||
"center_point": ORIGIN,
|
||||
"slow_factor": 0.1,
|
||||
"n_cycles": 1,
|
||||
"tex": "\\pi",
|
||||
"start_drawn": False,
|
||||
"max_circle_stroke_width": 1,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_vectors_circles_path()
|
||||
for n in range(self.n_cycles):
|
||||
self.run_one_cycle()
|
||||
|
||||
def add_vectors_circles_path(self):
|
||||
path = self.get_path()
|
||||
coefs = self.get_coefficients_of_path(path)
|
||||
vectors = self.get_rotating_vectors(coefficients=coefs)
|
||||
circles = self.get_circles(vectors)
|
||||
self.set_decreasing_stroke_widths(circles)
|
||||
# approx_path = self.get_vector_sum_path(circles)
|
||||
drawn_path = self.get_drawn_path(vectors)
|
||||
if self.start_drawn:
|
||||
self.vector_clock.increment_value(1)
|
||||
|
||||
self.add(path)
|
||||
self.add(vectors)
|
||||
self.add(circles)
|
||||
self.add(drawn_path)
|
||||
|
||||
self.vectors = vectors
|
||||
self.circles = circles
|
||||
self.path = path
|
||||
self.drawn_path = drawn_path
|
||||
|
||||
def run_one_cycle(self):
|
||||
time = 1 / self.slow_factor
|
||||
self.wait(time)
|
||||
|
||||
def set_decreasing_stroke_widths(self, circles):
|
||||
mcsw = self.max_circle_stroke_width
|
||||
for k, circle in zip(it.count(1), circles):
|
||||
circle.set_stroke(width=max(
|
||||
# mcsw / np.sqrt(k),
|
||||
mcsw / k,
|
||||
mcsw,
|
||||
))
|
||||
return circles
|
||||
|
||||
def get_path(self):
|
||||
tex_mob = TexMobject(self.tex)
|
||||
tex_mob.set_height(6)
|
||||
path = tex_mob.family_members_with_points()[0]
|
||||
path.set_fill(opacity=0)
|
||||
path.set_stroke(WHITE, 1)
|
||||
return path
|
||||
|
||||
|
||||
class FourierOfTexPaths(FourierOfPiSymbol, MovingCameraScene):
|
||||
CONFIG = {
|
||||
"n_vectors": 100,
|
||||
"name_color": WHITE,
|
||||
"animated_name": "Abc",
|
||||
"time_per_symbol": 5,
|
||||
"slow_factor": 1 / 5,
|
||||
"parametric_function_step_size": 0.01,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
name = TextMobject(self.animated_name)
|
||||
max_width = FRAME_WIDTH - 2
|
||||
max_height = FRAME_HEIGHT - 2
|
||||
name.set_width(max_width)
|
||||
if name.get_height() > max_height:
|
||||
name.set_height(max_height)
|
||||
|
||||
frame = self.camera.frame
|
||||
frame.save_state()
|
||||
|
||||
vectors = VGroup(VectorizedPoint())
|
||||
circles = VGroup(VectorizedPoint())
|
||||
for path in name.family_members_with_points():
|
||||
for subpath in path.get_subpaths():
|
||||
sp_mob = VMobject()
|
||||
sp_mob.set_points(subpath)
|
||||
coefs = self.get_coefficients_of_path(sp_mob)
|
||||
new_vectors = self.get_rotating_vectors(
|
||||
coefficients=coefs
|
||||
)
|
||||
new_circles = self.get_circles(new_vectors)
|
||||
self.set_decreasing_stroke_widths(new_circles)
|
||||
|
||||
drawn_path = self.get_drawn_path(new_vectors)
|
||||
drawn_path.clear_updaters()
|
||||
drawn_path.set_stroke(self.name_color, 3)
|
||||
|
||||
static_vectors = VMobject().become(new_vectors)
|
||||
static_circles = VMobject().become(new_circles)
|
||||
# static_circles = new_circles.deepcopy()
|
||||
# static_vectors.clear_updaters()
|
||||
# static_circles.clear_updaters()
|
||||
|
||||
self.play(
|
||||
Transform(vectors, static_vectors, remover=True),
|
||||
Transform(circles, static_circles, remover=True),
|
||||
frame.set_height, 1.5 * name.get_height(),
|
||||
frame.move_to, path,
|
||||
)
|
||||
|
||||
self.add(new_vectors, new_circles)
|
||||
self.vector_clock.set_value(0)
|
||||
self.play(
|
||||
ShowCreation(drawn_path),
|
||||
rate_func=linear,
|
||||
run_time=self.time_per_symbol
|
||||
)
|
||||
self.remove(new_vectors, new_circles)
|
||||
self.add(static_vectors, static_circles)
|
||||
|
||||
vectors = static_vectors
|
||||
circles = static_circles
|
||||
self.play(
|
||||
FadeOut(vectors),
|
||||
Restore(frame),
|
||||
run_time=2
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class FourierOfPiSymbol5(FourierOfPiSymbol):
|
||||
CONFIG = {
|
||||
"n_vectors": 5,
|
||||
"run_time": 10,
|
||||
}
|
||||
|
||||
|
||||
class FourierOfTrebleClef(FourierOfPiSymbol):
|
||||
CONFIG = {
|
||||
"n_vectors": 101,
|
||||
"run_time": 10,
|
||||
"start_drawn": True,
|
||||
"file_name": "TrebleClef",
|
||||
"height": 7.5,
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
shape = SVGMobject(self.file_name)
|
||||
return shape
|
||||
|
||||
def get_path(self):
|
||||
shape = self.get_shape()
|
||||
path = shape.family_members_with_points()[0]
|
||||
path.set_height(self.height)
|
||||
path.set_fill(opacity=0)
|
||||
path.set_stroke(WHITE, 0)
|
||||
return path
|
||||
|
||||
|
||||
class FourierOfIP(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"file_name": "IP_logo2",
|
||||
"height": 6,
|
||||
"n_vectors": 100,
|
||||
}
|
||||
|
||||
# def construct(self):
|
||||
# path = self.get_path()
|
||||
# self.add(path)
|
||||
|
||||
def get_shape(self):
|
||||
shape = SVGMobject(self.file_name)
|
||||
return shape
|
||||
|
||||
def get_path(self):
|
||||
shape = self.get_shape()
|
||||
path = shape.family_members_with_points()[0]
|
||||
path.add_line_to(path.get_start())
|
||||
# path.make_smooth()
|
||||
|
||||
path.set_height(self.height)
|
||||
path.set_fill(opacity=0)
|
||||
path.set_stroke(WHITE, 0)
|
||||
return path
|
||||
|
||||
|
||||
class FourierOfEighthNote(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"file_name": "EighthNote"
|
||||
}
|
||||
|
||||
|
||||
class FourierOfN(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"height": 6,
|
||||
"n_vectors": 1000,
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
return TexMobject("N")
|
||||
|
||||
|
||||
class FourierNailAndGear(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"height": 6,
|
||||
"n_vectors": 200,
|
||||
"run_time": 100,
|
||||
"slow_factor": 0.01,
|
||||
"parametric_function_step_size": 0.0001,
|
||||
"arrow_config": {
|
||||
"tip_length": 0.1,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
shape = SVGMobject("Nail_And_Gear")[1]
|
||||
return shape
|
||||
|
||||
|
||||
class FourierBatman(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"height": 4,
|
||||
"n_vectors": 100,
|
||||
"run_time": 10,
|
||||
"arrow_config": {
|
||||
"tip_length": 0.1,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
shape = SVGMobject("BatmanLogo")[1]
|
||||
return shape
|
||||
|
||||
|
||||
class FourierHeart(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"height": 4,
|
||||
"n_vectors": 100,
|
||||
"run_time": 10,
|
||||
"arrow_config": {
|
||||
"tip_length": 0.1,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
shape = SuitSymbol("hearts")
|
||||
return shape
|
||||
|
||||
def get_drawn_path(self, *args, **kwargs):
|
||||
kwargs["stroke_width"] = 5
|
||||
path = super().get_drawn_path(*args, **kwargs)
|
||||
path.set_color(PINK)
|
||||
return path
|
||||
|
||||
|
||||
class FourierNDQ(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"height": 4,
|
||||
"n_vectors": 1000,
|
||||
"run_time": 10,
|
||||
"arrow_config": {
|
||||
"tip_length": 0.1,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
path = VMobject()
|
||||
shape = TexMobject("NDQ")
|
||||
for sp in shape.family_members_with_points():
|
||||
path.append_points(sp.points)
|
||||
return path
|
||||
|
||||
|
||||
class FourierGoogleG(FourierOfTrebleClef):
|
||||
CONFIG = {
|
||||
"n_vectors": 10,
|
||||
"height": 5,
|
||||
"g_colors": [
|
||||
"#4285F4",
|
||||
"#DB4437",
|
||||
"#F4B400",
|
||||
"#0F9D58",
|
||||
]
|
||||
}
|
||||
|
||||
def get_shape(self):
|
||||
g = SVGMobject("google_logo")[5]
|
||||
g.center()
|
||||
self.add(g)
|
||||
return g
|
||||
|
||||
def get_drawn_path(self, *args, **kwargs):
|
||||
kwargs["stroke_width"] = 7
|
||||
path = super().get_drawn_path(*args, **kwargs)
|
||||
|
||||
blue, red, yellow, green = self.g_colors
|
||||
|
||||
path[:250].set_color(blue)
|
||||
path[250:333].set_color(green)
|
||||
path[333:370].set_color(yellow)
|
||||
path[370:755].set_color(red)
|
||||
path[755:780].set_color(yellow)
|
||||
path[780:860].set_color(green)
|
||||
path[860:].set_color(blue)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
class ExplainCircleAnimations(FourierCirclesScene):
|
||||
CONFIG = {
|
||||
"n_vectors": 100,
|
||||
"center_point": 2 * DOWN,
|
||||
"n_top_circles": 9,
|
||||
"path_height": 3,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_path()
|
||||
self.add_circles()
|
||||
self.wait(8)
|
||||
self.organize_circles_in_a_row()
|
||||
self.show_frequencies()
|
||||
self.show_examples_for_frequencies()
|
||||
self.show_as_vectors()
|
||||
self.show_vector_sum()
|
||||
self.tweak_starting_vectors()
|
||||
|
||||
def add_path(self):
|
||||
self.path = self.get_path()
|
||||
self.add(self.path)
|
||||
|
||||
def add_circles(self):
|
||||
coefs = self.get_coefficients_of_path(self.path)
|
||||
self.circles = self.get_circles(coefficients=coefs)
|
||||
|
||||
self.add(self.circles)
|
||||
self.drawn_path = self.get_drawn_path(self.circles)
|
||||
self.add(self.drawn_path)
|
||||
|
||||
def organize_circles_in_a_row(self):
|
||||
circles = self.circles
|
||||
top_circles = circles[:self.n_top_circles].copy()
|
||||
|
||||
center_trackers = VGroup()
|
||||
for circle in top_circles:
|
||||
tracker = VectorizedPoint(circle.center_func())
|
||||
circle.center_func = tracker.get_location
|
||||
center_trackers.add(tracker)
|
||||
tracker.freq = circle.freq
|
||||
tracker.circle = circle
|
||||
|
||||
center_trackers.submobjects.sort(
|
||||
key=lambda m: m.freq
|
||||
)
|
||||
center_trackers.generate_target()
|
||||
right_buff = 1.45
|
||||
center_trackers.target.arrange(RIGHT, buff=right_buff)
|
||||
center_trackers.target.to_edge(UP, buff=1.25)
|
||||
|
||||
self.add(top_circles)
|
||||
self.play(
|
||||
MoveToTarget(center_trackers),
|
||||
run_time=2
|
||||
)
|
||||
self.wait(4)
|
||||
|
||||
self.top_circles = top_circles
|
||||
self.center_trackers = center_trackers
|
||||
|
||||
def show_frequencies(self):
|
||||
center_trackers = self.center_trackers
|
||||
|
||||
freq_numbers = VGroup()
|
||||
for ct in center_trackers:
|
||||
number = Integer(ct.freq)
|
||||
number.next_to(ct, DOWN, buff=1)
|
||||
freq_numbers.add(number)
|
||||
ct.circle.number = number
|
||||
|
||||
ld, rd = [
|
||||
TexMobject("\\dots")
|
||||
for x in range(2)
|
||||
]
|
||||
ld.next_to(freq_numbers, LEFT, MED_LARGE_BUFF)
|
||||
rd.next_to(freq_numbers, RIGHT, MED_LARGE_BUFF)
|
||||
freq_numbers.add_to_back(ld)
|
||||
freq_numbers.add(rd)
|
||||
|
||||
freq_word = TextMobject("Frequencies")
|
||||
freq_word.scale(1.5)
|
||||
freq_word.set_color(YELLOW)
|
||||
freq_word.next_to(freq_numbers, DOWN, MED_LARGE_BUFF)
|
||||
|
||||
self.play(
|
||||
LaggedStartMap(
|
||||
FadeInFromDown, freq_numbers
|
||||
)
|
||||
)
|
||||
self.play(
|
||||
Write(freq_word),
|
||||
LaggedStartMap(
|
||||
ShowCreationThenFadeAround, freq_numbers,
|
||||
)
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
self.freq_numbers = freq_numbers
|
||||
self.freq_word = freq_word
|
||||
|
||||
def show_examples_for_frequencies(self):
|
||||
top_circles = self.top_circles
|
||||
c1, c2, c3 = [
|
||||
list(filter(
|
||||
lambda c: c.freq == k,
|
||||
top_circles
|
||||
))[0]
|
||||
for k in (1, 2, 3)
|
||||
]
|
||||
|
||||
neg_circles = VGroup(*filter(
|
||||
lambda c: c.freq < 0,
|
||||
top_circles
|
||||
))
|
||||
|
||||
for c in [c1, c2, c3, *neg_circles]:
|
||||
c.rect = SurroundingRectangle(c)
|
||||
|
||||
self.play(
|
||||
ShowCreation(c2.rect),
|
||||
WiggleOutThenIn(c2.number),
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
ReplacementTransform(c2.rect, c1.rect),
|
||||
)
|
||||
self.play(FadeOut(c1.rect))
|
||||
self.wait()
|
||||
self.play(
|
||||
ShowCreation(c3.rect),
|
||||
WiggleOutThenIn(c3.number),
|
||||
)
|
||||
self.play(
|
||||
FadeOut(c3.rect),
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
LaggedStart(*[
|
||||
ShowCreationThenFadeOut(c.rect)
|
||||
for c in neg_circles
|
||||
])
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(FadeOut(self.freq_word))
|
||||
|
||||
def show_as_vectors(self):
|
||||
top_circles = self.top_circles
|
||||
top_vectors = self.get_rotating_vectors(top_circles)
|
||||
top_vectors.set_color(WHITE)
|
||||
|
||||
original_circles = top_circles.copy()
|
||||
self.play(
|
||||
FadeIn(top_vectors),
|
||||
top_circles.set_opacity, 0,
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
top_circles.match_style, original_circles
|
||||
)
|
||||
self.remove(top_vectors)
|
||||
|
||||
self.top_vectors = top_vectors
|
||||
|
||||
def show_vector_sum(self):
|
||||
trackers = self.center_trackers.copy()
|
||||
trackers.sort(
|
||||
submob_func=lambda t: abs(t.circle.freq - 0.1)
|
||||
)
|
||||
plane = self.plane = NumberPlane(
|
||||
x_min=-3,
|
||||
x_max=3,
|
||||
y_min=-2,
|
||||
y_max=2,
|
||||
axis_config={
|
||||
"stroke_color": LIGHT_GREY,
|
||||
}
|
||||
)
|
||||
plane.set_stroke(width=1)
|
||||
plane.fade(0.5)
|
||||
plane.move_to(self.center_point)
|
||||
|
||||
self.play(
|
||||
FadeOut(self.drawn_path),
|
||||
FadeOut(self.circles),
|
||||
self.slow_factor_tracker.set_value, 0.05,
|
||||
)
|
||||
self.add(plane, self.path)
|
||||
self.play(FadeIn(plane))
|
||||
|
||||
new_circles = VGroup()
|
||||
last_tracker = None
|
||||
for tracker in trackers:
|
||||
if last_tracker:
|
||||
tracker.new_location_func = last_tracker.circle.get_start
|
||||
else:
|
||||
tracker.new_location_func = lambda: self.center_point
|
||||
|
||||
original_circle = tracker.circle
|
||||
tracker.circle = original_circle.copy()
|
||||
tracker.circle.center_func = tracker.get_location
|
||||
new_circles.add(tracker.circle)
|
||||
|
||||
self.add(tracker, tracker.circle)
|
||||
start_point = tracker.get_location()
|
||||
self.play(
|
||||
UpdateFromAlphaFunc(
|
||||
tracker, lambda t, a: t.move_to(
|
||||
interpolate(
|
||||
start_point,
|
||||
tracker.new_location_func(),
|
||||
a,
|
||||
)
|
||||
),
|
||||
run_time=2
|
||||
)
|
||||
)
|
||||
tracker.add_updater(lambda t: t.move_to(
|
||||
t.new_location_func()
|
||||
))
|
||||
self.wait(2)
|
||||
last_tracker = tracker
|
||||
|
||||
self.wait(3)
|
||||
|
||||
self.clear()
|
||||
self.slow_factor_tracker.set_value(0.1)
|
||||
self.add(
|
||||
self.top_circles,
|
||||
self.freq_numbers,
|
||||
self.path,
|
||||
)
|
||||
self.add_circles()
|
||||
for tc in self.top_circles:
|
||||
for c in self.circles:
|
||||
if c.freq == tc.freq:
|
||||
tc.rotate(
|
||||
angle_of_vector(c.get_start() - c.get_center()) -
|
||||
angle_of_vector(tc.get_start() - tc.get_center())
|
||||
)
|
||||
self.wait(10)
|
||||
|
||||
def tweak_starting_vectors(self):
|
||||
top_circles = self.top_circles
|
||||
circles = self.circles
|
||||
path = self.path
|
||||
drawn_path = self.drawn_path
|
||||
|
||||
new_path = self.get_new_path()
|
||||
new_coefs = self.get_coefficients_of_path(new_path)
|
||||
new_circles = self.get_circles(coefficients=new_coefs)
|
||||
|
||||
new_top_circles = VGroup()
|
||||
new_top_vectors = VGroup()
|
||||
for top_circle in top_circles:
|
||||
for circle in new_circles:
|
||||
if circle.freq == top_circle.freq:
|
||||
new_top_circle = circle.copy()
|
||||
new_top_circle.center_func = top_circle.get_center
|
||||
new_top_vector = self.get_rotating_vector(
|
||||
new_top_circle
|
||||
)
|
||||
new_top_circles.add(new_top_circle)
|
||||
new_top_vectors.add(new_top_vector)
|
||||
|
||||
self.play(
|
||||
self.slow_factor_tracker.set_value, 0,
|
||||
FadeOut(drawn_path)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
ReplacementTransform(top_circles, new_top_circles),
|
||||
ReplacementTransform(circles, new_circles),
|
||||
FadeOut(path),
|
||||
run_time=3,
|
||||
)
|
||||
new_drawn_path = self.get_drawn_path(
|
||||
new_circles, stroke_width=4,
|
||||
)
|
||||
self.add(new_drawn_path)
|
||||
self.slow_factor_tracker.set_value(0.1)
|
||||
self.wait(20)
|
||||
|
||||
#
|
||||
def configure_path(self, path):
|
||||
path.set_stroke(WHITE, 1)
|
||||
path.set_fill(BLACK, opacity=1)
|
||||
path.set_height(self.path_height)
|
||||
path.move_to(self.center_point)
|
||||
return path
|
||||
|
||||
def get_path(self):
|
||||
tex = TexMobject("f")
|
||||
path = tex.family_members_with_points()[0]
|
||||
self.configure_path(path)
|
||||
return path
|
||||
# return Square().set_height(3)
|
||||
|
||||
def get_new_path(self):
|
||||
shape = SVGMobject("TrebleClef")
|
||||
path = shape.family_members_with_points()[0]
|
||||
self.configure_path(path)
|
||||
path.scale(1.5, about_edge=DOWN)
|
||||
return path
|
File diff suppressed because it is too large
Load diff
|
@ -1,142 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
from active_projects.diffyq.part2.wordy_scenes import WriteHeatEquationTemplate
|
||||
|
||||
|
||||
class ReactionsToInitialHeatEquation(PiCreatureScene):
|
||||
def construct(self):
|
||||
randy = self.pi_creature
|
||||
randy.set_color(BLUE_C)
|
||||
randy.center()
|
||||
|
||||
point = VectorizedPoint().next_to(randy, UL, LARGE_BUFF)
|
||||
randy.add_updater(lambda r: r.look_at(point))
|
||||
|
||||
self.play(randy.change, "horrified")
|
||||
self.wait()
|
||||
self.play(randy.change, "pondering")
|
||||
self.wait()
|
||||
self.play(
|
||||
randy.change, "confused",
|
||||
point.next_to, randy, UR, LARGE_BUFF,
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
point.shift, 2 * DOWN,
|
||||
randy.change, "horrified"
|
||||
)
|
||||
self.wait(4)
|
||||
|
||||
|
||||
class ContrastPDEToODE(TeacherStudentsScene):
|
||||
CONFIG = {
|
||||
"random_seed": 2,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
student = self.students[2]
|
||||
pde, ode = words = VGroup(*[
|
||||
TextMobject(
|
||||
text + "\\\\",
|
||||
"Differential\\\\",
|
||||
"Equation"
|
||||
)
|
||||
for text in ("Partial", "Ordinary")
|
||||
])
|
||||
pde[0].set_color(YELLOW)
|
||||
ode[0].set_color(BLUE)
|
||||
for word in words:
|
||||
word.arrange(DOWN, aligned_edge=LEFT)
|
||||
|
||||
words.arrange(RIGHT, buff=LARGE_BUFF)
|
||||
words.next_to(student.get_corner(UR), UP, MED_LARGE_BUFF)
|
||||
words.shift(UR)
|
||||
lt = TexMobject("<")
|
||||
lt.scale(1.5)
|
||||
lt.move_to(Line(pde.get_right(), ode.get_left()))
|
||||
|
||||
for pi in self.pi_creatures:
|
||||
pi.add_updater(lambda p: p.look_at(pde))
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(VGroup(words, lt)),
|
||||
student.change, "raise_right_hand",
|
||||
)
|
||||
self.play(
|
||||
self.get_student_changes("pondering", "pondering", "hooray"),
|
||||
self.teacher.change, "happy"
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
Swap(ode, pde),
|
||||
self.teacher.change, "raise_right_hand",
|
||||
self.get_student_changes(
|
||||
"erm", "sassy", "confused"
|
||||
)
|
||||
)
|
||||
self.look_at(words)
|
||||
self.change_student_modes(
|
||||
"thinking", "thinking", "tease",
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class AskAboutWhereEquationComesFrom(TeacherStudentsScene, WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
equation = self.get_d1_equation()
|
||||
equation.move_to(self.hold_up_spot, DOWN)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(equation),
|
||||
self.teacher.change, "raise_right_hand"
|
||||
)
|
||||
self.student_says(
|
||||
"Um...why?",
|
||||
target_mode="sassy",
|
||||
student_index=2,
|
||||
bubble_kwargs={"direction": RIGHT},
|
||||
)
|
||||
self.change_student_modes(
|
||||
"confused", "confused", "sassy",
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
self.teacher.change, "pondering",
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class AskWhyRewriteIt(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.student_says(
|
||||
"Why?", student_index=1,
|
||||
bubble_kwargs={"height": 2, "width": 2},
|
||||
)
|
||||
self.students[1].bubble = None
|
||||
self.teacher_says(
|
||||
"One step closer\\\\to derivatives"
|
||||
)
|
||||
self.change_student_modes(
|
||||
"thinking", "thinking", "thinking",
|
||||
look_at_arg=4 * LEFT + 2 * UP
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class ReferenceKhanVideo(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
khan_logo = ImageMobject("KhanLogo")
|
||||
khan_logo.set_height(1)
|
||||
khan_logo.next_to(self.teacher, UP, buff=2)
|
||||
khan_logo.shift(2 * LEFT)
|
||||
|
||||
self.play(
|
||||
self.teacher.change, "raise_right_hand",
|
||||
)
|
||||
self.change_student_modes(
|
||||
"thinking", "pondering", "thinking",
|
||||
look_at_arg=self.screen
|
||||
)
|
||||
self.wait()
|
||||
self.play(FadeInFromDown(khan_logo))
|
||||
self.look_at(self.screen)
|
||||
self.wait(15)
|
|
@ -1,35 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
|
||||
TIME_COLOR = YELLOW
|
||||
X_COLOR = GREEN
|
||||
|
||||
|
||||
def get_heat_equation():
|
||||
pass
|
||||
|
||||
|
||||
def temperature_to_color(temp, min_temp=-1, max_temp=1):
|
||||
colors = [BLUE, TEAL, GREEN, YELLOW, "#ff0000"]
|
||||
|
||||
alpha = inverse_interpolate(min_temp, max_temp, temp)
|
||||
index, sub_alpha = integer_interpolate(
|
||||
0, len(colors) - 1, alpha
|
||||
)
|
||||
return interpolate_color(
|
||||
colors[index], colors[index + 1], sub_alpha
|
||||
)
|
||||
|
||||
|
||||
def two_d_temp_func(x, y, t):
|
||||
return np.sum([
|
||||
c * np.sin(f * var) * np.exp(-(f**2) * t)
|
||||
for c, f, var in [
|
||||
(0.2, 1, x),
|
||||
(0.3, 3, x),
|
||||
(0.02, 5, x),
|
||||
(0.01, 7, x),
|
||||
(0.5, 2, y),
|
||||
(0.1, 10, y),
|
||||
(0.01, 20, y),
|
||||
]
|
||||
])
|
|
@ -1,794 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
from active_projects.diffyq.part1.staging import TourOfDifferentialEquations
|
||||
|
||||
|
||||
class PartTwoOfTour(TourOfDifferentialEquations):
|
||||
CONFIG = {
|
||||
"zoomed_thumbnail_index": 1,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_title()
|
||||
self.show_thumbnails()
|
||||
self.zoom_in_to_one_thumbnail()
|
||||
|
||||
def zoom_in_to_one_thumbnail(self):
|
||||
frame = self.camera_frame
|
||||
thumbnails = self.thumbnails
|
||||
|
||||
ode = TextMobject("Ordinary\\\\", "Differential Equation")
|
||||
pde = TextMobject("Partial\\\\", "Differential Equation")
|
||||
for word, thumbnail, vect in zip([ode, pde], thumbnails, [DOWN, UP]):
|
||||
word.match_width(thumbnail)
|
||||
word.next_to(thumbnail, vect)
|
||||
ode[0].set_color(BLUE)
|
||||
pde[0].set_color(YELLOW)
|
||||
|
||||
self.add(ode)
|
||||
|
||||
frame.save_state()
|
||||
self.play(
|
||||
frame.replace,
|
||||
thumbnails[0],
|
||||
run_time=1,
|
||||
)
|
||||
self.play(
|
||||
Restore(frame, run_time=3),
|
||||
)
|
||||
self.play(
|
||||
TransformFromCopy(ode, pde),
|
||||
)
|
||||
self.play(
|
||||
ApplyMethod(
|
||||
frame.replace, thumbnails[1],
|
||||
path_arc=(-30 * DEGREES),
|
||||
run_time=3
|
||||
),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class BrownianMotion(Scene):
|
||||
CONFIG = {
|
||||
"wait_time": 60,
|
||||
"L": 3, # Box in [-L, L] x [-L, L]
|
||||
"n_particles": 100,
|
||||
"m1": 1,
|
||||
"m2": 100,
|
||||
"r1": 0.05,
|
||||
"r2": 0.5,
|
||||
"max_v": 5,
|
||||
"random_seed": 2,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_title()
|
||||
self.add_particles()
|
||||
self.wait(self.wait_time)
|
||||
|
||||
def add_title(self):
|
||||
square = Square(side_length=2 * self.L)
|
||||
title = TextMobject("Brownian motion")
|
||||
title.scale(1.5)
|
||||
title.next_to(square, UP)
|
||||
|
||||
self.add(square)
|
||||
self.add(title)
|
||||
|
||||
def add_particles(self):
|
||||
m1 = self.m1
|
||||
m2 = self.m2
|
||||
r1 = self.r1
|
||||
r2 = self.r2
|
||||
L = self.L
|
||||
max_v = self.max_v
|
||||
n_particles = self.n_particles
|
||||
|
||||
lil_particles = VGroup(*[
|
||||
self.get_particle(m1, r1, L, max_v)
|
||||
for k in range(n_particles)
|
||||
])
|
||||
big_particle = self.get_particle(m2, r2, L=r2, max_v=0)
|
||||
big_particle.set_fill(YELLOW, 1)
|
||||
|
||||
for p in lil_particles:
|
||||
if self.are_colliding(p, big_particle):
|
||||
lil_particles.remove(p)
|
||||
all_particles = VGroup(big_particle, *lil_particles)
|
||||
all_particles.add_updater(self.update_particles)
|
||||
|
||||
path = self.get_traced_path(big_particle)
|
||||
|
||||
self.add(all_particles)
|
||||
self.add(path)
|
||||
|
||||
self.particles = all_particles
|
||||
self.big_particle = big_particle
|
||||
self.path = path
|
||||
|
||||
def get_particle(self, m, r, L, max_v):
|
||||
dot = Dot(radius=r)
|
||||
dot.set_fill(WHITE, 0.7)
|
||||
dot.mass = m
|
||||
dot.radius = r
|
||||
dot.center = op.add(
|
||||
np.random.uniform(-L + r, L - r) * RIGHT,
|
||||
np.random.uniform(-L + r, L - r) * UP
|
||||
)
|
||||
dot.move_to(dot.center)
|
||||
dot.velocity = rotate_vector(
|
||||
np.random.uniform(0, max_v) * RIGHT,
|
||||
np.random.uniform(0, TAU),
|
||||
)
|
||||
return dot
|
||||
|
||||
def are_colliding(self, p1, p2):
|
||||
d = get_norm(p1.get_center() - p2.get_center())
|
||||
return (d < p1.radius + p2.radius)
|
||||
|
||||
def get_traced_path(self, particle):
|
||||
path = VMobject()
|
||||
path.set_stroke(BLUE, 3)
|
||||
path.start_new_path(particle.get_center())
|
||||
|
||||
buff = 0.02
|
||||
|
||||
def update_path(path):
|
||||
new_point = particle.get_center()
|
||||
if get_norm(new_point - path.get_last_point()) > buff:
|
||||
path.add_line_to(new_point)
|
||||
|
||||
path.add_updater(update_path)
|
||||
return path
|
||||
|
||||
def update_particles(self, particles, dt):
|
||||
for p1 in particles:
|
||||
p1.center += p1.velocity * dt
|
||||
|
||||
# Check particle collisions
|
||||
buff = 0.01
|
||||
for p2 in particles:
|
||||
if p1 is p2:
|
||||
continue
|
||||
v = p2.center - p1.center
|
||||
dist = get_norm(v)
|
||||
r_sum = p1.radius + p2.radius
|
||||
diff = dist - r_sum
|
||||
if diff < 0:
|
||||
unit_v = v / dist
|
||||
p1.center += (diff - buff) * unit_v / 2
|
||||
p2.center += -(diff - buff) * unit_v / 2
|
||||
u1 = p1.velocity
|
||||
u2 = p2.velocity
|
||||
m1 = p1.mass
|
||||
m2 = p2.mass
|
||||
v1 = (
|
||||
(m2 * (u2 - u1) + m1 * u1 + m2 * u2) /
|
||||
(m1 + m2)
|
||||
)
|
||||
v2 = (
|
||||
(m1 * (u1 - u2) + m1 * u1 + m2 * u2) /
|
||||
(m1 + m2)
|
||||
)
|
||||
p1.velocity = v1
|
||||
p2.velocity = v2
|
||||
|
||||
# Check edge collisions
|
||||
r1 = p1.radius
|
||||
c1 = p1.center
|
||||
for i in [0, 1]:
|
||||
if abs(c1[i]) + r1 > self.L:
|
||||
c1[i] = np.sign(c1[i]) * (self.L - r1)
|
||||
p1.velocity[i] *= -1 * op.mul(
|
||||
np.sign(p1.velocity[i]),
|
||||
np.sign(c1[i])
|
||||
)
|
||||
|
||||
for p in particles:
|
||||
p.move_to(p.center)
|
||||
return particles
|
||||
|
||||
|
||||
class AltBrownianMotion(BrownianMotion):
|
||||
CONFIG = {
|
||||
"wait_time": 20,
|
||||
"n_particles": 100,
|
||||
"m2": 10,
|
||||
}
|
||||
|
||||
|
||||
class BlackScholes(AltBrownianMotion):
|
||||
def construct(self):
|
||||
# For some reason I'm amused by the thought
|
||||
# Of this graph perfectly matching the Brownian
|
||||
# Motion y-coordiante
|
||||
self.add_title()
|
||||
self.add_particles()
|
||||
self.particles.set_opacity(0)
|
||||
self.remove(self.path)
|
||||
self.add_graph()
|
||||
self.wait(self.wait_time)
|
||||
|
||||
def add_title(self):
|
||||
title = TextMobject("Black-Scholes equations")
|
||||
title.scale(1.5)
|
||||
title.next_to(2 * UP, UP)
|
||||
|
||||
equation = TexMobject(
|
||||
"{\\partial V \\over \\partial t}", "+",
|
||||
"\\frac{1}{2} \\sigma^2 S^2",
|
||||
"{\\partial^2 V \\over \\partial S^2}", "+",
|
||||
"rS", "{\\partial V \\over \\partial S}",
|
||||
"-rV", "=", "0",
|
||||
)
|
||||
equation.scale(0.8)
|
||||
equation.next_to(title, DOWN)
|
||||
|
||||
self.add(title)
|
||||
self.add(equation)
|
||||
self.title = title
|
||||
self.equation = equation
|
||||
|
||||
def add_graph(self):
|
||||
axes = Axes(
|
||||
x_min=-1,
|
||||
x_max=20,
|
||||
y_min=0,
|
||||
y_max=10,
|
||||
axis_config={
|
||||
"unit_size": 0.5,
|
||||
},
|
||||
)
|
||||
axes.set_height(4)
|
||||
axes.move_to(DOWN)
|
||||
|
||||
def get_graph_point():
|
||||
return axes.c2p(
|
||||
self.get_time(),
|
||||
5 + 2 * self.big_particle.get_center()[1]
|
||||
)
|
||||
|
||||
graph = VMobject()
|
||||
graph.match_style(self.path)
|
||||
graph.start_new_path(get_graph_point())
|
||||
graph.add_updater(
|
||||
lambda g: g.add_line_to(get_graph_point())
|
||||
)
|
||||
|
||||
self.add(axes)
|
||||
self.add(graph)
|
||||
|
||||
|
||||
class ContrastChapters1And2(Scene):
|
||||
def construct(self):
|
||||
c1_frame, c2_frame = frames = VGroup(*[
|
||||
ScreenRectangle(height=3.5)
|
||||
for x in range(2)
|
||||
])
|
||||
frames.arrange(RIGHT, buff=LARGE_BUFF)
|
||||
|
||||
c1_title, c2_title = titles = VGroup(
|
||||
TextMobject("Chapter 1"),
|
||||
TextMobject("Chapter 2"),
|
||||
)
|
||||
titles.scale(1.5)
|
||||
|
||||
ode, pde = des = VGroup(
|
||||
TextMobject(
|
||||
"Ordinary",
|
||||
"Differential Equations\\\\",
|
||||
"ODEs",
|
||||
),
|
||||
TextMobject(
|
||||
"Partial",
|
||||
"Differential Equations\\\\",
|
||||
"PDEs",
|
||||
),
|
||||
)
|
||||
ode[0].set_color(BLUE)
|
||||
pde[0].set_color(YELLOW)
|
||||
for de in des:
|
||||
de[-1][0].match_color(de[0])
|
||||
de[-1].scale(1.5, about_point=de.get_top())
|
||||
|
||||
for title, frame, de in zip(titles, frames, des):
|
||||
title.next_to(frame, UP)
|
||||
de.match_width(frame)
|
||||
de.next_to(frame, DOWN)
|
||||
|
||||
lt = TexMobject("<")
|
||||
lt.move_to(Line(ode.get_right(), pde.get_left()))
|
||||
lt.scale(2, about_edge=UP)
|
||||
|
||||
c1_words = TextMobject(
|
||||
"They're", "really\\\\", "{}",
|
||||
"freaking", "hard\\\\",
|
||||
"to", "solve\\\\",
|
||||
)
|
||||
c1_words.set_height(0.5 * c1_frame.get_height())
|
||||
c1_words.move_to(c1_frame)
|
||||
|
||||
c2_words = TextMobject(
|
||||
"They're", "really", "\\emph{really}\\\\",
|
||||
"freaking", "hard\\\\",
|
||||
"to", "solve\\\\",
|
||||
)
|
||||
c2_words.set_color_by_tex("\\emph", YELLOW)
|
||||
c2_words.move_to(c2_frame)
|
||||
edit_shift = MED_LARGE_BUFF * RIGHT
|
||||
c2_edits = VGroup(
|
||||
TextMobject("sometimes").next_to(
|
||||
c2_words[1:3], UP,
|
||||
aligned_edge=LEFT,
|
||||
),
|
||||
Line(
|
||||
c2_words[1].get_left(),
|
||||
c2_words[2].get_right(),
|
||||
stroke_width=8,
|
||||
),
|
||||
TextMobject("not too").next_to(
|
||||
c2_words[3], LEFT,
|
||||
),
|
||||
Line(
|
||||
c2_words[3].get_left(),
|
||||
c2_words[3].get_right(),
|
||||
stroke_width=8,
|
||||
),
|
||||
)
|
||||
c2_edits.set_color(RED)
|
||||
c2_edits[2:].shift(edit_shift)
|
||||
|
||||
self.add(titles)
|
||||
self.add(frames)
|
||||
self.add(des)
|
||||
|
||||
self.wait()
|
||||
self.play(LaggedStartMap(
|
||||
FadeInFromDown, c1_words,
|
||||
lag_ratio=0.1,
|
||||
))
|
||||
self.wait()
|
||||
# self.play(FadeIn(ode))
|
||||
self.play(
|
||||
# TransformFromCopy(ode, pde),
|
||||
TransformFromCopy(c1_words, c2_words),
|
||||
Write(lt)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Write(c2_edits[:2], run_time=1),
|
||||
)
|
||||
self.play(
|
||||
c2_words[3:5].shift, edit_shift,
|
||||
Write(c2_edits[2:]),
|
||||
run_time=1,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ShowCubeFormation(ThreeDScene):
|
||||
CONFIG = {
|
||||
"camera_config": {
|
||||
"shading_factor": 1.0,
|
||||
},
|
||||
"color": False,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
light_source = self.camera.light_source
|
||||
light_source.move_to(np.array([-6, -3, 6]))
|
||||
|
||||
cube = Cube(
|
||||
side_length=4,
|
||||
fill_color=GREY,
|
||||
stroke_color=WHITE,
|
||||
stroke_width=0.5,
|
||||
)
|
||||
cube.set_fill(opacity=1)
|
||||
if self.color:
|
||||
# cube[0].set_color(BLUE)
|
||||
# cube[1].set_color(RED)
|
||||
# for face in cube[2:]:
|
||||
# face.set_color([BLUE, RED])
|
||||
cube.color_using_background_image("VerticalTempGradient")
|
||||
|
||||
# light_source.next_to(cube, np.array([1, -1, 1]), buff=2)
|
||||
|
||||
cube_3d = cube.copy()
|
||||
cube_2d = cube_3d.copy().stretch(0, 2)
|
||||
cube_1d = cube_2d.copy().stretch(0, 1)
|
||||
cube_0d = cube_1d.copy().stretch(0, 0)
|
||||
|
||||
cube.become(cube_0d)
|
||||
|
||||
self.set_camera_orientation(
|
||||
phi=70 * DEGREES,
|
||||
theta=-145 * DEGREES,
|
||||
)
|
||||
self.begin_ambient_camera_rotation(rate=0.05)
|
||||
|
||||
for target in [cube_1d, cube_2d, cube_3d]:
|
||||
self.play(
|
||||
Transform(cube, target, run_time=1.5)
|
||||
)
|
||||
self.wait(8)
|
||||
|
||||
|
||||
class ShowCubeFormationWithColor(ShowCubeFormation):
|
||||
CONFIG = {
|
||||
"color": True,
|
||||
}
|
||||
|
||||
|
||||
class ShowRect(Scene):
|
||||
CONFIG = {
|
||||
"height": 1,
|
||||
"width": 3,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
rect = Rectangle(
|
||||
height=self.height,
|
||||
width=self.width,
|
||||
)
|
||||
rect.set_color(YELLOW)
|
||||
self.play(ShowCreationThenFadeOut(rect))
|
||||
|
||||
|
||||
class ShowSquare(ShowRect):
|
||||
CONFIG = {
|
||||
"height": 1,
|
||||
"width": 1,
|
||||
}
|
||||
|
||||
|
||||
class ShowHLine(Scene):
|
||||
def construct(self):
|
||||
line = Line(LEFT, RIGHT)
|
||||
line.set_color(BLUE)
|
||||
self.play(ShowCreationThenFadeOut(line))
|
||||
|
||||
|
||||
class ShowCross(Scene):
|
||||
def construct(self):
|
||||
cross = Cross(Square())
|
||||
cross.set_width(3)
|
||||
cross.set_height(1, stretch=True)
|
||||
self.play(ShowCreation(cross))
|
||||
|
||||
|
||||
class TwoBodyEquations(Scene):
|
||||
def construct(self):
|
||||
kw = {
|
||||
"tex_to_color_map": {
|
||||
"x_1": LIGHT_GREY,
|
||||
"y_1": LIGHT_GREY,
|
||||
"x_2": BLUE,
|
||||
"y_2": BLUE,
|
||||
"=": WHITE,
|
||||
}
|
||||
}
|
||||
equations = VGroup(
|
||||
TexMobject(
|
||||
"{d^2 x_1 \\over dt^2}",
|
||||
"=",
|
||||
"{x_2 - x_1 \\over m_1 \\left(",
|
||||
"(x_2 - x_1)^2 + (y_2 - y_1)^2",
|
||||
"\\right)^{3/2}",
|
||||
**kw
|
||||
),
|
||||
TexMobject(
|
||||
"{d^2 y_1 \\over dt^2}",
|
||||
"=",
|
||||
"{y_2 - y_1 \\over m_1 \\left(",
|
||||
"(x_2 - x_1)^2 + (y_2 - y_1)^2",
|
||||
"\\right)^{3/2}",
|
||||
**kw
|
||||
),
|
||||
TexMobject(
|
||||
"{d^2 x_2 \\over dt^2}",
|
||||
"=",
|
||||
"{x_1 - x_2 \\over m_2 \\left(",
|
||||
"(x_2 - x_1)^2 + (y_2 - y_1)^2",
|
||||
"\\right)^{3/2}",
|
||||
**kw
|
||||
),
|
||||
TexMobject(
|
||||
"{d^2 y_2 \\over dt^2}",
|
||||
"=",
|
||||
"{y_1 - y_2 \\over m_2 \\left(",
|
||||
"(x_2 - x_1)^2 + (y_2 - y_1)^2",
|
||||
"\\right)^{3/2}",
|
||||
**kw
|
||||
),
|
||||
)
|
||||
|
||||
equations.arrange(DOWN, buff=LARGE_BUFF)
|
||||
equations.set_height(6)
|
||||
equations.to_edge(LEFT)
|
||||
|
||||
variables = VGroup()
|
||||
lhss = VGroup()
|
||||
rhss = VGroup()
|
||||
for equation in equations:
|
||||
variable = equation[1]
|
||||
lhs = equation[:4]
|
||||
rhs = equation[4:]
|
||||
variables.add(variable)
|
||||
lhss.add(lhs)
|
||||
rhss.add(rhs)
|
||||
lhss_copy = lhss.copy()
|
||||
|
||||
for variable, lhs in zip(variables, lhss):
|
||||
variable.save_state()
|
||||
variable.match_height(lhs)
|
||||
variable.scale(0.7)
|
||||
variable.move_to(lhs, LEFT)
|
||||
|
||||
self.play(LaggedStart(*[
|
||||
FadeInFrom(v, RIGHT)
|
||||
for v in variables
|
||||
]))
|
||||
self.wait()
|
||||
self.play(
|
||||
LaggedStartMap(Restore, variables),
|
||||
FadeIn(
|
||||
lhss_copy,
|
||||
remover=True,
|
||||
lag_ratio=0.1,
|
||||
run_time=2,
|
||||
)
|
||||
)
|
||||
self.add(lhss)
|
||||
self.wait()
|
||||
self.play(LaggedStartMap(
|
||||
FadeIn, rhss
|
||||
))
|
||||
self.wait()
|
||||
self.play(
|
||||
LaggedStart(*[
|
||||
ShowCreationThenFadeAround(lhs[:3])
|
||||
for lhs in lhss
|
||||
])
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
LaggedStartMap(
|
||||
ShowCreationThenFadeAround,
|
||||
rhss,
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class LaplacianIntuition(SpecialThreeDScene):
|
||||
CONFIG = {
|
||||
"three_d_axes_config": {
|
||||
"x_min": -5,
|
||||
"x_max": 5,
|
||||
"y_min": -5,
|
||||
"y_max": 5,
|
||||
},
|
||||
"surface_resolution": 32,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
axes = self.get_axes()
|
||||
axes.scale(0.5, about_point=ORIGIN)
|
||||
self.set_camera_to_default_position()
|
||||
self.begin_ambient_camera_rotation()
|
||||
|
||||
def func(x, y):
|
||||
return np.array([
|
||||
x, y,
|
||||
2.7 + 0.5 * (np.sin(x) + np.cos(y)) -
|
||||
0.025 * (x**2 + y**2)
|
||||
])
|
||||
|
||||
surface_config = {
|
||||
"u_min": -5,
|
||||
"u_max": 5,
|
||||
"v_min": -5,
|
||||
"v_max": 5,
|
||||
"resolution": self.surface_resolution,
|
||||
}
|
||||
# plane = ParametricSurface(
|
||||
# lambda u, v: np.array([u, v, 0]),
|
||||
# **surface_config
|
||||
# )
|
||||
# plane.set_stroke(WHITE, width=0.1)
|
||||
# plane.set_fill(WHITE, opacity=0.1)
|
||||
plane = Square(
|
||||
side_length=10,
|
||||
stroke_width=0,
|
||||
fill_color=WHITE,
|
||||
fill_opacity=0.1,
|
||||
)
|
||||
plane.center()
|
||||
plane.set_shade_in_3d(True)
|
||||
|
||||
surface = ParametricSurface(
|
||||
func, **surface_config
|
||||
)
|
||||
surface.set_stroke(BLUE, width=0.1)
|
||||
surface.set_fill(BLUE, opacity=0.25)
|
||||
|
||||
self.add(axes, plane, surface)
|
||||
|
||||
point = VectorizedPoint(np.array([2, -2, 0]))
|
||||
dot = Dot()
|
||||
dot.set_color(GREEN)
|
||||
dot.add_updater(lambda d: d.move_to(point))
|
||||
line = always_redraw(lambda: DashedLine(
|
||||
point.get_location(),
|
||||
func(*point.get_location()[:2]),
|
||||
background_image_file="VerticalTempGradient",
|
||||
))
|
||||
|
||||
circle = Circle(radius=0.25)
|
||||
circle.set_color(YELLOW)
|
||||
circle.insert_n_curves(20)
|
||||
circle.add_updater(lambda m: m.move_to(point))
|
||||
circle.set_shade_in_3d(True)
|
||||
surface_circle = always_redraw(
|
||||
lambda: circle.copy().apply_function(
|
||||
lambda p: func(*p[:2])
|
||||
).shift(
|
||||
0.02 * IN
|
||||
).color_using_background_image("VerticalTempGradient")
|
||||
)
|
||||
|
||||
self.play(FadeInFromLarge(dot))
|
||||
self.play(ShowCreation(line))
|
||||
self.play(TransformFromCopy(dot, circle))
|
||||
self.play(
|
||||
Transform(
|
||||
circle.copy(),
|
||||
surface_circle.copy().clear_updaters(),
|
||||
remover=True,
|
||||
)
|
||||
)
|
||||
self.add(surface_circle)
|
||||
|
||||
self.wait()
|
||||
for vect in [4 * LEFT, DOWN, 4 * RIGHT, UP]:
|
||||
self.play(
|
||||
point.shift, vect,
|
||||
run_time=3,
|
||||
)
|
||||
|
||||
|
||||
class StrogatzMention(PiCreatureScene):
|
||||
def construct(self):
|
||||
self.show_book()
|
||||
self.show_motives()
|
||||
self.show_pages()
|
||||
|
||||
def show_book(self):
|
||||
morty = self.pi_creature
|
||||
book = ImageMobject("InfinitePowers")
|
||||
book.set_height(5)
|
||||
book.to_edge(LEFT)
|
||||
|
||||
steve = ImageMobject("Strogatz_by_bricks")
|
||||
steve.set_height(5)
|
||||
steve.to_edge(LEFT)
|
||||
|
||||
name = TextMobject("Steven Strogatz")
|
||||
name.match_width(steve)
|
||||
name.next_to(steve, DOWN)
|
||||
|
||||
self.think(
|
||||
"Hmm...many good\\\\lessons here...",
|
||||
run_time=1
|
||||
)
|
||||
self.wait()
|
||||
self.play(FadeInFromDown(steve))
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeInFrom(book, DOWN),
|
||||
steve.shift, 4 * RIGHT,
|
||||
RemovePiCreatureBubble(
|
||||
morty, target_mode="thinking"
|
||||
)
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
FadeOut(steve),
|
||||
FadeOut(morty),
|
||||
)
|
||||
|
||||
self.book = book
|
||||
|
||||
def show_motives(self):
|
||||
motives = VGroup(
|
||||
TextMobject("1) Scratch and itch"),
|
||||
TextMobject("2) Make people love math"),
|
||||
)
|
||||
motives.scale(1.5)
|
||||
motives.arrange(
|
||||
DOWN, LARGE_BUFF,
|
||||
aligned_edge=LEFT,
|
||||
)
|
||||
motives.move_to(
|
||||
Line(
|
||||
self.book.get_right(),
|
||||
FRAME_WIDTH * RIGHT / 2
|
||||
)
|
||||
)
|
||||
motives.to_edge(UP)
|
||||
|
||||
for motive in motives:
|
||||
self.play(FadeInFromDown(motive))
|
||||
self.wait(2)
|
||||
self.play(FadeOut(motives))
|
||||
|
||||
def show_pages(self):
|
||||
book = self.book
|
||||
pages = Group(*[
|
||||
ImageMobject("IP_Sample_Page{}".format(i))
|
||||
for i in range(1, 4)
|
||||
])
|
||||
for page in pages:
|
||||
page.match_height(book)
|
||||
page.next_to(book, RIGHT)
|
||||
|
||||
last_page = VectorizedPoint()
|
||||
for page in pages:
|
||||
self.play(
|
||||
FadeOut(last_page),
|
||||
FadeIn(page)
|
||||
)
|
||||
self.wait()
|
||||
last_page = page
|
||||
|
||||
self.play(FadeOut(last_page))
|
||||
|
||||
def create_pi_creature(self):
|
||||
return Mortimer().to_corner(DR)
|
||||
|
||||
|
||||
class Thumbnail(Scene):
|
||||
def construct(self):
|
||||
image = ImageMobject("HeatSurfaceExampleFlipped")
|
||||
image.set_height(6.5)
|
||||
image.to_edge(DOWN, buff=-SMALL_BUFF)
|
||||
self.add(image)
|
||||
|
||||
equation = TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}", "=",
|
||||
"\\alpha", "\\nabla^2 {T}",
|
||||
tex_to_color_map={
|
||||
"{t}": YELLOW,
|
||||
"{T}": RED,
|
||||
}
|
||||
)
|
||||
equation.scale(2)
|
||||
equation.to_edge(UP)
|
||||
|
||||
self.add(equation)
|
||||
|
||||
Group(equation, image).shift(1.5 * RIGHT)
|
||||
|
||||
question = TextMobject("What is\\\\this?")
|
||||
question.scale(2.5)
|
||||
question.to_edge(LEFT)
|
||||
arrow = Arrow(
|
||||
question.get_top(),
|
||||
equation.get_left(),
|
||||
buff=0.5,
|
||||
path_arc=-90 * DEGREES,
|
||||
)
|
||||
arrow.set_stroke(width=5)
|
||||
|
||||
self.add(question, arrow)
|
||||
|
||||
|
||||
class ShowNewton(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
|
||||
class ShowCupOfWater(Scene):
|
||||
def construct(self):
|
||||
pass
|
|
@ -1,785 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
|
||||
|
||||
class WriteHeatEquationTemplate(Scene):
|
||||
CONFIG = {
|
||||
"tex_mobject_config": {
|
||||
"tex_to_color_map": {
|
||||
"{T}": WHITE,
|
||||
"{t}": YELLOW,
|
||||
"{x}": GREEN,
|
||||
"{y}": RED,
|
||||
"{z}": BLUE,
|
||||
"\\partial": WHITE,
|
||||
"2": WHITE,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def get_d1_equation(self):
|
||||
return TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}({x}, {t})", "=",
|
||||
"\\alpha \\cdot",
|
||||
"{\\partial^2 {T} \\over \\partial {x}^2} ({x}, {t})",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
|
||||
def get_d1_equation_without_inputs(self):
|
||||
return TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}", "=",
|
||||
"\\alpha \\cdot",
|
||||
"{\\partial^2 {T} \\over \\partial {x}^2}",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
|
||||
def get_d3_equation(self):
|
||||
return TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}", "=",
|
||||
"\\alpha \\left(",
|
||||
"{\\partial^2 {T} \\over \\partial {x}^2} + ",
|
||||
"{\\partial^2 {T} \\over \\partial {y}^2} + ",
|
||||
"{\\partial^2 {T} \\over \\partial {z}^2}",
|
||||
"\\right)",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
|
||||
def get_general_equation(self):
|
||||
return TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}", "=",
|
||||
"\\alpha", "\\nabla^2 {T}",
|
||||
**self.tex_mobject_config,
|
||||
)
|
||||
|
||||
def get_d3_equation_with_inputs(self):
|
||||
return TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}",
|
||||
"({x}, {y}, {z}, {t})", "=",
|
||||
"\\alpha \\left(",
|
||||
"{\\partial^2 {T} \\over \\partial {x}^2}",
|
||||
"({x}, {y}, {z}, {t}) + ",
|
||||
"{\\partial^2 {T} \\over \\partial {y}^2}",
|
||||
"({x}, {y}, {z}, {t}) + ",
|
||||
"{\\partial^2 {T} \\over \\partial {z}^2}",
|
||||
"({x}, {y}, {z}, {t})",
|
||||
"\\right)",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
|
||||
def get_d1_words(self):
|
||||
return TextMobject("Heat equation\\\\", "(1 dimension)")
|
||||
|
||||
def get_d3_words(self):
|
||||
return TextMobject("Heat equation\\\\", "(3 dimensions)")
|
||||
|
||||
def get_d1_group(self):
|
||||
group = VGroup(
|
||||
self.get_d1_words(),
|
||||
self.get_d1_equation(),
|
||||
)
|
||||
group.arrange(DOWN, buff=MED_LARGE_BUFF)
|
||||
return group
|
||||
|
||||
def get_d3_group(self):
|
||||
group = VGroup(
|
||||
self.get_d3_words(),
|
||||
self.get_d3_equation(),
|
||||
)
|
||||
group.arrange(DOWN, buff=MED_LARGE_BUFF)
|
||||
return group
|
||||
|
||||
|
||||
class HeatEquationIntroTitle(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
scale_factor = 1.25
|
||||
title = TextMobject("The Heat Equation")
|
||||
title.scale(scale_factor)
|
||||
title.to_edge(UP)
|
||||
|
||||
equation = self.get_general_equation()
|
||||
equation.scale(scale_factor)
|
||||
equation.next_to(title, DOWN, MED_LARGE_BUFF)
|
||||
equation.set_color_by_tex("{T}", RED)
|
||||
|
||||
self.play(
|
||||
FadeInFrom(title, DOWN),
|
||||
FadeInFrom(equation, UP),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class BringTogether(Scene):
|
||||
def construct(self):
|
||||
arrows = VGroup(Vector(2 * RIGHT), Vector(2 * LEFT))
|
||||
arrows.arrange(RIGHT, buff=2)
|
||||
words = TextMobject("Bring together")[0]
|
||||
words.next_to(arrows, DOWN)
|
||||
words.save_state()
|
||||
words.space_out_submobjects(1.2)
|
||||
|
||||
self.play(
|
||||
VFadeIn(words),
|
||||
Restore(words),
|
||||
arrows.arrange, RIGHT, {"buff": SMALL_BUFF},
|
||||
VFadeIn(arrows),
|
||||
)
|
||||
self.play(FadeOut(words), FadeOut(arrows))
|
||||
|
||||
|
||||
class FourierSeriesIntro(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
title_scale_value = 1.5
|
||||
|
||||
title = TextMobject(
|
||||
"Fourier ", "Series",
|
||||
)
|
||||
title.scale(title_scale_value)
|
||||
title.to_edge(UP)
|
||||
title.generate_target()
|
||||
|
||||
details_coming = TextMobject("Details coming...")
|
||||
details_coming.next_to(title.get_corner(DR), DOWN)
|
||||
details_coming.set_color(LIGHT_GREY)
|
||||
|
||||
# physics = TextMobject("Physics")
|
||||
heat = TextMobject("Heat")
|
||||
heat.scale(title_scale_value)
|
||||
physics = self.get_general_equation()
|
||||
physics.set_color_by_tex("{T}", RED)
|
||||
arrow1 = Arrow(LEFT, RIGHT)
|
||||
arrow2 = Arrow(LEFT, RIGHT)
|
||||
group = VGroup(
|
||||
heat, arrow1, physics, arrow2, title.target
|
||||
)
|
||||
group.arrange(RIGHT)
|
||||
# physics.align_to(title.target, UP)
|
||||
group.to_edge(UP)
|
||||
|
||||
rot_square = Square()
|
||||
rot_square.fade(1)
|
||||
rot_square.add_updater(lambda m, dt: m.rotate(dt))
|
||||
|
||||
def update_heat_colors(heat):
|
||||
colors = [YELLOW, RED]
|
||||
vertices = rot_square.get_vertices()
|
||||
letters = heat.family_members_with_points()
|
||||
for letter, vertex in zip(letters, vertices):
|
||||
alpha = (normalize(vertex)[0] + 1) / 2
|
||||
i, sa = integer_interpolate(0, len(colors) - 1, alpha)
|
||||
letter.set_color(interpolate_color(
|
||||
colors[i], colors[i + 1], alpha,
|
||||
))
|
||||
heat.add_updater(update_heat_colors)
|
||||
|
||||
image = ImageMobject("Joseph Fourier")
|
||||
image.set_height(5)
|
||||
image.next_to(title, DOWN, LARGE_BUFF)
|
||||
image.to_edge(LEFT)
|
||||
name = TextMobject("Joseph", "Fourier")
|
||||
name.next_to(image, DOWN)
|
||||
|
||||
bubble = ThoughtBubble(
|
||||
height=2,
|
||||
width=2.5,
|
||||
direction=RIGHT,
|
||||
)
|
||||
bubble.set_fill(opacity=0)
|
||||
bubble.set_stroke(WHITE)
|
||||
bubble.set_stroke(BLACK, 5, background=True)
|
||||
bubble.shift(heat.get_center() - bubble.get_bubble_center())
|
||||
bubble[:-1].shift(LEFT + 0.2 * DOWN)
|
||||
bubble[:-1].rotate(-20 * DEGREES)
|
||||
for mob in bubble[:-1]:
|
||||
mob.rotate(20 * DEGREES)
|
||||
|
||||
# self.play(FadeInFromDown(title))
|
||||
self.add(title)
|
||||
self.play(
|
||||
FadeInFromDown(image),
|
||||
TransformFromCopy(
|
||||
title.get_part_by_tex("Fourier"),
|
||||
name.get_part_by_tex("Fourier"),
|
||||
path_arc=90 * DEGREES,
|
||||
),
|
||||
FadeIn(name.get_part_by_tex("Joseph")),
|
||||
)
|
||||
self.play(Write(details_coming, run_time=1))
|
||||
self.play(LaggedStartMap(FadeOut, details_coming[0], run_time=1))
|
||||
self.wait()
|
||||
self.add(rot_square)
|
||||
self.play(
|
||||
FadeInFrom(physics, RIGHT),
|
||||
GrowArrow(arrow2),
|
||||
FadeInFrom(heat, RIGHT),
|
||||
GrowArrow(arrow1),
|
||||
MoveToTarget(title),
|
||||
)
|
||||
self.play(ShowCreation(bubble))
|
||||
self.wait(10)
|
||||
|
||||
|
||||
class CompareODEToPDE(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
|
||||
class TodaysTargetWrapper(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
|
||||
class TwoGraphTypeTitles(Scene):
|
||||
def construct(self):
|
||||
left_title = TextMobject(
|
||||
"Represent time\\\\with actual time"
|
||||
)
|
||||
left_title.shift(FRAME_WIDTH * LEFT / 4)
|
||||
right_title = TextMobject(
|
||||
"Represent time\\\\with an axis"
|
||||
)
|
||||
right_title.shift(FRAME_WIDTH * RIGHT / 4)
|
||||
|
||||
titles = VGroup(left_title, right_title)
|
||||
for title in titles:
|
||||
title.scale(1.25)
|
||||
title.to_edge(UP)
|
||||
|
||||
self.play(FadeInFromDown(right_title))
|
||||
self.wait()
|
||||
self.play(FadeInFromDown(left_title))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ShowPartialDerivativeSymbols(Scene):
|
||||
def construct(self):
|
||||
t2c = {
|
||||
"{x}": GREEN,
|
||||
"{t}": YELLOW,
|
||||
}
|
||||
d_derivs, del_derivs = VGroup(*[
|
||||
VGroup(*[
|
||||
TexMobject(
|
||||
"{" + sym, "T", "\\over", sym, var + "}",
|
||||
"(", "{x}", ",", "{t}", ")",
|
||||
).set_color_by_tex_to_color_map(t2c)
|
||||
for var in ("{x}", "{t}")
|
||||
])
|
||||
for sym in ("d", "\\partial")
|
||||
])
|
||||
dTdx, dTdt = d_derivs
|
||||
delTdelx, delTdelx = del_derivs
|
||||
dels = VGroup(*it.chain(*[
|
||||
del_deriv.get_parts_by_tex("\\partial")
|
||||
for del_deriv in del_derivs
|
||||
]))
|
||||
|
||||
dTdx.to_edge(UP)
|
||||
self.play(FadeInFrom(dTdx, DOWN))
|
||||
self.wait()
|
||||
self.play(ShowCreationThenFadeAround(dTdx[3:5]))
|
||||
self.play(ShowCreationThenFadeAround(dTdx[:2]))
|
||||
self.wait()
|
||||
|
||||
dTdt.move_to(dTdx)
|
||||
self.play(
|
||||
dTdx.next_to, dTdt, RIGHT, {"buff": 1.5},
|
||||
dTdx.set_opacity, 0.5,
|
||||
FadeInFromDown(dTdt)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
for m1, m2 in zip(d_derivs, del_derivs):
|
||||
m2.move_to(m1)
|
||||
|
||||
pd_words = TextMobject("Partial derivatives")
|
||||
pd_words.next_to(del_derivs, DOWN, MED_LARGE_BUFF)
|
||||
|
||||
self.play(
|
||||
Write(pd_words),
|
||||
dTdx.set_opacity, 1,
|
||||
run_time=1,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
ReplacementTransform(d_derivs, del_derivs)
|
||||
)
|
||||
self.play(
|
||||
LaggedStartMap(
|
||||
ShowCreationThenFadeAround,
|
||||
dels,
|
||||
surrounding_rectangle_config={
|
||||
"color": BLUE,
|
||||
"buff": 0.5 * SMALL_BUFF,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
num_words = VGroup(*[
|
||||
TextMobject(
|
||||
"Change in $T$\\\\caused by {}",
|
||||
"$\\partial$", "${}$".format(var),
|
||||
arg_separator="",
|
||||
).set_color_by_tex_to_color_map(t2c)
|
||||
for var in ("{x}", "{t}")
|
||||
])
|
||||
num_words.scale(0.8)
|
||||
for word, deriv in zip(num_words, del_derivs):
|
||||
num = deriv[:2]
|
||||
word.move_to(num, UP)
|
||||
word.to_edge(UP, buff=MED_SMALL_BUFF)
|
||||
deriv.rect = SurroundingRectangle(
|
||||
num,
|
||||
buff=SMALL_BUFF,
|
||||
stroke_width=2,
|
||||
color=word[-1].get_color(),
|
||||
)
|
||||
deriv.rect.mob = num
|
||||
deriv.rect.add_updater(lambda r: r.move_to(r.mob))
|
||||
|
||||
self.play(
|
||||
Write(num_words[1]),
|
||||
VGroup(del_derivs, pd_words).shift, DOWN,
|
||||
ShowCreation(del_derivs[1].rect),
|
||||
)
|
||||
self.play(
|
||||
Write(num_words[0]),
|
||||
ShowCreation(del_derivs[0].rect),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class WriteHeatEquation(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
title = TextMobject("The Heat Equation")
|
||||
title.to_edge(UP)
|
||||
|
||||
equation = self.get_d1_equation()
|
||||
equation.next_to(title, DOWN)
|
||||
|
||||
eq_i = equation.index_of_part_by_tex("=")
|
||||
dt_part = equation[:eq_i]
|
||||
dx_part = equation[eq_i + 3:]
|
||||
dt_rect = SurroundingRectangle(dt_part)
|
||||
dt_rect.set_stroke(YELLOW, 2)
|
||||
dx_rect = SurroundingRectangle(dx_part)
|
||||
dx_rect.set_stroke(GREEN, 2)
|
||||
|
||||
two_outlines = equation.get_parts_by_tex("2").copy()
|
||||
two_outlines.set_stroke(YELLOW, 2)
|
||||
two_outlines.set_fill(opacity=0)
|
||||
|
||||
to_be_explained = TextMobject(
|
||||
"To be explained shortly..."
|
||||
)
|
||||
to_be_explained.scale(0.7)
|
||||
to_be_explained.next_to(equation, RIGHT, MED_LARGE_BUFF)
|
||||
to_be_explained.fade(1)
|
||||
|
||||
pde = TextMobject("Partial Differential Equation")
|
||||
pde.move_to(title)
|
||||
|
||||
del_outlines = equation.get_parts_by_tex("\\partial").copy()
|
||||
del_outlines.set_stroke(YELLOW, 2)
|
||||
del_outlines.set_fill(opacity=0)
|
||||
|
||||
self.play(
|
||||
FadeInFrom(title, 0.5 * DOWN),
|
||||
FadeInFrom(equation, 0.5 * UP),
|
||||
)
|
||||
self.wait()
|
||||
self.play(ShowCreation(dt_rect))
|
||||
self.wait()
|
||||
self.play(TransformFromCopy(dt_rect, dx_rect))
|
||||
self.play(ShowCreationThenDestruction(two_outlines))
|
||||
self.wait()
|
||||
self.play(Write(to_be_explained, run_time=1))
|
||||
self.wait(2)
|
||||
self.play(
|
||||
ShowCreationThenDestruction(
|
||||
del_outlines,
|
||||
lag_ratio=0.1,
|
||||
)
|
||||
)
|
||||
self.play(
|
||||
FadeOutAndShift(title, UP),
|
||||
FadeInFrom(pde, DOWN),
|
||||
FadeOut(dt_rect),
|
||||
FadeOut(dx_rect),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class Show3DEquation(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
equation = self.get_d3_equation_with_inputs()
|
||||
equation.set_width(FRAME_WIDTH - 1)
|
||||
inputs = VGroup(*it.chain(*[
|
||||
equation.get_parts_by_tex(s)
|
||||
for s in ["{x}", "{y}", "{z}", "{t}"]
|
||||
]))
|
||||
inputs.sort()
|
||||
equation.to_edge(UP)
|
||||
|
||||
self.add(equation)
|
||||
self.play(LaggedStartMap(
|
||||
ShowCreationThenFadeAround, inputs,
|
||||
surrounding_rectangle_config={
|
||||
"buff": 0.05,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
))
|
||||
self.wait()
|
||||
|
||||
|
||||
class Show1DAnd3DEquations(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
d1_group = self.get_d1_group()
|
||||
d3_group = self.get_d3_group()
|
||||
d1_words, d1_equation = d1_group
|
||||
d3_words, d3_equation = d3_group
|
||||
|
||||
groups = VGroup(d1_group, d3_group)
|
||||
for group in groups:
|
||||
group.arrange(DOWN, buff=MED_LARGE_BUFF)
|
||||
groups.arrange(RIGHT, buff=1.5)
|
||||
groups.to_edge(UP)
|
||||
|
||||
d3_rhs = d3_equation[9:-2]
|
||||
d3_brace = Brace(d3_rhs, DOWN)
|
||||
nabla_words = TextMobject("Sometimes written as")
|
||||
nabla_words.match_width(d3_brace)
|
||||
nabla_words.next_to(d3_brace, DOWN)
|
||||
nabla_exp = TexMobject(
|
||||
"\\nabla^2 {T}",
|
||||
**self.tex_mobject_config,
|
||||
)
|
||||
nabla_exp.next_to(nabla_words, DOWN)
|
||||
# nabla_group = VGroup(nabla_words, nabla_exp)
|
||||
|
||||
d1_group.save_state()
|
||||
d1_group.center().to_edge(UP)
|
||||
|
||||
self.play(
|
||||
Write(d1_words),
|
||||
FadeInFrom(d1_equation, UP),
|
||||
run_time=1,
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
Restore(d1_group),
|
||||
FadeInFrom(d3_group, LEFT)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
GrowFromCenter(d3_brace),
|
||||
Write(nabla_words),
|
||||
TransformFromCopy(d3_rhs, nabla_exp),
|
||||
run_time=1,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class D1EquationNoInputs(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
equation = self.get_d1_equation_without_inputs()
|
||||
equation.to_edge(UP)
|
||||
# i1 = equation.index_of_part_by_tex("\\partial")
|
||||
# i2 = equation.index_of_part_by_tex("\\cdot")
|
||||
# equation[i1:i1 + 2].set_color(RED)
|
||||
# equation[i2 + 1:i2 + 6].set_color(RED)
|
||||
equation.set_color_by_tex("{T}", RED)
|
||||
self.add(equation)
|
||||
|
||||
|
||||
class AltHeatRHS(Scene):
|
||||
def construct(self):
|
||||
formula = TexMobject(
|
||||
"{\\alpha \\over 2}", "\\Big(",
|
||||
"T({x} - 1, {t}) + T({x} + 1, {t})"
|
||||
"\\Big)",
|
||||
tex_to_color_map={
|
||||
"{x}": GREEN,
|
||||
"{t}": YELLOW,
|
||||
}
|
||||
)
|
||||
self.add(formula)
|
||||
|
||||
|
||||
class CompareInputsOfGeneralCaseTo1D(WriteHeatEquation):
|
||||
def construct(self):
|
||||
three_d_expr, one_d_expr = [
|
||||
TexMobject(
|
||||
"{T}(" + inputs + ", {t})",
|
||||
**self.tex_mobject_config,
|
||||
)
|
||||
for inputs in ["{x}, {y}, {z}", "{x}"]
|
||||
]
|
||||
for expr in three_d_expr, one_d_expr:
|
||||
expr.scale(2)
|
||||
expr.to_edge(UP)
|
||||
|
||||
x, y, z = [
|
||||
three_d_expr.get_part_by_tex(letter)
|
||||
for letter in ["x", "y", "z"]
|
||||
]
|
||||
|
||||
self.play(FadeInFromDown(three_d_expr))
|
||||
self.play(LaggedStartMap(
|
||||
ShowCreationThenFadeAround,
|
||||
VGroup(x, y, z)
|
||||
))
|
||||
self.wait()
|
||||
low = 3
|
||||
high = -3
|
||||
self.play(
|
||||
ReplacementTransform(three_d_expr[:low], one_d_expr[:low]),
|
||||
ReplacementTransform(three_d_expr[high:], one_d_expr[high:]),
|
||||
three_d_expr[low:high].scale, 0,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ShowLaplacian(WriteHeatEquation):
|
||||
def construct(self):
|
||||
equation = self.get_d3_equation()
|
||||
equation.to_edge(UP, buff=MED_SMALL_BUFF)
|
||||
|
||||
parts = VGroup()
|
||||
plusses = VGroup()
|
||||
for char in "xyz":
|
||||
index = equation.index_of_part_by_tex(
|
||||
"{" + char + "}"
|
||||
)
|
||||
part = equation[index - 6:index + 3]
|
||||
rect = SurroundingRectangle(part)
|
||||
rect.match_color(equation[index])
|
||||
parts.add(part)
|
||||
part.rect = rect
|
||||
if char in "yz":
|
||||
plus = equation[index - 8]
|
||||
part.plus = plus
|
||||
plusses.add(plus)
|
||||
|
||||
lp = equation.get_part_by_tex("(")
|
||||
rp = equation.get_part_by_tex(")")
|
||||
|
||||
for part in parts:
|
||||
part.rp = rp.copy()
|
||||
part.rp.next_to(part, RIGHT, SMALL_BUFF)
|
||||
part.rp.align_to(lp, UP)
|
||||
rp.become(parts[0].rp)
|
||||
|
||||
# Show new second derivatives
|
||||
self.add(*equation)
|
||||
self.remove(*plusses, *parts[1], *parts[2])
|
||||
for part in parts[1:]:
|
||||
self.play(
|
||||
rp.become, part.rp,
|
||||
FadeInFrom(part, LEFT),
|
||||
Write(part.plus),
|
||||
ShowCreation(part.rect),
|
||||
)
|
||||
self.play(
|
||||
FadeOut(part.rect),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show laplacian
|
||||
brace = Brace(parts, DOWN)
|
||||
laplacian = TexMobject("\\nabla^2", "T")
|
||||
laplacian.next_to(brace, DOWN)
|
||||
laplacian_name = TextMobject(
|
||||
"``Laplacian''"
|
||||
)
|
||||
laplacian_name.next_to(laplacian, DOWN)
|
||||
|
||||
T_parts = VGroup(*[part[3] for part in parts])
|
||||
non_T_parts = VGroup(*[
|
||||
VGroup(*part[:3], *part[4:])
|
||||
for part in parts
|
||||
])
|
||||
|
||||
self.play(GrowFromCenter(brace))
|
||||
self.play(Write(laplacian_name))
|
||||
self.play(
|
||||
TransformFromCopy(non_T_parts, laplacian[0])
|
||||
)
|
||||
self.play(
|
||||
TransformFromCopy(T_parts, laplacian[1])
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class AskAboutActuallySolving(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
equation = self.get_d1_equation()
|
||||
equation.center()
|
||||
|
||||
q1 = TextMobject("Solve for T?")
|
||||
q1.next_to(equation, UP, LARGE_BUFF)
|
||||
q2 = TextMobject("What does it \\emph{mean} to solve this?")
|
||||
q2.next_to(equation, UP, LARGE_BUFF)
|
||||
formula = TexMobject(
|
||||
"T({x}, {t}) = \\sin\\big(a{x}\\big) e^{-\\alpha \\cdot a^2 {t}}",
|
||||
tex_to_color_map={
|
||||
"{x}": GREEN,
|
||||
"{t}": YELLOW,
|
||||
}
|
||||
)
|
||||
formula.next_to(equation, DOWN, LARGE_BUFF)
|
||||
q3 = TextMobject("Is this it?")
|
||||
arrow = Vector(LEFT, color=WHITE)
|
||||
arrow.next_to(formula, RIGHT)
|
||||
q3.next_to(arrow, RIGHT)
|
||||
|
||||
self.add(equation)
|
||||
self.play(FadeInFromDown(q1))
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeInFromDown(q2),
|
||||
q1.shift, 1.5 * UP,
|
||||
)
|
||||
self.play(FadeInFrom(formula, UP))
|
||||
self.play(
|
||||
GrowArrow(arrow),
|
||||
FadeInFrom(q3, LEFT)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class PDEPatreonEndscreen(PatreonEndScreen):
|
||||
CONFIG = {
|
||||
"specific_patrons": [
|
||||
"Juan Benet",
|
||||
"Vassili Philippov",
|
||||
"Burt Humburg",
|
||||
"Matt Russell",
|
||||
"Scott Gray",
|
||||
"soekul",
|
||||
"Tihan Seale",
|
||||
"Richard Barthel",
|
||||
"Ali Yahya",
|
||||
"dave nicponski",
|
||||
"Evan Phillips",
|
||||
"Graham",
|
||||
"Joseph Kelly",
|
||||
"Kaustuv DeBiswas",
|
||||
"LambdaLabs",
|
||||
"Lukas Biewald",
|
||||
"Mike Coleman",
|
||||
"Peter Mcinerney",
|
||||
"Quantopian",
|
||||
"Roy Larson",
|
||||
"Scott Walter, Ph.D.",
|
||||
"Yana Chernobilsky",
|
||||
"Yu Jun",
|
||||
"Jordan Scales",
|
||||
"D. Sivakumar",
|
||||
"Lukas -krtek.net- Novy",
|
||||
"John Shaughnessy",
|
||||
"Britt Selvitelle",
|
||||
"David Gow",
|
||||
"J",
|
||||
"Jonathan Wilson",
|
||||
"Joseph John Cox",
|
||||
"Magnus Dahlström",
|
||||
"Randy C. Will",
|
||||
"Ryan Atallah",
|
||||
"Luc Ritchie",
|
||||
"1stViewMaths",
|
||||
"Adrian Robinson",
|
||||
"Alexis Olson",
|
||||
"Andreas Benjamin Brössel",
|
||||
"Andrew Busey",
|
||||
"Ankalagon",
|
||||
"Antoine Bruguier",
|
||||
"Antonio Juarez",
|
||||
"Arjun Chakroborty",
|
||||
"Art Ianuzzi",
|
||||
"Awoo",
|
||||
"Bernd Sing",
|
||||
"Boris Veselinovich",
|
||||
"Brian Staroselsky",
|
||||
"Chad Hurst",
|
||||
"Charles Southerland",
|
||||
"Chris Connett",
|
||||
"Christian Kaiser",
|
||||
"Clark Gaebel",
|
||||
"Cooper Jones",
|
||||
"Danger Dai",
|
||||
"Dave B",
|
||||
"Dave Kester",
|
||||
"David B. Hill",
|
||||
"David Clark",
|
||||
"DeathByShrimp",
|
||||
"Delton Ding",
|
||||
"eaglle",
|
||||
"emptymachine",
|
||||
"Eric Younge",
|
||||
"Eryq Ouithaqueue",
|
||||
"Federico Lebron",
|
||||
"Giovanni Filippi",
|
||||
"Hal Hildebrand",
|
||||
"Hitoshi Yamauchi",
|
||||
"Isaac Jeffrey Lee",
|
||||
"j eduardo perez",
|
||||
"Jacob Magnuson",
|
||||
"Jameel Syed",
|
||||
"Jason Hise",
|
||||
"Jeff Linse",
|
||||
"Jeff Straathof",
|
||||
"John Griffith",
|
||||
"John Haley",
|
||||
"John V Wertheim",
|
||||
"Jonathan Eppele",
|
||||
"Kai-Siang Ang",
|
||||
"Kanan Gill",
|
||||
"L0j1k",
|
||||
"Lee Beck",
|
||||
"Lee Redden",
|
||||
"Linh Tran",
|
||||
"Ludwig Schubert",
|
||||
"Magister Mugit",
|
||||
"Mark B Bahu",
|
||||
"Mark Heising",
|
||||
"Martin Price",
|
||||
"Mathias Jansson",
|
||||
"Matt Langford",
|
||||
"Matt Roveto",
|
||||
"Matthew Bouchard",
|
||||
"Matthew Cocke",
|
||||
"Michael Faust",
|
||||
"Michael Hardel",
|
||||
"Mirik Gogri",
|
||||
"Mustafa Mahdi",
|
||||
"Márton Vaitkus",
|
||||
"Nero Li",
|
||||
"Nikita Lesnikov",
|
||||
"Omar Zrien",
|
||||
"Owen Campbell-Moore",
|
||||
"Peter Ehrnstrom",
|
||||
"RedAgent14",
|
||||
"rehmi post",
|
||||
"Richard Burgmann",
|
||||
"Richard Comish",
|
||||
"Ripta Pasay",
|
||||
"Rish Kundalia",
|
||||
"Robert Teed",
|
||||
"Roobie",
|
||||
"Ryan Williams",
|
||||
"Sachit Nagpal",
|
||||
"Solara570",
|
||||
"Stevie Metke",
|
||||
"Tal Einav",
|
||||
"Ted Suzman",
|
||||
"Thomas Tarler",
|
||||
"Tom Fleming",
|
||||
"Valeriy Skobelev",
|
||||
"Xavier Bernard",
|
||||
"Yavor Ivanov",
|
||||
"Yaw Etse",
|
||||
"YinYangBalance.Asia",
|
||||
"Zach Cardwell",
|
||||
],
|
||||
}
|
|
@ -1,303 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
from active_projects.diffyq.part2.heat_equation import *
|
||||
|
||||
|
||||
class ShowNewRuleAtDiscreteBoundary(DiscreteSetup):
|
||||
CONFIG = {
|
||||
"axes_config": {
|
||||
"x_min": 0,
|
||||
"stroke_width": 1,
|
||||
"x_axis_config": {
|
||||
"include_tip": False,
|
||||
},
|
||||
},
|
||||
"freq_amplitude_pairs": [
|
||||
(1, 0.5),
|
||||
(2, 1),
|
||||
(3, 0.5),
|
||||
(4, 0.3),
|
||||
],
|
||||
"v_line_class": DashedLine,
|
||||
"v_line_config": {
|
||||
|
||||
},
|
||||
"step_size": 1,
|
||||
"wait_time": 15,
|
||||
"alpha": 0.25,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_axes()
|
||||
self.set_points()
|
||||
self.show_boundary_point_influenced_by_neighbor()
|
||||
self.add_clock()
|
||||
self.let_evolve()
|
||||
|
||||
def set_points(self):
|
||||
axes = self.axes
|
||||
for mob in axes.family_members_with_points():
|
||||
if isinstance(mob, Line):
|
||||
mob.set_stroke(width=1)
|
||||
|
||||
step_size = self.step_size
|
||||
xs = np.arange(
|
||||
axes.x_min,
|
||||
axes.x_max + step_size,
|
||||
step_size
|
||||
)
|
||||
|
||||
dots = self.dots = self.get_dots(axes, xs)
|
||||
self.v_lines = self.get_v_lines(dots)
|
||||
self.rod_pieces = self.get_rod_pieces(dots)
|
||||
|
||||
# rod_pieces
|
||||
|
||||
self.add(self.dots)
|
||||
self.add(self.v_lines)
|
||||
self.add(self.rod_pieces)
|
||||
|
||||
def show_boundary_point_influenced_by_neighbor(self):
|
||||
dots = self.dots
|
||||
ld = dots[0]
|
||||
ld_in = dots[1]
|
||||
rd = dots[-1]
|
||||
rd_in = dots[-2]
|
||||
v_len = 0.75
|
||||
l_arrow = Vector(v_len * LEFT)
|
||||
l_arrow.move_to(ld.get_left(), RIGHT)
|
||||
r_arrow = Vector(v_len * RIGHT)
|
||||
r_arrow.move_to(rd.get_right(), LEFT)
|
||||
arrows = VGroup(l_arrow, r_arrow)
|
||||
q_marks = VGroup(*[
|
||||
TexMobject("?").scale(1.5).next_to(
|
||||
arrow, arrow.get_vector()
|
||||
)
|
||||
for arrow in arrows
|
||||
])
|
||||
|
||||
arrows.set_color(YELLOW)
|
||||
q_marks.set_color(YELLOW)
|
||||
|
||||
blocking_rects = VGroup(*[
|
||||
BackgroundRectangle(VGroup(
|
||||
*dots[i:-i],
|
||||
*self.rod_pieces[i:-i]
|
||||
))
|
||||
for i in [1, 2]
|
||||
])
|
||||
for rect in blocking_rects:
|
||||
rect.stretch(1.1, dim=1, about_edge=UP)
|
||||
|
||||
self.play(FadeIn(blocking_rects[0]))
|
||||
self.play(
|
||||
LaggedStartMap(ShowCreation, arrows),
|
||||
LaggedStart(*[
|
||||
FadeInFrom(q_mark, -arrow.get_vector())
|
||||
for q_mark, arrow in zip(q_marks, arrows)
|
||||
]),
|
||||
run_time=1.5
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Point to inward neighbor
|
||||
new_arrows = VGroup(*[
|
||||
Arrow(
|
||||
d1.get_center(),
|
||||
VGroup(d1, d2).get_center(),
|
||||
buff=0,
|
||||
).match_style(l_arrow)
|
||||
for d1, d2 in [(ld, ld_in), (rd, rd_in)]
|
||||
])
|
||||
new_arrows.match_style(arrows)
|
||||
|
||||
l_brace = Brace(VGroup(ld, ld_in), DOWN)
|
||||
r_brace = Brace(VGroup(rd, rd_in), DOWN)
|
||||
braces = VGroup(l_brace, r_brace)
|
||||
for brace in braces:
|
||||
brace.align_to(
|
||||
self.axes.x_axis.get_center(), UP
|
||||
)
|
||||
brace.shift(SMALL_BUFF * DOWN)
|
||||
brace.add(brace.get_tex("\\Delta x"))
|
||||
|
||||
self.play(
|
||||
ReplacementTransform(arrows, new_arrows),
|
||||
FadeOut(q_marks),
|
||||
ReplacementTransform(*blocking_rects)
|
||||
)
|
||||
self.wait()
|
||||
self.play(FadeInFrom(braces, UP))
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeOut(new_arrows),
|
||||
FadeOut(blocking_rects[1]),
|
||||
FadeOut(braces),
|
||||
)
|
||||
|
||||
def add_clock(self):
|
||||
super().add_clock()
|
||||
self.time_label.add_updater(
|
||||
lambda d, dt: d.increment_value(dt)
|
||||
)
|
||||
VGroup(
|
||||
self.clock,
|
||||
self.time_label
|
||||
).shift(2 * LEFT)
|
||||
|
||||
def let_evolve(self):
|
||||
dots = self.dots
|
||||
dots.add_updater(self.update_dots)
|
||||
|
||||
wait_time = self.wait_time
|
||||
self.play(
|
||||
ClockPassesTime(
|
||||
self.clock,
|
||||
run_time=wait_time,
|
||||
hours_passed=wait_time,
|
||||
),
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
def get_dots(self, axes, xs):
|
||||
dots = VGroup(*[
|
||||
Dot(axes.c2p(x, self.temp_func(x, 0)))
|
||||
for x in xs
|
||||
])
|
||||
|
||||
max_width = 0.8 * self.step_size
|
||||
for dot in dots:
|
||||
dot.add_updater(self.update_dot_color)
|
||||
if dot.get_width() > max_width:
|
||||
dot.set_width(max_width)
|
||||
|
||||
return dots
|
||||
|
||||
def get_v_lines(self, dots):
|
||||
return always_redraw(lambda: VGroup(*[
|
||||
self.get_v_line(dot)
|
||||
for dot in dots
|
||||
]))
|
||||
|
||||
def get_v_line(self, dot):
|
||||
x_axis = self.axes.x_axis
|
||||
bottom = dot.get_bottom()
|
||||
x = x_axis.p2n(bottom)
|
||||
proj_point = x_axis.n2p(x)
|
||||
return self.v_line_class(
|
||||
proj_point, bottom,
|
||||
**self.v_line_config,
|
||||
)
|
||||
|
||||
def get_rod_pieces(self, dots):
|
||||
axis = self.axes.x_axis
|
||||
factor = 1 - np.exp(-(0.8 / self.step_size)**2)
|
||||
width = factor * self.step_size
|
||||
|
||||
pieces = VGroup()
|
||||
for dot in dots:
|
||||
piece = Line(ORIGIN, width * RIGHT)
|
||||
piece.set_stroke(width=5)
|
||||
piece.move_to(dot)
|
||||
piece.set_y(axis.get_center()[1])
|
||||
piece.dot = dot
|
||||
piece.add_updater(
|
||||
lambda p: p.match_color(p.dot)
|
||||
)
|
||||
pieces.add(piece)
|
||||
return pieces
|
||||
|
||||
def update_dot_color(self, dot):
|
||||
y = self.axes.y_axis.p2n(dot.get_center())
|
||||
dot.set_color(self.y_to_color(y))
|
||||
|
||||
def update_dots(self, dots, dt):
|
||||
for ds in zip(dots, dots[1:], dots[2:]):
|
||||
points = [d.get_center() for d in ds]
|
||||
x0, x1, x2 = [p[0] for p in points]
|
||||
dx = x1 - x0
|
||||
y0, y1, y2 = [p[1] for p in points]
|
||||
|
||||
self.update_dot(
|
||||
dot=ds[1],
|
||||
dt=dt,
|
||||
mean_diff=0.5 * (y2 - 2 * y1 + y0) / dx
|
||||
)
|
||||
if ds[0] is dots[0]:
|
||||
self.update_dot(
|
||||
dot=ds[0],
|
||||
dt=dt,
|
||||
mean_diff=(y1 - y0) / dx
|
||||
)
|
||||
elif ds[-1] is dots[-1]:
|
||||
self.update_dot(
|
||||
dot=ds[-1],
|
||||
dt=dt,
|
||||
mean_diff=(y1 - y2) / dx
|
||||
)
|
||||
|
||||
def update_dot(self, dot, dt, mean_diff):
|
||||
dot.shift(mean_diff * self.alpha * dt * UP)
|
||||
|
||||
|
||||
class DiscreteEvolutionPoint25(ShowNewRuleAtDiscreteBoundary):
|
||||
CONFIG = {
|
||||
"step_size": 0.25,
|
||||
"alpha": 0.5,
|
||||
"wait_time": 30,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_axes()
|
||||
self.set_points()
|
||||
self.add_clock()
|
||||
self.let_evolve()
|
||||
|
||||
|
||||
class DiscreteEvolutionPoint1(DiscreteEvolutionPoint25):
|
||||
CONFIG = {
|
||||
"step_size": 0.1,
|
||||
"v_line_config": {
|
||||
"stroke_width": 1,
|
||||
},
|
||||
"wait_time": 30,
|
||||
}
|
||||
|
||||
|
||||
class FlatEdgesForDiscreteEvolution(DiscreteEvolutionPoint1):
|
||||
CONFIG = {
|
||||
"wait_time": 20,
|
||||
"step_size": 0.1,
|
||||
}
|
||||
|
||||
def let_evolve(self):
|
||||
lines = VGroup(*[
|
||||
Line(LEFT, RIGHT)
|
||||
for x in range(2)
|
||||
])
|
||||
lines.set_width(1.5)
|
||||
lines.set_stroke(WHITE, 5, opacity=0.5)
|
||||
lines.add_updater(self.update_lines)
|
||||
|
||||
turn_animation_into_updater(
|
||||
ShowCreation(lines, run_time=2)
|
||||
)
|
||||
self.add(lines)
|
||||
|
||||
super().let_evolve()
|
||||
|
||||
def update_lines(self, lines):
|
||||
dots = self.dots
|
||||
for line, dot in zip(lines, [dots[0], dots[-1]]):
|
||||
line.move_to(dot)
|
||||
|
||||
|
||||
class FlatEdgesForDiscreteEvolutionTinySteps(FlatEdgesForDiscreteEvolution):
|
||||
CONFIG = {
|
||||
"step_size": 0.025,
|
||||
"wait_time": 10,
|
||||
"v_line_class": Line,
|
||||
"v_line_config": {
|
||||
"stroke_opacity": 0.5,
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
from manimlib.imports import *
|
||||
from active_projects.diffyq.part2.wordy_scenes import *
|
||||
|
||||
|
||||
class IveHeardOfThis(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
point = VectorizedPoint()
|
||||
point.move_to(3 * RIGHT + 2 * UP)
|
||||
self.student_says(
|
||||
"I've heard\\\\", "of this!",
|
||||
student_index=1,
|
||||
target_mode="hooray",
|
||||
bubble_kwargs={
|
||||
"height": 3,
|
||||
"width": 3,
|
||||
"direction": RIGHT,
|
||||
},
|
||||
run_time=1,
|
||||
)
|
||||
self.change_student_modes(
|
||||
"thinking", "hooray", "thinking",
|
||||
look_at_arg=point,
|
||||
added_anims=[self.teacher.change, "happy"]
|
||||
)
|
||||
self.wait(3)
|
||||
self.student_says(
|
||||
"But who\\\\", "cares?",
|
||||
student_index=1,
|
||||
target_mode="maybe",
|
||||
bubble_kwargs={
|
||||
"direction": RIGHT,
|
||||
"width": 3,
|
||||
"height": 3,
|
||||
},
|
||||
run_time=1,
|
||||
)
|
||||
self.change_student_modes(
|
||||
"pondering", "maybe", "pondering",
|
||||
look_at_arg=point,
|
||||
added_anims=[self.teacher.change, "guilty"]
|
||||
)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class InFouriersShoes(PiCreatureScene, WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
randy = self.pi_creature
|
||||
fourier = ImageMobject("Joseph Fourier")
|
||||
fourier.set_height(4)
|
||||
fourier.next_to(randy, RIGHT, LARGE_BUFF)
|
||||
fourier.align_to(randy, DOWN)
|
||||
|
||||
equation = self.get_d1_equation()
|
||||
equation.next_to(fourier, UP, MED_LARGE_BUFF)
|
||||
|
||||
decades = list(range(1740, 2040, 20))
|
||||
time_line = NumberLine(
|
||||
x_min=decades[0],
|
||||
x_max=decades[-1],
|
||||
tick_frequency=1,
|
||||
tick_size=0.05,
|
||||
longer_tick_multiple=4,
|
||||
unit_size=0.2,
|
||||
numbers_with_elongated_ticks=decades,
|
||||
numbers_to_show=decades,
|
||||
decimal_number_config={
|
||||
"group_with_commas": False,
|
||||
},
|
||||
stroke_width=2,
|
||||
)
|
||||
time_line.add_numbers()
|
||||
time_line.move_to(ORIGIN, RIGHT)
|
||||
time_line.to_edge(UP)
|
||||
triangle = ArrowTip(start_angle=-90 * DEGREES)
|
||||
triangle.set_height(0.25)
|
||||
triangle.move_to(time_line.n2p(2019), DOWN)
|
||||
triangle.set_color(WHITE)
|
||||
|
||||
self.play(FadeInFrom(fourier, 2 * LEFT))
|
||||
self.play(randy.change, "pondering")
|
||||
self.wait()
|
||||
self.play(
|
||||
DrawBorderThenFill(triangle, run_time=1),
|
||||
FadeInFromDown(equation),
|
||||
FadeIn(time_line),
|
||||
)
|
||||
self.play(
|
||||
Animation(triangle),
|
||||
ApplyMethod(
|
||||
time_line.shift,
|
||||
time_line.n2p(2019) - time_line.n2p(1822),
|
||||
run_time=5
|
||||
),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class SineCurveIsUnrealistic(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.student_says(
|
||||
"But that would\\\\never happen!",
|
||||
student_index=1,
|
||||
bubble_kwargs={
|
||||
"direction": RIGHT,
|
||||
"height": 3,
|
||||
"width": 4,
|
||||
},
|
||||
target_mode="angry"
|
||||
)
|
||||
self.change_student_modes(
|
||||
"guilty", "angry", "hesitant",
|
||||
added_anims=[
|
||||
self.teacher.change, "tease"
|
||||
]
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
RemovePiCreatureBubble(self.students[1]),
|
||||
self.teacher.change, "raise_right_hand"
|
||||
)
|
||||
self.change_all_student_modes(
|
||||
"pondering",
|
||||
look_at_arg=3 * UP,
|
||||
)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class IfOnly(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.teacher_says(
|
||||
"If only!",
|
||||
target_mode="angry"
|
||||
)
|
||||
self.change_all_student_modes(
|
||||
"confused",
|
||||
look_at_arg=self.screen
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class SoWeGotNowhere(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.student_says(
|
||||
"So we've gotten\\\\nowhere!",
|
||||
target_mode="angry",
|
||||
added_anims=[
|
||||
self.teacher.change, "guilty"
|
||||
]
|
||||
)
|
||||
self.change_all_student_modes("angry")
|
||||
self.wait()
|
||||
text = TexMobject(
|
||||
"&\\text{Actually,}\\\\",
|
||||
"&\\sin\\left({x}\\right)"
|
||||
"e^{-\\alpha {t}}\\\\",
|
||||
"&\\text{isn't far off.}",
|
||||
tex_to_color_map={
|
||||
"{x}": GREEN,
|
||||
"{t}": YELLOW,
|
||||
}
|
||||
)
|
||||
text.scale(0.8)
|
||||
self.teacher_says(
|
||||
text,
|
||||
content_introduction_class=FadeIn,
|
||||
bubble_kwargs={
|
||||
"width": 4,
|
||||
"height": 3.5,
|
||||
}
|
||||
)
|
||||
self.change_all_student_modes(
|
||||
"pondering",
|
||||
look_at_arg=self.screen
|
||||
)
|
||||
self.wait(3)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue