mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
Merge pull request #270 from 3b1b/simple-improvements
Simple improvements
This commit is contained in:
commit
c8900492fd
9 changed files with 89 additions and 39 deletions
|
@ -27,6 +27,8 @@ pip install -r requirements.txt
|
|||
```
|
||||
|
||||
## How to Use
|
||||
Todd Zimmerman put together a [very nice tutorial](https://talkingphysics.wordpress.com/2018/06/11/learning-how-to-animate-videos-using-manim-series-a-journey/) on getting started with manim. I can't make promises that future versions will always be compatible with what is discussed in that tutorial, but he certainly does a much better job than I have laying out the basics.
|
||||
|
||||
Try running the following:
|
||||
```sh
|
||||
python extract_scene.py example_scenes.py SquareToCircle -pl
|
||||
|
|
|
@ -79,6 +79,13 @@ class ReplacementTransform(Transform):
|
|||
}
|
||||
|
||||
|
||||
class TransformFromCopy(ReplacementTransform):
|
||||
def __init__(self, mobject, target_mobject, **kwargs):
|
||||
ReplacementTransform.__init__(
|
||||
self, mobject.deepcopy(), target_mobject, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class ClockwiseTransform(Transform):
|
||||
CONFIG = {
|
||||
"path_arc": -np.pi
|
||||
|
|
|
@ -25,6 +25,7 @@ class ThreeDCamera(Camera):
|
|||
"gamma": 0, # Rotation about normal vector to camera
|
||||
"light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
|
||||
"frame_center": ORIGIN,
|
||||
"should_apply_shading": True,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -50,6 +51,8 @@ class ThreeDCamera(Camera):
|
|||
]
|
||||
|
||||
def modified_rgbas(self, vmobject, rgbas):
|
||||
if not self.should_apply_shading:
|
||||
return rgbas
|
||||
is_3d = isinstance(vmobject, ThreeDVMobject)
|
||||
has_points = (vmobject.get_num_points() > 0)
|
||||
if is_3d and has_points:
|
||||
|
@ -157,7 +160,8 @@ class ThreeDCamera(Camera):
|
|||
rot_matrix = self.get_rotation_matrix()
|
||||
points = np.dot(points, rot_matrix.T)
|
||||
zs = points[:, 2]
|
||||
points[:, 0] *= (distance + zs) / distance
|
||||
points[:, 1] *= (distance + zs) / distance
|
||||
zs[zs >= distance] = distance - 0.001
|
||||
for i in 0, 1:
|
||||
points[:, i] *= distance / (distance - zs)
|
||||
points += fc
|
||||
return points
|
||||
|
|
|
@ -707,9 +707,9 @@ class Mobject(Container):
|
|||
all_points = self.get_all_points()
|
||||
for dim in range(self.dim):
|
||||
if direction[dim] <= 0:
|
||||
min_val = np.min(all_points[:, dim])
|
||||
min_val = min(all_points[:, dim])
|
||||
if direction[dim] >= 0:
|
||||
max_val = np.max(all_points[:, dim])
|
||||
max_val = max(all_points[:, dim])
|
||||
|
||||
if direction[dim] == 0:
|
||||
result[dim] = (max_val + min_val) / 2
|
||||
|
|
|
@ -7,6 +7,7 @@ from mobject.types.vectorized_mobject import VGroup
|
|||
from mobject.geometry import Square
|
||||
|
||||
from utils.config_ops import digest_config
|
||||
from utils.iterables import tuplify
|
||||
from utils.space_ops import z_to_vector
|
||||
from utils.space_ops import get_unit_normal
|
||||
|
||||
|
@ -75,8 +76,6 @@ class ParametricSurface(VGroup):
|
|||
"v_min": 0,
|
||||
"v_max": 1,
|
||||
"resolution": 32,
|
||||
"u_resolution": None,
|
||||
"v_resolution": None,
|
||||
"surface_piece_config": {},
|
||||
"fill_color": BLUE_D,
|
||||
"fill_opacity": 1.0,
|
||||
|
@ -94,27 +93,34 @@ class ParametricSurface(VGroup):
|
|||
self.make_jagged()
|
||||
|
||||
def setup_in_uv_space(self):
|
||||
res = tuplify(self.resolution)
|
||||
if len(res) == 1:
|
||||
u_res = v_res = res
|
||||
else:
|
||||
u_res, v_res = res
|
||||
u_min = self.u_min
|
||||
u_max = self.u_max
|
||||
u_res = self.u_resolution or self.resolution
|
||||
v_min = self.v_min
|
||||
v_max = self.v_max
|
||||
v_res = self.v_resolution or self.resolution
|
||||
|
||||
u_values = np.linspace(u_min, u_max, u_res + 1)
|
||||
v_values = np.linspace(v_min, v_max, v_res + 1)
|
||||
faces = VGroup()
|
||||
for u1, u2 in zip(u_values[:-1], u_values[1:]):
|
||||
for v1, v2 in zip(v_values[:-1], v_values[1:]):
|
||||
piece = ThreeDVMobject()
|
||||
piece.set_points_as_corners([
|
||||
for i in range(u_res):
|
||||
for j in range(v_res):
|
||||
u1, u2 = u_values[i:i + 2]
|
||||
v1, v2 = v_values[j:j + 2]
|
||||
face = ThreeDVMobject()
|
||||
face.set_points_as_corners([
|
||||
[u1, v1, 0],
|
||||
[u2, v1, 0],
|
||||
[u2, v2, 0],
|
||||
[u1, v2, 0],
|
||||
[u1, v1, 0],
|
||||
])
|
||||
faces.add(piece)
|
||||
faces.add(face)
|
||||
face.u_index = i
|
||||
face.v_index = j
|
||||
faces.set_fill(
|
||||
color=self.fill_color,
|
||||
opacity=self.fill_opacity
|
||||
|
@ -128,16 +134,11 @@ class ParametricSurface(VGroup):
|
|||
if self.checkerboard_colors:
|
||||
self.set_fill_by_checkerboard(*self.checkerboard_colors)
|
||||
|
||||
def set_fill_by_checkerboard(self, color1, color2):
|
||||
u_res = self.u_resolution or self.resolution
|
||||
v_res = self.v_resolution or self.resolution
|
||||
for i in range(u_res):
|
||||
for j in range(v_res):
|
||||
face = self[i * v_res + j]
|
||||
if (i + j) % 2 == 0:
|
||||
face.set_fill(color1)
|
||||
else:
|
||||
face.set_fill(color2)
|
||||
def set_fill_by_checkerboard(self, *colors, opacity=None):
|
||||
n_colors = len(colors)
|
||||
for face in self:
|
||||
c_index = (face.u_index + face.v_index) % n_colors
|
||||
face.set_fill(colors[c_index], opacity=opacity)
|
||||
|
||||
|
||||
# Specific shapes
|
||||
|
@ -145,15 +146,15 @@ class ParametricSurface(VGroup):
|
|||
|
||||
class Sphere(ParametricSurface):
|
||||
CONFIG = {
|
||||
"resolution": 12,
|
||||
"resolution": (12, 24),
|
||||
"radius": 3,
|
||||
"u_min": 0.001,
|
||||
"u_max": PI - 0.001,
|
||||
"v_min": 0,
|
||||
"v_max": TAU,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
kwargs["u_resolution"] = self.u_resolution or self.resolution
|
||||
kwargs["v_resolution"] = self.u_resolution or 2 * self.resolution
|
||||
ParametricSurface.__init__(
|
||||
self, self.func, **kwargs
|
||||
)
|
||||
|
@ -161,9 +162,9 @@ class Sphere(ParametricSurface):
|
|||
|
||||
def func(self, u, v):
|
||||
return np.array([
|
||||
np.cos(TAU * v) * np.sin(PI * u),
|
||||
np.sin(TAU * v) * np.sin(PI * u),
|
||||
np.cos(PI * u)
|
||||
np.cos(v) * np.sin(u),
|
||||
np.sin(v) * np.sin(u),
|
||||
np.cos(u)
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ from utils.color import color_to_rgba
|
|||
from utils.iterables import make_even
|
||||
from utils.iterables import tuplify
|
||||
from utils.iterables import stretch_array_to_length
|
||||
from utils.simple_functions import clip_in_place
|
||||
|
||||
|
||||
class VMobject(Mobject):
|
||||
|
@ -93,7 +94,7 @@ class VMobject(Mobject):
|
|||
if sheen != 0 and len(rgbas) == 1:
|
||||
light_rgbas = np.array(rgbas)
|
||||
light_rgbas[:, :3] += sheen
|
||||
light_rgbas = np.clip(light_rgbas, 0, 1)
|
||||
clip_in_place(light_rgbas, 0, 1)
|
||||
rgbas = np.append(rgbas, light_rgbas, axis=0)
|
||||
return rgbas
|
||||
|
||||
|
@ -183,7 +184,7 @@ class VMobject(Mobject):
|
|||
|
||||
def get_fill_rgbas(self):
|
||||
try:
|
||||
return np.clip(self.fill_rgbas, 0, 1)
|
||||
return self.fill_rgbas
|
||||
except AttributeError:
|
||||
return np.zeros((1, 4))
|
||||
|
||||
|
@ -216,7 +217,7 @@ class VMobject(Mobject):
|
|||
rgbas = self.background_stroke_rgbas
|
||||
else:
|
||||
rgbas = self.stroke_rgbas
|
||||
return np.clip(rgbas, 0, 1)
|
||||
return rgbas
|
||||
except AttributeError:
|
||||
return np.zeros((1, 4))
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import numpy as np
|
||||
|
||||
from scipy import linalg
|
||||
from utils.simple_functions import choose
|
||||
from utils.simple_functions import choose_using_cache
|
||||
from utils.space_ops import get_norm
|
||||
|
||||
CLOSED_THRESHOLD = 0.001
|
||||
|
@ -10,7 +10,7 @@ CLOSED_THRESHOLD = 0.001
|
|||
def bezier(points):
|
||||
n = len(points) - 1
|
||||
return lambda t: sum([
|
||||
((1 - t)**(n - k)) * (t**k) * choose(n, k) * point
|
||||
((1 - t)**(n - k)) * (t**k) * choose_using_cache(n, k) * point
|
||||
for k, point in enumerate(points)
|
||||
])
|
||||
|
||||
|
|
|
@ -7,12 +7,16 @@ from constants import PALETTE
|
|||
|
||||
from utils.bezier import interpolate
|
||||
from utils.space_ops import normalize
|
||||
from utils.simple_functions import clip_in_place
|
||||
|
||||
|
||||
def color_to_rgb(color):
|
||||
if not isinstance(color, Color):
|
||||
color = Color(color)
|
||||
return np.array(color.get_rgb())
|
||||
if isinstance(color, str):
|
||||
return hex_to_rgb(color)
|
||||
elif isinstance(color, Color):
|
||||
return np.array(color.get_rgb())
|
||||
else:
|
||||
raise Exception("Invalid color type")
|
||||
|
||||
|
||||
def color_to_rgba(color, alpha=1):
|
||||
|
@ -34,6 +38,16 @@ def rgb_to_hex(rgb):
|
|||
return "#" + "".join('%02x' % int(255 * x) for x in rgb)
|
||||
|
||||
|
||||
def hex_to_rgb(hex_code):
|
||||
hex_part = hex_code[1:]
|
||||
if len(hex_part) == 3:
|
||||
"".join([2 * c for c in hex_part])
|
||||
return np.array([
|
||||
int(hex_part[i:i + 2], 16) / 255
|
||||
for i in range(0, 6, 2)
|
||||
])
|
||||
|
||||
|
||||
def invert_color(color):
|
||||
return rgb_to_color(1.0 - color_to_rgb(color))
|
||||
|
||||
|
@ -92,4 +106,6 @@ def get_shaded_rgb(rgb, point, unit_normal_vect, light_source):
|
|||
factor = 0.5 * np.dot(unit_normal_vect, to_sun)**3
|
||||
if factor < 0:
|
||||
factor *= 0.5
|
||||
return np.clip(rgb + factor, 0, 1)
|
||||
result = rgb + factor
|
||||
clip_in_place(rgb + factor, 0, 1)
|
||||
return result
|
||||
|
|
|
@ -8,6 +8,17 @@ def sigmoid(x):
|
|||
return 1.0 / (1 + np.exp(-x))
|
||||
|
||||
|
||||
CHOOSE_CACHE = {}
|
||||
|
||||
|
||||
def choose_using_cache(n, r):
|
||||
if n not in CHOOSE_CACHE:
|
||||
CHOOSE_CACHE[n] = {}
|
||||
if r not in CHOOSE_CACHE[n]:
|
||||
CHOOSE_CACHE[n][r] = choose(n, r)
|
||||
return CHOOSE_CACHE[n][r]
|
||||
|
||||
|
||||
def choose(n, r):
|
||||
if n < r:
|
||||
return 0
|
||||
|
@ -28,6 +39,14 @@ def get_num_args(function):
|
|||
# but for now, we just allow the option to handle indeterminate 0/0.
|
||||
|
||||
|
||||
def clip_in_place(array, min_val=None, max_val=None):
|
||||
if max_val is not None:
|
||||
array[array > max_val] = max_val
|
||||
if min_val is not None:
|
||||
array[array < min_val] = min_val
|
||||
return array
|
||||
|
||||
|
||||
def fdiv(a, b, zero_over_zero_value=None):
|
||||
if zero_over_zero_value is not None:
|
||||
out = np.full_like(a, zero_over_zero_value)
|
||||
|
|
Loading…
Add table
Reference in a new issue