mirror of
https://github.com/3b1b/manim.git
synced 2025-08-21 05:44:04 +00:00
commit
2c110790d2
31 changed files with 414 additions and 172 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -91,6 +91,7 @@ ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
pyrightconfig.json
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
|
|
@ -74,6 +74,7 @@ class Animation(object):
|
||||||
# It is, however, okay and desirable to call
|
# It is, however, okay and desirable to call
|
||||||
# the internal updaters of self.starting_mobject,
|
# the internal updaters of self.starting_mobject,
|
||||||
# or any others among self.get_all_mobjects()
|
# or any others among self.get_all_mobjects()
|
||||||
|
self.mobject_was_updating = not self.mobject.updating_suspended
|
||||||
self.mobject.suspend_updating()
|
self.mobject.suspend_updating()
|
||||||
self.families = list(self.get_all_families_zipped())
|
self.families = list(self.get_all_families_zipped())
|
||||||
self.interpolate(0)
|
self.interpolate(0)
|
||||||
|
@ -81,7 +82,7 @@ class Animation(object):
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
self.interpolate(self.final_alpha_value)
|
self.interpolate(self.final_alpha_value)
|
||||||
self.mobject.set_animating_status(False)
|
self.mobject.set_animating_status(False)
|
||||||
if self.suspend_mobject_updating:
|
if self.suspend_mobject_updating and self.mobject_was_updating:
|
||||||
self.mobject.resume_updating()
|
self.mobject.resume_updating()
|
||||||
|
|
||||||
def clean_up_from_scene(self, scene: Scene) -> None:
|
def clean_up_from_scene(self, scene: Scene) -> None:
|
||||||
|
|
|
@ -167,7 +167,6 @@ class LaggedStartMap(LaggedStart):
|
||||||
self,
|
self,
|
||||||
anim_func: Callable[[Mobject], Animation],
|
anim_func: Callable[[Mobject], Animation],
|
||||||
group: Mobject,
|
group: Mobject,
|
||||||
arg_creator: Callable[[Mobject], tuple] | None = None,
|
|
||||||
run_time: float = 2.0,
|
run_time: float = 2.0,
|
||||||
lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,
|
lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,
|
||||||
**kwargs
|
**kwargs
|
||||||
|
|
|
@ -12,6 +12,7 @@ from manimlib.utils.bezier import integer_interpolate
|
||||||
from manimlib.utils.rate_functions import linear
|
from manimlib.utils.rate_functions import linear
|
||||||
from manimlib.utils.rate_functions import double_smooth
|
from manimlib.utils.rate_functions import double_smooth
|
||||||
from manimlib.utils.rate_functions import smooth
|
from manimlib.utils.rate_functions import smooth
|
||||||
|
from manimlib.utils.simple_functions import clip
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -138,6 +139,8 @@ class DrawBorderThenFill(Animation):
|
||||||
submob.pointwise_become_partial(outline, 0, subalpha)
|
submob.pointwise_become_partial(outline, 0, subalpha)
|
||||||
else:
|
else:
|
||||||
submob.interpolate(outline, start, subalpha)
|
submob.interpolate(outline, start, subalpha)
|
||||||
|
submob.note_changed_stroke()
|
||||||
|
submob.note_changed_fill()
|
||||||
|
|
||||||
|
|
||||||
class Write(DrawBorderThenFill):
|
class Write(DrawBorderThenFill):
|
||||||
|
@ -189,6 +192,7 @@ class ShowIncreasingSubsets(Animation):
|
||||||
|
|
||||||
def interpolate_mobject(self, alpha: float) -> None:
|
def interpolate_mobject(self, alpha: float) -> None:
|
||||||
n_submobs = len(self.all_submobs)
|
n_submobs = len(self.all_submobs)
|
||||||
|
alpha = self.rate_func(alpha)
|
||||||
index = int(self.int_func(alpha * n_submobs))
|
index = int(self.int_func(alpha * n_submobs))
|
||||||
self.update_submobject_list(index)
|
self.update_submobject_list(index)
|
||||||
|
|
||||||
|
@ -206,7 +210,7 @@ class ShowSubmobjectsOneByOne(ShowIncreasingSubsets):
|
||||||
super().__init__(group, int_func=int_func, **kwargs)
|
super().__init__(group, int_func=int_func, **kwargs)
|
||||||
|
|
||||||
def update_submobject_list(self, index: int) -> None:
|
def update_submobject_list(self, index: int) -> None:
|
||||||
# N = len(self.all_submobs)
|
index = int(clip(index, 0, len(self.all_submobs) - 1))
|
||||||
if index == 0:
|
if index == 0:
|
||||||
self.mobject.set_submobjects([])
|
self.mobject.set_submobjects([])
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -258,6 +258,7 @@ class FlashAround(VShowPassingFlash):
|
||||||
self,
|
self,
|
||||||
mobject: Mobject,
|
mobject: Mobject,
|
||||||
time_width: float = 1.0,
|
time_width: float = 1.0,
|
||||||
|
taper_width: float = 0.0,
|
||||||
stroke_width: float = 4.0,
|
stroke_width: float = 4.0,
|
||||||
color: ManimColor = YELLOW,
|
color: ManimColor = YELLOW,
|
||||||
buff: float = SMALL_BUFF,
|
buff: float = SMALL_BUFF,
|
||||||
|
@ -270,7 +271,7 @@ class FlashAround(VShowPassingFlash):
|
||||||
path.insert_n_curves(n_inserted_curves)
|
path.insert_n_curves(n_inserted_curves)
|
||||||
path.set_points(path.get_points_without_null_curves())
|
path.set_points(path.get_points_without_null_curves())
|
||||||
path.set_stroke(color, stroke_width)
|
path.set_stroke(color, stroke_width)
|
||||||
super().__init__(path, time_width=time_width, **kwargs)
|
super().__init__(path, time_width=time_width, taper_width=taper_width, **kwargs)
|
||||||
|
|
||||||
def get_path(self, mobject: Mobject, buff: float) -> SurroundingRectangle:
|
def get_path(self, mobject: Mobject, buff: float) -> SurroundingRectangle:
|
||||||
return SurroundingRectangle(mobject, buff=buff)
|
return SurroundingRectangle(mobject, buff=buff)
|
||||||
|
@ -278,7 +279,7 @@ class FlashAround(VShowPassingFlash):
|
||||||
|
|
||||||
class FlashUnder(FlashAround):
|
class FlashUnder(FlashAround):
|
||||||
def get_path(self, mobject: Mobject, buff: float) -> Underline:
|
def get_path(self, mobject: Mobject, buff: float) -> Underline:
|
||||||
return Underline(mobject, buff=buff)
|
return Underline(mobject, buff=buff, stretch_factor=1.0)
|
||||||
|
|
||||||
|
|
||||||
class ShowCreationThenDestruction(ShowPassingFlash):
|
class ShowCreationThenDestruction(ShowPassingFlash):
|
||||||
|
|
|
@ -11,6 +11,7 @@ if TYPE_CHECKING:
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||||
|
|
||||||
|
|
||||||
class Homotopy(Animation):
|
class Homotopy(Animation):
|
||||||
|
@ -105,7 +106,7 @@ class MoveAlongPath(Animation):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mobject: Mobject,
|
mobject: Mobject,
|
||||||
path: Mobject,
|
path: VMobject,
|
||||||
suspend_mobject_updating: bool = False,
|
suspend_mobject_updating: bool = False,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
@ -113,5 +114,5 @@ class MoveAlongPath(Animation):
|
||||||
super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs)
|
super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs)
|
||||||
|
|
||||||
def interpolate_mobject(self, alpha: float) -> None:
|
def interpolate_mobject(self, alpha: float) -> None:
|
||||||
point = self.path.point_from_proportion(alpha)
|
point = self.path.quick_point_from_proportion(self.rate_func(alpha))
|
||||||
self.mobject.move_to(point)
|
self.mobject.move_to(point)
|
||||||
|
|
|
@ -22,6 +22,7 @@ from manimlib.mobject.types.dot_cloud import DotCloud
|
||||||
from manimlib.mobject.types.surface import ParametricSurface
|
from manimlib.mobject.types.surface import ParametricSurface
|
||||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||||
|
from manimlib.utils.bezier import inverse_interpolate
|
||||||
from manimlib.utils.dict_ops import merge_dicts_recursively
|
from manimlib.utils.dict_ops import merge_dicts_recursively
|
||||||
from manimlib.utils.simple_functions import binary_search
|
from manimlib.utils.simple_functions import binary_search
|
||||||
from manimlib.utils.space_ops import angle_of_vector
|
from manimlib.utils.space_ops import angle_of_vector
|
||||||
|
@ -174,6 +175,7 @@ class CoordinateSystem(ABC):
|
||||||
self,
|
self,
|
||||||
function: Callable[[float], float],
|
function: Callable[[float], float],
|
||||||
x_range: Sequence[float] | None = None,
|
x_range: Sequence[float] | None = None,
|
||||||
|
bind: bool = False,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> ParametricCurve:
|
) -> ParametricCurve:
|
||||||
x_range = x_range or self.x_range
|
x_range = x_range or self.x_range
|
||||||
|
@ -194,6 +196,10 @@ class CoordinateSystem(ABC):
|
||||||
)
|
)
|
||||||
graph.underlying_function = function
|
graph.underlying_function = function
|
||||||
graph.x_range = x_range
|
graph.x_range = x_range
|
||||||
|
|
||||||
|
if bind:
|
||||||
|
self.bind_graph_to_func(graph, function)
|
||||||
|
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
def get_parametric_curve(
|
def get_parametric_curve(
|
||||||
|
@ -239,7 +245,7 @@ class CoordinateSystem(ABC):
|
||||||
def bind_graph_to_func(
|
def bind_graph_to_func(
|
||||||
self,
|
self,
|
||||||
graph: VMobject,
|
graph: VMobject,
|
||||||
func: Callable[[Vect3], Vect3],
|
func: Callable[[VectN], VectN],
|
||||||
jagged: bool = False,
|
jagged: bool = False,
|
||||||
get_discontinuities: Optional[Callable[[], Vect3]] = None
|
get_discontinuities: Optional[Callable[[], Vect3]] = None
|
||||||
) -> VMobject:
|
) -> VMobject:
|
||||||
|
@ -398,9 +404,24 @@ class CoordinateSystem(ABC):
|
||||||
rect.set_fill(negative_color)
|
rect.set_fill(negative_color)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_area_under_graph(self, graph, x_range, fill_color=BLUE, fill_opacity=1):
|
def get_area_under_graph(self, graph, x_range, fill_color=BLUE, fill_opacity=0.5):
|
||||||
# TODO
|
if not hasattr(graph, "x_range"):
|
||||||
pass
|
raise Exception("Argument `graph` must have attribute `x_range`")
|
||||||
|
|
||||||
|
alpha_bounds = [
|
||||||
|
inverse_interpolate(*graph.x_range, x)
|
||||||
|
for x in x_range
|
||||||
|
]
|
||||||
|
sub_graph = graph.copy()
|
||||||
|
sub_graph.pointwise_become_partial(graph, *alpha_bounds)
|
||||||
|
sub_graph.add_line_to(self.c2p(x_range[1], 0))
|
||||||
|
sub_graph.add_line_to(self.c2p(x_range[0], 0))
|
||||||
|
sub_graph.add_line_to(sub_graph.get_start())
|
||||||
|
|
||||||
|
sub_graph.set_stroke(width=0)
|
||||||
|
sub_graph.set_fill(fill_color, fill_opacity)
|
||||||
|
|
||||||
|
return sub_graph
|
||||||
|
|
||||||
|
|
||||||
class Axes(VGroup, CoordinateSystem):
|
class Axes(VGroup, CoordinateSystem):
|
||||||
|
@ -421,6 +442,7 @@ class Axes(VGroup, CoordinateSystem):
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
CoordinateSystem.__init__(self, x_range, y_range, **kwargs)
|
CoordinateSystem.__init__(self, x_range, y_range, **kwargs)
|
||||||
|
kwargs.pop("num_sampled_graph_points_per_tick", None)
|
||||||
VGroup.__init__(self, **kwargs)
|
VGroup.__init__(self, **kwargs)
|
||||||
|
|
||||||
axis_config = dict(**axis_config, unit_size=unit_size)
|
axis_config = dict(**axis_config, unit_size=unit_size)
|
||||||
|
@ -507,7 +529,7 @@ class ThreeDAxes(Axes):
|
||||||
z_range: RangeSpecifier = (-4.0, 4.0, 1.0),
|
z_range: RangeSpecifier = (-4.0, 4.0, 1.0),
|
||||||
z_axis_config: dict = dict(),
|
z_axis_config: dict = dict(),
|
||||||
z_normal: Vect3 = DOWN,
|
z_normal: Vect3 = DOWN,
|
||||||
depth: float = 6.0,
|
depth: float | None = None,
|
||||||
flat_stroke: bool = False,
|
flat_stroke: bool = False,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
@ -519,7 +541,7 @@ class ThreeDAxes(Axes):
|
||||||
axis_config=merge_dicts_recursively(
|
axis_config=merge_dicts_recursively(
|
||||||
self.default_axis_config,
|
self.default_axis_config,
|
||||||
self.default_z_axis_config,
|
self.default_z_axis_config,
|
||||||
kwargs.get("axes_config", {}),
|
kwargs.get("axis_config", {}),
|
||||||
z_axis_config
|
z_axis_config
|
||||||
),
|
),
|
||||||
length=depth,
|
length=depth,
|
||||||
|
@ -549,20 +571,47 @@ class ThreeDAxes(Axes):
|
||||||
axis.add(label)
|
axis.add(label)
|
||||||
self.axis_labels = labels
|
self.axis_labels = labels
|
||||||
|
|
||||||
def get_graph(self, func, color=BLUE_E, opacity=0.9, **kwargs):
|
def get_graph(
|
||||||
|
self,
|
||||||
|
func,
|
||||||
|
color=BLUE_E,
|
||||||
|
opacity=0.9,
|
||||||
|
u_range=None,
|
||||||
|
v_range=None,
|
||||||
|
**kwargs
|
||||||
|
) -> ParametricSurface:
|
||||||
xu = self.x_axis.get_unit_size()
|
xu = self.x_axis.get_unit_size()
|
||||||
yu = self.y_axis.get_unit_size()
|
yu = self.y_axis.get_unit_size()
|
||||||
zu = self.z_axis.get_unit_size()
|
zu = self.z_axis.get_unit_size()
|
||||||
x0, y0, z0 = self.get_origin()
|
x0, y0, z0 = self.get_origin()
|
||||||
|
u_range = u_range or self.x_range[:2]
|
||||||
|
v_range = v_range or self.y_range[:2]
|
||||||
return ParametricSurface(
|
return ParametricSurface(
|
||||||
lambda u, v: [xu * u + x0, yu * v + y0, zu * func(u, v) + z0],
|
lambda u, v: [xu * u + x0, yu * v + y0, zu * func(u, v) + z0],
|
||||||
u_range=self.x_range[:2],
|
u_range=u_range,
|
||||||
v_range=self.y_range[:2],
|
v_range=v_range,
|
||||||
color=color,
|
color=color,
|
||||||
opacity=opacity,
|
opacity=opacity,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_parametric_surface(
|
||||||
|
self,
|
||||||
|
func,
|
||||||
|
color=BLUE_E,
|
||||||
|
opacity=0.9,
|
||||||
|
**kwargs
|
||||||
|
) -> ParametricSurface:
|
||||||
|
surface = ParametricSurface(func, color=color, opacity=opacity, **kwargs)
|
||||||
|
xu = self.x_axis.get_unit_size()
|
||||||
|
yu = self.y_axis.get_unit_size()
|
||||||
|
zu = self.z_axis.get_unit_size()
|
||||||
|
axes = [self.x_axis, self.y_axis, self.z_axis]
|
||||||
|
for dim, axis in zip(range(3), axes):
|
||||||
|
surface.stretch(axis.get_unit_size(), dim, about_point=ORIGIN)
|
||||||
|
surface.shift(self.get_origin())
|
||||||
|
return surface
|
||||||
|
|
||||||
|
|
||||||
class NumberPlane(Axes):
|
class NumberPlane(Axes):
|
||||||
default_axis_config: dict = dict(
|
default_axis_config: dict = dict(
|
||||||
|
|
|
@ -7,12 +7,15 @@ import numpy as np
|
||||||
|
|
||||||
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UL, UP, UR
|
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UL, UP, UR
|
||||||
from manimlib.constants import GREY_A, RED, WHITE, BLACK
|
from manimlib.constants import GREY_A, RED, WHITE, BLACK
|
||||||
from manimlib.constants import MED_SMALL_BUFF
|
from manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF
|
||||||
from manimlib.constants import DEGREES, PI, TAU
|
from manimlib.constants import DEGREES, PI, TAU
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
from manimlib.mobject.types.vectorized_mobject import DashedVMobject
|
from manimlib.mobject.types.vectorized_mobject import DashedVMobject
|
||||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||||
|
from manimlib.utils.bezier import bezier
|
||||||
|
from manimlib.utils.bezier import quadratic_bezier_points_for_arc
|
||||||
|
from manimlib.utils.bezier import partial_quadratic_bezier_points
|
||||||
from manimlib.utils.iterables import adjacent_n_tuples
|
from manimlib.utils.iterables import adjacent_n_tuples
|
||||||
from manimlib.utils.iterables import adjacent_pairs
|
from manimlib.utils.iterables import adjacent_pairs
|
||||||
from manimlib.utils.simple_functions import clip
|
from manimlib.utils.simple_functions import clip
|
||||||
|
@ -26,6 +29,7 @@ from manimlib.utils.space_ops import get_norm
|
||||||
from manimlib.utils.space_ops import normalize
|
from manimlib.utils.space_ops import normalize
|
||||||
from manimlib.utils.space_ops import rotate_vector
|
from manimlib.utils.space_ops import rotate_vector
|
||||||
from manimlib.utils.space_ops import rotation_matrix_transpose
|
from manimlib.utils.space_ops import rotation_matrix_transpose
|
||||||
|
from manimlib.utils.space_ops import rotation_about_z
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -213,28 +217,11 @@ class Arc(TipableVMobject):
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.set_points(Arc.create_quadratic_bezier_points(
|
self.set_points(quadratic_bezier_points_for_arc(angle, n_components))
|
||||||
angle=angle,
|
self.rotate(start_angle, about_point=ORIGIN)
|
||||||
start_angle=start_angle,
|
|
||||||
n_components=n_components
|
|
||||||
))
|
|
||||||
self.scale(radius, about_point=ORIGIN)
|
self.scale(radius, about_point=ORIGIN)
|
||||||
self.shift(arc_center)
|
self.shift(arc_center)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_quadratic_bezier_points(
|
|
||||||
angle: float,
|
|
||||||
start_angle: float = 0,
|
|
||||||
n_components: int = 8
|
|
||||||
) -> Vect3Array:
|
|
||||||
n_points = 2 * n_components + 1
|
|
||||||
angles = np.linspace(start_angle, start_angle + angle, n_points)
|
|
||||||
points = np.array([np.cos(angles), np.sin(angles), np.zeros(n_points)]).T
|
|
||||||
# Adjust handles
|
|
||||||
theta = angle / n_components
|
|
||||||
points[1::2] /= np.cos(theta / 2)
|
|
||||||
return points
|
|
||||||
|
|
||||||
def get_arc_center(self) -> Vect3:
|
def get_arc_center(self) -> Vect3:
|
||||||
"""
|
"""
|
||||||
Looks at the normals to the first two
|
Looks at the normals to the first two
|
||||||
|
@ -448,8 +435,8 @@ class Annulus(VMobject):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.radius = outer_radius
|
self.radius = outer_radius
|
||||||
outer_path = outer_radius * Arc.create_quadratic_bezier_points(TAU, 0)
|
outer_path = outer_radius * quadratic_bezier_points_for_arc(TAU)
|
||||||
inner_path = inner_radius * Arc.create_quadratic_bezier_points(-TAU, 0)
|
inner_path = inner_radius * quadratic_bezier_points_for_arc(-TAU)
|
||||||
self.add_subpath(outer_path)
|
self.add_subpath(outer_path)
|
||||||
self.add_subpath(inner_path)
|
self.add_subpath(inner_path)
|
||||||
self.shift(center)
|
self.shift(center)
|
||||||
|
@ -466,6 +453,7 @@ class Line(TipableVMobject):
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.path_arc = path_arc
|
self.path_arc = path_arc
|
||||||
|
self.buff = buff
|
||||||
self.set_start_and_end_attrs(start, end)
|
self.set_start_and_end_attrs(start, end)
|
||||||
self.set_points_by_ends(self.start, self.end, buff, path_arc)
|
self.set_points_by_ends(self.start, self.end, buff, path_arc)
|
||||||
|
|
||||||
|
@ -476,32 +464,15 @@ class Line(TipableVMobject):
|
||||||
buff: float = 0,
|
buff: float = 0,
|
||||||
path_arc: float = 0
|
path_arc: float = 0
|
||||||
) -> Self:
|
) -> Self:
|
||||||
vect = end - start
|
self.clear_points()
|
||||||
dist = get_norm(vect)
|
self.start_new_path(start)
|
||||||
if np.isclose(dist, 0):
|
self.add_arc_to(end, path_arc)
|
||||||
self.set_points_as_corners([start, end])
|
|
||||||
return self
|
|
||||||
if path_arc:
|
|
||||||
neg = path_arc < 0
|
|
||||||
if neg:
|
|
||||||
path_arc = -path_arc
|
|
||||||
start, end = end, start
|
|
||||||
radius = (dist / 2) / math.sin(path_arc / 2)
|
|
||||||
alpha = (PI - path_arc) / 2
|
|
||||||
center = start + radius * normalize(rotate_vector(end - start, alpha))
|
|
||||||
|
|
||||||
raw_arc_points = Arc.create_quadratic_bezier_points(
|
# Apply buffer
|
||||||
angle=path_arc - 2 * buff / radius,
|
if buff > 0:
|
||||||
start_angle=angle_of_vector(start - center) + buff / radius,
|
length = self.get_arc_length()
|
||||||
)
|
alpha = min(buff / length, 0.5)
|
||||||
if neg:
|
self.pointwise_become_partial(self, alpha, 1 - alpha)
|
||||||
raw_arc_points = raw_arc_points[::-1]
|
|
||||||
self.set_points(center + radius * raw_arc_points)
|
|
||||||
else:
|
|
||||||
if buff > 0 and dist > 0:
|
|
||||||
start = start + vect * (buff / dist)
|
|
||||||
end = end - vect * (buff / dist)
|
|
||||||
self.set_points_as_corners([start, end])
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_path_arc(self, new_value: float) -> Self:
|
def set_path_arc(self, new_value: float) -> Self:
|
||||||
|
@ -673,15 +644,17 @@ class Arrow(Line):
|
||||||
stroke_width: float = 5,
|
stroke_width: float = 5,
|
||||||
buff: float = 0.25,
|
buff: float = 0.25,
|
||||||
tip_width_ratio: float = 5,
|
tip_width_ratio: float = 5,
|
||||||
width_to_tip_len: float = 0.0075,
|
tip_len_to_width: float = 0.0075,
|
||||||
max_tip_length_to_length_ratio: float = 0.3,
|
max_tip_length_to_length_ratio: float = 0.3,
|
||||||
max_width_to_length_ratio: float = 8.0,
|
max_width_to_length_ratio: float = 8.0,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
self.tip_width_ratio = tip_width_ratio
|
self.tip_width_ratio = tip_width_ratio
|
||||||
self.width_to_tip_len = width_to_tip_len
|
self.tip_len_to_width = tip_len_to_width
|
||||||
self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio
|
self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio
|
||||||
self.max_width_to_length_ratio = max_width_to_length_ratio
|
self.max_width_to_length_ratio = max_width_to_length_ratio
|
||||||
|
self.n_tip_points = 3
|
||||||
|
self.original_stroke_width = stroke_width
|
||||||
super().__init__(
|
super().__init__(
|
||||||
start, end,
|
start, end,
|
||||||
stroke_color=stroke_color,
|
stroke_color=stroke_color,
|
||||||
|
@ -705,27 +678,32 @@ class Arrow(Line):
|
||||||
def insert_tip_anchor(self) -> Self:
|
def insert_tip_anchor(self) -> Self:
|
||||||
prev_end = self.get_end()
|
prev_end = self.get_end()
|
||||||
arc_len = self.get_arc_length()
|
arc_len = self.get_arc_length()
|
||||||
tip_len = self.get_stroke_width() * self.width_to_tip_len * self.tip_width_ratio
|
tip_len = self.get_stroke_width() * self.tip_width_ratio * self.tip_len_to_width
|
||||||
if tip_len >= self.max_tip_length_to_length_ratio * arc_len:
|
if tip_len >= self.max_tip_length_to_length_ratio * arc_len or arc_len == 0:
|
||||||
alpha = self.max_tip_length_to_length_ratio
|
alpha = self.max_tip_length_to_length_ratio
|
||||||
else:
|
else:
|
||||||
alpha = tip_len / arc_len
|
alpha = tip_len / arc_len
|
||||||
self.pointwise_become_partial(self, 0, 1 - alpha)
|
|
||||||
# Dumb that this is needed
|
if self.path_arc > 0 and self.buff > 0:
|
||||||
self.start_new_path(self.point_from_proportion(1 - 1e-5))
|
self.insert_n_curves(10) # Is this needed?
|
||||||
|
self.pointwise_become_partial(self, 0.0, 1.0 - alpha)
|
||||||
|
self.add_line_to(self.get_end())
|
||||||
self.add_line_to(prev_end)
|
self.add_line_to(prev_end)
|
||||||
|
self.n_tip_points = 3
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@Mobject.affects_data
|
@Mobject.affects_data
|
||||||
def create_tip_with_stroke_width(self) -> Self:
|
def create_tip_with_stroke_width(self) -> Self:
|
||||||
if self.get_num_points() < 3:
|
if self.get_num_points() < 3:
|
||||||
return self
|
return self
|
||||||
tip_width = self.tip_width_ratio * min(
|
stroke_width = min(
|
||||||
float(self.get_stroke_width()),
|
self.original_stroke_width,
|
||||||
self.max_width_to_length_ratio * self.get_length(),
|
self.max_width_to_length_ratio * self.get_length(),
|
||||||
)
|
)
|
||||||
self.data['stroke_width'][:-3] = self.data['stroke_width'][0]
|
tip_width = self.tip_width_ratio * stroke_width
|
||||||
self.data['stroke_width'][-3:, 0] = tip_width * np.linspace(1, 0, 3)
|
ntp = self.n_tip_points
|
||||||
|
self.data['stroke_width'][:-ntp] = self.data['stroke_width'][0]
|
||||||
|
self.data['stroke_width'][-ntp:, 0] = tip_width * np.linspace(1, 0, ntp)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def reset_tip(self) -> Self:
|
def reset_tip(self) -> Self:
|
||||||
|
@ -742,6 +720,7 @@ class Arrow(Line):
|
||||||
*args, **kwargs
|
*args, **kwargs
|
||||||
) -> Self:
|
) -> Self:
|
||||||
super().set_stroke(color=color, width=width, *args, **kwargs)
|
super().set_stroke(color=color, width=width, *args, **kwargs)
|
||||||
|
self.original_stroke_width = self.get_stroke_width()
|
||||||
if self.has_points():
|
if self.has_points():
|
||||||
self.reset_tip()
|
self.reset_tip()
|
||||||
return self
|
return self
|
||||||
|
@ -817,7 +796,7 @@ class FillArrow(Line):
|
||||||
R = (-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a)
|
R = (-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a)
|
||||||
|
|
||||||
# Find arc points
|
# Find arc points
|
||||||
points1 = Arc.create_quadratic_bezier_points(path_arc)
|
points1 = quadratic_bezier_points_for_arc(path_arc)
|
||||||
points2 = np.array(points1[::-1])
|
points2 = np.array(points1[::-1])
|
||||||
points1 *= (R + thickness / 2)
|
points1 *= (R + thickness / 2)
|
||||||
points2 *= (R - thickness / 2)
|
points2 *= (R - thickness / 2)
|
||||||
|
@ -1046,6 +1025,12 @@ class Rectangle(Polygon):
|
||||||
self.set_width(width, stretch=True)
|
self.set_width(width, stretch=True)
|
||||||
self.set_height(height, stretch=True)
|
self.set_height(height, stretch=True)
|
||||||
|
|
||||||
|
def surround(self, mobject, buff=SMALL_BUFF) -> Self:
|
||||||
|
target_shape = np.array(mobject.get_shape()) + 2 * buff
|
||||||
|
self.set_shape(*target_shape)
|
||||||
|
self.move_to(mobject)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class Square(Rectangle):
|
class Square(Rectangle):
|
||||||
def __init__(self, side_length: float = 2.0, **kwargs):
|
def __init__(self, side_length: float = 2.0, **kwargs):
|
||||||
|
|
|
@ -224,7 +224,7 @@ class Mobject(object):
|
||||||
@affects_family_data
|
@affects_family_data
|
||||||
def reverse_points(self) -> Self:
|
def reverse_points(self) -> Self:
|
||||||
for mob in self.get_family():
|
for mob in self.get_family():
|
||||||
mob.data = mob.data[::-1]
|
mob.data[:] = mob.data[::-1]
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@affects_family_data
|
@affects_family_data
|
||||||
|
@ -790,13 +790,13 @@ class Mobject(object):
|
||||||
def update(self, dt: float = 0, recurse: bool = True) -> Self:
|
def update(self, dt: float = 0, recurse: bool = True) -> Self:
|
||||||
if not self.has_updaters or self.updating_suspended:
|
if not self.has_updaters or self.updating_suspended:
|
||||||
return self
|
return self
|
||||||
|
if recurse:
|
||||||
|
for submob in self.submobjects:
|
||||||
|
submob.update(dt, recurse)
|
||||||
for updater in self.time_based_updaters:
|
for updater in self.time_based_updaters:
|
||||||
updater(self, dt)
|
updater(self, dt)
|
||||||
for updater in self.non_time_updaters:
|
for updater in self.non_time_updaters:
|
||||||
updater(self)
|
updater(self)
|
||||||
if recurse:
|
|
||||||
for submob in self.submobjects:
|
|
||||||
submob.update(dt, recurse)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_time_based_updaters(self) -> list[TimeBasedUpdater]:
|
def get_time_based_updaters(self) -> list[TimeBasedUpdater]:
|
||||||
|
@ -1334,7 +1334,7 @@ class Mobject(object):
|
||||||
rgbs = resize_with_interpolation(rgbs, len(data))
|
rgbs = resize_with_interpolation(rgbs, len(data))
|
||||||
data[name][:, :3] = rgbs
|
data[name][:, :3] = rgbs
|
||||||
if opacity is not None:
|
if opacity is not None:
|
||||||
if isinstance(opacity, list):
|
if not isinstance(opacity, (float, int)):
|
||||||
opacity = resize_with_interpolation(np.array(opacity), len(data))
|
opacity = resize_with_interpolation(np.array(opacity), len(data))
|
||||||
data[name][:, 3] = opacity
|
data[name][:, 3] = opacity
|
||||||
return self
|
return self
|
||||||
|
@ -1540,6 +1540,9 @@ class Mobject(object):
|
||||||
def get_depth(self) -> float:
|
def get_depth(self) -> float:
|
||||||
return self.length_over_dim(2)
|
return self.length_over_dim(2)
|
||||||
|
|
||||||
|
def get_shape(self) -> Tuple[float]:
|
||||||
|
return tuple(self.length_over_dim(dim) for dim in range(3))
|
||||||
|
|
||||||
def get_coord(self, dim: int, direction: Vect3 = ORIGIN) -> float:
|
def get_coord(self, dim: int, direction: Vect3 = ORIGIN) -> float:
|
||||||
"""
|
"""
|
||||||
Meant to generalize get_x, get_y, get_z
|
Meant to generalize get_x, get_y, get_z
|
||||||
|
@ -1598,6 +1601,12 @@ class Mobject(object):
|
||||||
def match_color(self, mobject: Mobject) -> Self:
|
def match_color(self, mobject: Mobject) -> Self:
|
||||||
return self.set_color(mobject.get_color())
|
return self.set_color(mobject.get_color())
|
||||||
|
|
||||||
|
def match_style(self, mobject: Mobject) -> Self:
|
||||||
|
self.set_color(mobject.get_color())
|
||||||
|
self.set_opacity(mobject.get_opacity())
|
||||||
|
self.set_shading(*mobject.get_shading())
|
||||||
|
return self
|
||||||
|
|
||||||
def match_dim_size(self, mobject: Mobject, dim: int, **kwargs) -> Self:
|
def match_dim_size(self, mobject: Mobject, dim: int, **kwargs) -> Self:
|
||||||
return self.rescale_to_fit(
|
return self.rescale_to_fit(
|
||||||
mobject.length_over_dim(dim), dim,
|
mobject.length_over_dim(dim), dim,
|
||||||
|
|
|
@ -94,7 +94,8 @@ class NumberLine(Line):
|
||||||
x_max = self.x_max
|
x_max = self.x_max
|
||||||
else:
|
else:
|
||||||
x_max = self.x_max + self.x_step
|
x_max = self.x_max + self.x_step
|
||||||
return np.arange(self.x_min, x_max, self.x_step)
|
result = np.arange(self.x_min, x_max, self.x_step)
|
||||||
|
return result[result <= self.x_max]
|
||||||
|
|
||||||
def add_ticks(self) -> None:
|
def add_ticks(self) -> None:
|
||||||
ticks = VGroup()
|
ticks = VGroup()
|
||||||
|
|
|
@ -98,6 +98,8 @@ class DecimalNumber(VMobject):
|
||||||
formatter = self.get_complex_formatter()
|
formatter = self.get_complex_formatter()
|
||||||
else:
|
else:
|
||||||
formatter = self.get_formatter()
|
formatter = self.get_formatter()
|
||||||
|
if self.num_decimal_places == 0 and isinstance(number, float):
|
||||||
|
number = int(number)
|
||||||
num_string = formatter.format(number)
|
num_string = formatter.format(number)
|
||||||
|
|
||||||
rounded_num = np.round(number, self.num_decimal_places)
|
rounded_num = np.round(number, self.num_decimal_places)
|
||||||
|
@ -149,7 +151,7 @@ class DecimalNumber(VMobject):
|
||||||
":",
|
":",
|
||||||
"+" if config["include_sign"] else "",
|
"+" if config["include_sign"] else "",
|
||||||
"," if config["group_with_commas"] else "",
|
"," if config["group_with_commas"] else "",
|
||||||
f".{ndp}f",
|
f".{ndp}f" if ndp > 0 else "d",
|
||||||
"}",
|
"}",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -169,6 +171,7 @@ class DecimalNumber(VMobject):
|
||||||
self.set_submobjects_from_number(number)
|
self.set_submobjects_from_number(number)
|
||||||
self.move_to(move_to_point, self.edge_to_fix)
|
self.move_to(move_to_point, self.edge_to_fix)
|
||||||
self.set_style(**style)
|
self.set_style(**style)
|
||||||
|
self.fix_in_frame(self._is_fixed_in_frame)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _handle_scale_side_effects(self, scale_factor: float) -> Self:
|
def _handle_scale_side_effects(self, scale_factor: float) -> Self:
|
||||||
|
|
|
@ -27,13 +27,20 @@ class SurroundingRectangle(Rectangle):
|
||||||
color: ManimColor = YELLOW,
|
color: ManimColor = YELLOW,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(color=color, **kwargs)
|
||||||
width=mobject.get_width() + 2 * buff,
|
self.buff = buff
|
||||||
height=mobject.get_height() + 2 * buff,
|
self.surround(mobject)
|
||||||
color=color,
|
|
||||||
**kwargs
|
def surround(self, mobject, buff=None) -> Self:
|
||||||
)
|
self.mobject = mobject
|
||||||
self.move_to(mobject)
|
self.buff = buff if buff is not None else self.buff
|
||||||
|
super().surround(mobject, self.buff)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_buff(self, buff) -> Self:
|
||||||
|
self.buff = buff
|
||||||
|
self.surround(self.mobject)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class BackgroundRectangle(SurroundingRectangle):
|
class BackgroundRectangle(SurroundingRectangle):
|
||||||
|
@ -110,6 +117,7 @@ class Underline(Line):
|
||||||
buff: float = SMALL_BUFF,
|
buff: float = SMALL_BUFF,
|
||||||
stroke_color=WHITE,
|
stroke_color=WHITE,
|
||||||
stroke_width: float | Sequence[float] = [0, 3, 3, 0],
|
stroke_width: float | Sequence[float] = [0, 3, 3, 0],
|
||||||
|
stretch_factor=1.2,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
@ -119,5 +127,6 @@ class Underline(Line):
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
self.insert_n_curves(30)
|
self.insert_n_curves(30)
|
||||||
self.match_width(mobject)
|
self.set_stroke(stroke_color, stroke_width)
|
||||||
|
self.set_width(mobject.get_width() * stretch_factor)
|
||||||
self.next_to(mobject, DOWN, buff=buff)
|
self.next_to(mobject, DOWN, buff=buff)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import itertools as it
|
||||||
|
|
||||||
from manimlib.animation.composition import AnimationGroup
|
from manimlib.animation.composition import AnimationGroup
|
||||||
from manimlib.animation.rotation import Rotating
|
from manimlib.animation.rotation import Rotating
|
||||||
|
@ -14,6 +15,7 @@ from manimlib.constants import DOWN
|
||||||
from manimlib.constants import FRAME_WIDTH
|
from manimlib.constants import FRAME_WIDTH
|
||||||
from manimlib.constants import GREEN
|
from manimlib.constants import GREEN
|
||||||
from manimlib.constants import GREEN_SCREEN
|
from manimlib.constants import GREEN_SCREEN
|
||||||
|
from manimlib.constants import GREEN_E
|
||||||
from manimlib.constants import GREY
|
from manimlib.constants import GREY
|
||||||
from manimlib.constants import GREY_A
|
from manimlib.constants import GREY_A
|
||||||
from manimlib.constants import GREY_B
|
from manimlib.constants import GREY_B
|
||||||
|
@ -26,6 +28,7 @@ from manimlib.constants import ORIGIN
|
||||||
from manimlib.constants import OUT
|
from manimlib.constants import OUT
|
||||||
from manimlib.constants import PI
|
from manimlib.constants import PI
|
||||||
from manimlib.constants import RED
|
from manimlib.constants import RED
|
||||||
|
from manimlib.constants import RED_E
|
||||||
from manimlib.constants import RIGHT
|
from manimlib.constants import RIGHT
|
||||||
from manimlib.constants import SMALL_BUFF
|
from manimlib.constants import SMALL_BUFF
|
||||||
from manimlib.constants import SMALL_BUFF
|
from manimlib.constants import SMALL_BUFF
|
||||||
|
@ -36,6 +39,7 @@ from manimlib.constants import DL
|
||||||
from manimlib.constants import DR
|
from manimlib.constants import DR
|
||||||
from manimlib.constants import WHITE
|
from manimlib.constants import WHITE
|
||||||
from manimlib.constants import YELLOW
|
from manimlib.constants import YELLOW
|
||||||
|
from manimlib.constants import TAU
|
||||||
from manimlib.mobject.boolean_ops import Difference
|
from manimlib.mobject.boolean_ops import Difference
|
||||||
from manimlib.mobject.geometry import Arc
|
from manimlib.mobject.geometry import Arc
|
||||||
from manimlib.mobject.geometry import Circle
|
from manimlib.mobject.geometry import Circle
|
||||||
|
@ -44,6 +48,7 @@ from manimlib.mobject.geometry import Line
|
||||||
from manimlib.mobject.geometry import Polygon
|
from manimlib.mobject.geometry import Polygon
|
||||||
from manimlib.mobject.geometry import Rectangle
|
from manimlib.mobject.geometry import Rectangle
|
||||||
from manimlib.mobject.geometry import Square
|
from manimlib.mobject.geometry import Square
|
||||||
|
from manimlib.mobject.geometry import AnnularSector
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
from manimlib.mobject.numbers import Integer
|
from manimlib.mobject.numbers import Integer
|
||||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||||
|
@ -358,7 +363,7 @@ class Bubble(SVGMobject):
|
||||||
stroke_width: float = 3.0,
|
stroke_width: float = 3.0,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
self.direction = direction
|
self.direction = LEFT # Possibly updated below by self.flip()
|
||||||
self.bubble_center_adjustment_factor = bubble_center_adjustment_factor
|
self.bubble_center_adjustment_factor = bubble_center_adjustment_factor
|
||||||
self.content_scale_factor = content_scale_factor
|
self.content_scale_factor = content_scale_factor
|
||||||
|
|
||||||
|
@ -380,7 +385,7 @@ class Bubble(SVGMobject):
|
||||||
if direction[0] > 0:
|
if direction[0] > 0:
|
||||||
self.flip()
|
self.flip()
|
||||||
|
|
||||||
self.content = Mobject()
|
self.content = VMobject()
|
||||||
|
|
||||||
def get_tip(self):
|
def get_tip(self):
|
||||||
# TODO, find a better way
|
# TODO, find a better way
|
||||||
|
@ -403,10 +408,10 @@ class Bubble(SVGMobject):
|
||||||
self.direction = -np.array(self.direction)
|
self.direction = -np.array(self.direction)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def pin_to(self, mobject):
|
def pin_to(self, mobject, auto_flip=False):
|
||||||
mob_center = mobject.get_center()
|
mob_center = mobject.get_center()
|
||||||
want_to_flip = np.sign(mob_center[0]) != np.sign(self.direction[0])
|
want_to_flip = np.sign(mob_center[0]) != np.sign(self.direction[0])
|
||||||
if want_to_flip:
|
if want_to_flip and auto_flip:
|
||||||
self.flip()
|
self.flip()
|
||||||
boundary_point = mobject.get_bounding_box_point(UP - self.direction)
|
boundary_point = mobject.get_bounding_box_point(UP - self.direction)
|
||||||
vector_from_center = 1.0 * (boundary_point - mob_center)
|
vector_from_center = 1.0 * (boundary_point - mob_center)
|
||||||
|
@ -579,7 +584,6 @@ class Piano3D(VGroup):
|
||||||
key.set_color(BLACK)
|
key.set_color(BLACK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DieFace(VGroup):
|
class DieFace(VGroup):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -590,7 +594,7 @@ class DieFace(VGroup):
|
||||||
stroke_width: float = 2.0,
|
stroke_width: float = 2.0,
|
||||||
fill_color: ManimColor = GREY_E,
|
fill_color: ManimColor = GREY_E,
|
||||||
dot_radius: float = 0.08,
|
dot_radius: float = 0.08,
|
||||||
dot_color: ManimColor = BLUE_B,
|
dot_color: ManimColor = WHITE,
|
||||||
dot_coalesce_factor: float = 0.5
|
dot_coalesce_factor: float = 0.5
|
||||||
):
|
):
|
||||||
dot = Dot(radius=dot_radius, fill_color=dot_color)
|
dot = Dot(radius=dot_radius, fill_color=dot_color)
|
||||||
|
@ -622,5 +626,50 @@ class DieFace(VGroup):
|
||||||
arrangement.space_out_submobjects(dot_coalesce_factor)
|
arrangement.space_out_submobjects(dot_coalesce_factor)
|
||||||
|
|
||||||
super().__init__(square, arrangement)
|
super().__init__(square, arrangement)
|
||||||
|
self.dots = arrangement
|
||||||
self.value = value
|
self.value = value
|
||||||
self.index = value
|
self.index = value
|
||||||
|
|
||||||
|
|
||||||
|
class Dartboard(VGroup):
|
||||||
|
radius = 3
|
||||||
|
n_sectors = 20
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
n_sectors = self.n_sectors
|
||||||
|
angle = TAU / n_sectors
|
||||||
|
|
||||||
|
segments = VGroup(*[
|
||||||
|
VGroup(*[
|
||||||
|
AnnularSector(
|
||||||
|
inner_radius=in_r,
|
||||||
|
outer_radius=out_r,
|
||||||
|
start_angle=n * angle,
|
||||||
|
angle=angle,
|
||||||
|
fill_color=color,
|
||||||
|
)
|
||||||
|
for n, color in zip(
|
||||||
|
range(n_sectors),
|
||||||
|
it.cycle(colors)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
for colors, in_r, out_r in [
|
||||||
|
([GREY_B, GREY_E], 0, 1),
|
||||||
|
([GREEN_E, RED_E], 0.5, 0.55),
|
||||||
|
([GREEN_E, RED_E], 0.95, 1),
|
||||||
|
]
|
||||||
|
])
|
||||||
|
segments.rotate(-angle / 2)
|
||||||
|
bullseyes = VGroup(*[
|
||||||
|
Circle(radius=r)
|
||||||
|
for r in [0.07, 0.035]
|
||||||
|
])
|
||||||
|
bullseyes.set_fill(opacity=1)
|
||||||
|
bullseyes.set_stroke(width=0)
|
||||||
|
bullseyes[0].set_color(GREEN_E)
|
||||||
|
bullseyes[1].set_color(RED_E)
|
||||||
|
|
||||||
|
self.bullseye = bullseyes[1]
|
||||||
|
self.add(*segments, *bullseyes)
|
||||||
|
self.scale(self.radius)
|
||||||
|
|
|
@ -47,6 +47,7 @@ class StringMobject(SVGMobject, ABC):
|
||||||
self,
|
self,
|
||||||
string: str,
|
string: str,
|
||||||
fill_color: ManimColor = WHITE,
|
fill_color: ManimColor = WHITE,
|
||||||
|
fill_border_width: float = 0.5,
|
||||||
stroke_color: ManimColor = WHITE,
|
stroke_color: ManimColor = WHITE,
|
||||||
stroke_width: float = 0,
|
stroke_width: float = 0,
|
||||||
base_color: ManimColor = WHITE,
|
base_color: ManimColor = WHITE,
|
||||||
|
@ -65,12 +66,10 @@ class StringMobject(SVGMobject, ABC):
|
||||||
self.use_labelled_svg = use_labelled_svg
|
self.use_labelled_svg = use_labelled_svg
|
||||||
|
|
||||||
self.parse()
|
self.parse()
|
||||||
super().__init__(
|
super().__init__(**kwargs)
|
||||||
stroke_color=stroke_color,
|
self.set_stroke(stroke_color, stroke_width)
|
||||||
fill_color=fill_color,
|
self.set_fill(fill_color, border_width=fill_border_width)
|
||||||
stroke_width=stroke_width,
|
self.note_changed_stroke()
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
self.labels = [submob.label for submob in self.submobjects]
|
self.labels = [submob.label for submob in self.submobjects]
|
||||||
|
|
||||||
def get_file_path(self, is_labelled: bool = False) -> str:
|
def get_file_path(self, is_labelled: bool = False) -> str:
|
||||||
|
|
|
@ -22,6 +22,7 @@ DEFAULT_GLOW_DOT_RADIUS = 0.2
|
||||||
DEFAULT_GRID_HEIGHT = 6
|
DEFAULT_GRID_HEIGHT = 6
|
||||||
DEFAULT_BUFF_RATIO = 0.5
|
DEFAULT_BUFF_RATIO = 0.5
|
||||||
|
|
||||||
|
|
||||||
class DotCloud(PMobject):
|
class DotCloud(PMobject):
|
||||||
shader_folder: str = "true_dot"
|
shader_folder: str = "true_dot"
|
||||||
render_primitive: int = moderngl.POINTS
|
render_primitive: int = moderngl.POINTS
|
||||||
|
@ -116,6 +117,10 @@ class DotCloud(PMobject):
|
||||||
def get_radius(self) -> float:
|
def get_radius(self) -> float:
|
||||||
return self.get_radii().max()
|
return self.get_radii().max()
|
||||||
|
|
||||||
|
def scale_radii(self, scale_factor: float) -> Self:
|
||||||
|
self.set_radius(scale_factor * self.get_radii())
|
||||||
|
return self
|
||||||
|
|
||||||
def set_glow_factor(self, glow_factor: float) -> Self:
|
def set_glow_factor(self, glow_factor: float) -> Self:
|
||||||
self.uniforms["glow_factor"] = glow_factor
|
self.uniforms["glow_factor"] = glow_factor
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ImageMobject(Mobject):
|
||||||
('opacity', np.float32, (1,)),
|
('opacity', np.float32, (1,)),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
filename: str,
|
filename: str,
|
||||||
height: float = 4.0,
|
height: float = 4.0,
|
||||||
|
|
|
@ -130,11 +130,13 @@ class Surface(Mobject):
|
||||||
def get_unit_normals(self) -> Vect3Array:
|
def get_unit_normals(self) -> Vect3Array:
|
||||||
nu, nv = self.resolution
|
nu, nv = self.resolution
|
||||||
indices = np.arange(nu * nv)
|
indices = np.arange(nu * nv)
|
||||||
|
if len(indices) == 0:
|
||||||
|
return np.zeros((3, 0))
|
||||||
|
|
||||||
left = indices - 1
|
left = indices - 1
|
||||||
right = indices + 1
|
right = indices + 1
|
||||||
up = indices - nv
|
up = indices - nv
|
||||||
down = indices + nv
|
down = indices + nv
|
||||||
|
|
||||||
left[0] = indices[0]
|
left[0] = indices[0]
|
||||||
right[-1] = indices[-1]
|
right[-1] = indices[-1]
|
||||||
|
@ -166,7 +168,7 @@ class Surface(Mobject):
|
||||||
|
|
||||||
nu, nv = smobject.resolution
|
nu, nv = smobject.resolution
|
||||||
self.data['point'][:] = self.get_partial_points_array(
|
self.data['point'][:] = self.get_partial_points_array(
|
||||||
self.data['point'], a, b,
|
smobject.data['point'], a, b,
|
||||||
(nu, nv, 3),
|
(nu, nv, 3),
|
||||||
axis=axis
|
axis=axis
|
||||||
)
|
)
|
||||||
|
@ -183,7 +185,7 @@ class Surface(Mobject):
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
return points
|
return points
|
||||||
nu, nv = resolution[:2]
|
nu, nv = resolution[:2]
|
||||||
points = points.reshape(resolution)
|
points = points.reshape(resolution).copy()
|
||||||
max_index = resolution[axis] - 1
|
max_index = resolution[axis] - 1
|
||||||
lower_index, lower_residue = integer_interpolate(0, max_index, a)
|
lower_index, lower_residue = integer_interpolate(0, max_index, a)
|
||||||
upper_index, upper_residue = integer_interpolate(0, max_index, b)
|
upper_index, upper_residue = integer_interpolate(0, max_index, b)
|
||||||
|
|
|
@ -11,17 +11,20 @@ from manimlib.constants import DEFAULT_STROKE_WIDTH
|
||||||
from manimlib.constants import DEGREES
|
from manimlib.constants import DEGREES
|
||||||
from manimlib.constants import JOINT_TYPE_MAP
|
from manimlib.constants import JOINT_TYPE_MAP
|
||||||
from manimlib.constants import ORIGIN, OUT
|
from manimlib.constants import ORIGIN, OUT
|
||||||
|
from manimlib.constants import TAU
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
from manimlib.mobject.mobject import Point
|
from manimlib.mobject.mobject import Point
|
||||||
from manimlib.utils.bezier import bezier
|
from manimlib.utils.bezier import bezier
|
||||||
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
|
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
|
||||||
from manimlib.utils.bezier import approx_smooth_quadratic_bezier_handles
|
from manimlib.utils.bezier import approx_smooth_quadratic_bezier_handles
|
||||||
from manimlib.utils.bezier import smooth_quadratic_path
|
from manimlib.utils.bezier import smooth_quadratic_path
|
||||||
|
from manimlib.utils.bezier import interpolate
|
||||||
from manimlib.utils.bezier import integer_interpolate
|
from manimlib.utils.bezier import integer_interpolate
|
||||||
from manimlib.utils.bezier import inverse_interpolate
|
from manimlib.utils.bezier import inverse_interpolate
|
||||||
from manimlib.utils.bezier import find_intersection
|
from manimlib.utils.bezier import find_intersection
|
||||||
from manimlib.utils.bezier import partial_quadratic_bezier_points
|
|
||||||
from manimlib.utils.bezier import outer_interpolate
|
from manimlib.utils.bezier import outer_interpolate
|
||||||
|
from manimlib.utils.bezier import partial_quadratic_bezier_points
|
||||||
|
from manimlib.utils.bezier import quadratic_bezier_points_for_arc
|
||||||
from manimlib.utils.color import color_gradient
|
from manimlib.utils.color import color_gradient
|
||||||
from manimlib.utils.color import rgb_to_hex
|
from manimlib.utils.color import rgb_to_hex
|
||||||
from manimlib.utils.iterables import make_even
|
from manimlib.utils.iterables import make_even
|
||||||
|
@ -38,6 +41,8 @@ from manimlib.utils.space_ops import get_unit_normal
|
||||||
from manimlib.utils.space_ops import line_intersects_path
|
from manimlib.utils.space_ops import line_intersects_path
|
||||||
from manimlib.utils.space_ops import midpoint
|
from manimlib.utils.space_ops import midpoint
|
||||||
from manimlib.utils.space_ops import normalize_along_axis
|
from manimlib.utils.space_ops import normalize_along_axis
|
||||||
|
from manimlib.utils.space_ops import rotation_between_vectors
|
||||||
|
from manimlib.utils.space_ops import poly_line_length
|
||||||
from manimlib.utils.space_ops import z_to_vector
|
from manimlib.utils.space_ops import z_to_vector
|
||||||
from manimlib.shader_wrapper import ShaderWrapper
|
from manimlib.shader_wrapper import ShaderWrapper
|
||||||
from manimlib.shader_wrapper import FillShaderWrapper
|
from manimlib.shader_wrapper import FillShaderWrapper
|
||||||
|
@ -234,7 +239,7 @@ class VMobject(Mobject):
|
||||||
stroke_opacity: float | Iterable[float] | None = None,
|
stroke_opacity: float | Iterable[float] | None = None,
|
||||||
stroke_rgba: Vect4 | None = None,
|
stroke_rgba: Vect4 | None = None,
|
||||||
stroke_width: float | Iterable[float] | None = None,
|
stroke_width: float | Iterable[float] | None = None,
|
||||||
stroke_background: bool = True,
|
stroke_background: bool = False,
|
||||||
shading: Tuple[float, float, float] | None = None,
|
shading: Tuple[float, float, float] | None = None,
|
||||||
recurse: bool = True
|
recurse: bool = True
|
||||||
) -> Self:
|
) -> Self:
|
||||||
|
@ -374,7 +379,7 @@ class VMobject(Mobject):
|
||||||
data = self.data if self.has_points() else self._data_defaults
|
data = self.data if self.has_points() else self._data_defaults
|
||||||
return rgb_to_hex(data["stroke_rgba"][0, :3])
|
return rgb_to_hex(data["stroke_rgba"][0, :3])
|
||||||
|
|
||||||
def get_stroke_width(self) -> float | np.ndarray:
|
def get_stroke_width(self) -> float:
|
||||||
data = self.data if self.has_points() else self._data_defaults
|
data = self.data if self.has_points() else self._data_defaults
|
||||||
return data["stroke_width"][0, 0]
|
return data["stroke_width"][0, 0]
|
||||||
|
|
||||||
|
@ -423,7 +428,7 @@ class VMobject(Mobject):
|
||||||
self,
|
self,
|
||||||
anti_alias_width: float = 0,
|
anti_alias_width: float = 0,
|
||||||
fill_border_width: float = 0,
|
fill_border_width: float = 0,
|
||||||
recurse: bool=True
|
recurse: bool = True
|
||||||
) -> Self:
|
) -> Self:
|
||||||
super().apply_depth_test(recurse)
|
super().apply_depth_test(recurse)
|
||||||
self.set_anti_alias_width(anti_alias_width)
|
self.set_anti_alias_width(anti_alias_width)
|
||||||
|
@ -434,9 +439,9 @@ class VMobject(Mobject):
|
||||||
self,
|
self,
|
||||||
anti_alias_width: float = 1.0,
|
anti_alias_width: float = 1.0,
|
||||||
fill_border_width: float = 0.5,
|
fill_border_width: float = 0.5,
|
||||||
recurse: bool=True
|
recurse: bool = True
|
||||||
) -> Self:
|
) -> Self:
|
||||||
super().apply_depth_test(recurse)
|
super().deactivate_depth_test(recurse)
|
||||||
self.set_anti_alias_width(anti_alias_width)
|
self.set_anti_alias_width(anti_alias_width)
|
||||||
self.set_fill(border_width=fill_border_width)
|
self.set_fill(border_width=fill_border_width)
|
||||||
return self
|
return self
|
||||||
|
@ -455,6 +460,9 @@ class VMobject(Mobject):
|
||||||
anchors: Vect3Array,
|
anchors: Vect3Array,
|
||||||
handles: Vect3Array,
|
handles: Vect3Array,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
|
if len(anchors) == 0:
|
||||||
|
self.clear_points()
|
||||||
|
return self
|
||||||
assert(len(anchors) == len(handles) + 1)
|
assert(len(anchors) == len(handles) + 1)
|
||||||
points = resize_array(self.get_points(), 2 * len(anchors) - 1)
|
points = resize_array(self.get_points(), 2 * len(anchors) - 1)
|
||||||
points[0::2] = anchors
|
points[0::2] = anchors
|
||||||
|
@ -543,6 +551,26 @@ class VMobject(Mobject):
|
||||||
self.add_cubic_bezier_curve_to(new_handle, handle, point)
|
self.add_cubic_bezier_curve_to(new_handle, handle, point)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def add_arc_to(self, point: Vect3, angle: float, n_components: int | None = None, threshold: float = 1e-3) -> Self:
|
||||||
|
self.throw_error_if_no_points()
|
||||||
|
if abs(angle) < threshold:
|
||||||
|
self.add_line_to(point)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# Assign default value for n_components
|
||||||
|
if n_components is None:
|
||||||
|
n_components = int(np.ceil(8 * abs(angle) / TAU))
|
||||||
|
|
||||||
|
arc_points = quadratic_bezier_points_for_arc(angle, n_components)
|
||||||
|
target_vect = point - self.get_end()
|
||||||
|
curr_vect = arc_points[-1] - arc_points[0]
|
||||||
|
|
||||||
|
arc_points = arc_points @ rotation_between_vectors(curr_vect, target_vect).T
|
||||||
|
arc_points *= get_norm(target_vect) / get_norm(curr_vect)
|
||||||
|
arc_points += (self.get_end() - arc_points[0])
|
||||||
|
self.append_points(arc_points[1:])
|
||||||
|
return self
|
||||||
|
|
||||||
def has_new_path_started(self) -> bool:
|
def has_new_path_started(self) -> bool:
|
||||||
points = self.get_points()
|
points = self.get_points()
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
|
@ -642,6 +670,8 @@ class VMobject(Mobject):
|
||||||
|
|
||||||
def change_anchor_mode(self, mode: str) -> Self:
|
def change_anchor_mode(self, mode: str) -> Self:
|
||||||
assert(mode in ("jagged", "approx_smooth", "true_smooth"))
|
assert(mode in ("jagged", "approx_smooth", "true_smooth"))
|
||||||
|
if self.get_num_points() == 0:
|
||||||
|
return self
|
||||||
subpaths = self.get_subpaths()
|
subpaths = self.get_subpaths()
|
||||||
self.clear_points()
|
self.clear_points()
|
||||||
for subpath in subpaths:
|
for subpath in subpaths:
|
||||||
|
@ -745,8 +775,8 @@ class VMobject(Mobject):
|
||||||
return self.get_subpaths_from_points(self.get_points())
|
return self.get_subpaths_from_points(self.get_points())
|
||||||
|
|
||||||
def get_nth_curve_points(self, n: int) -> Vect3Array:
|
def get_nth_curve_points(self, n: int) -> Vect3Array:
|
||||||
assert(n < self.get_num_curves())
|
assert n < self.get_num_curves()
|
||||||
return self.get_points()[2 * n : 2 * n + 3]
|
return self.get_points()[2 * n:2 * n + 3]
|
||||||
|
|
||||||
def get_nth_curve_function(self, n: int) -> Callable[[float], Vect3]:
|
def get_nth_curve_function(self, n: int) -> Callable[[float], Vect3]:
|
||||||
return bezier(self.get_nth_curve_points(n))
|
return bezier(self.get_nth_curve_points(n))
|
||||||
|
@ -761,12 +791,14 @@ class VMobject(Mobject):
|
||||||
curve_func = self.get_nth_curve_function(n)
|
curve_func = self.get_nth_curve_function(n)
|
||||||
return curve_func(residue)
|
return curve_func(residue)
|
||||||
|
|
||||||
def point_from_proportion(self, alpha: float) -> Vect3:
|
def curve_and_prop_of_partial_point(self, alpha) -> Tuple[int, float]:
|
||||||
if alpha <= 0:
|
"""
|
||||||
return self.get_start()
|
If you want a point a proportion alpha along the curve, this
|
||||||
elif alpha >= 1:
|
gives you the index of the appropriate bezier curve, together
|
||||||
return self.get_end()
|
with the proportion along that curve you'd need to travel
|
||||||
|
"""
|
||||||
|
if alpha == 0:
|
||||||
|
return (0, 0.0)
|
||||||
partials: list[float] = [0]
|
partials: list[float] = [0]
|
||||||
for tup in self.get_bezier_tuples():
|
for tup in self.get_bezier_tuples():
|
||||||
if self.consider_points_equal(tup[0], tup[1]):
|
if self.consider_points_equal(tup[0], tup[1]):
|
||||||
|
@ -778,14 +810,24 @@ class VMobject(Mobject):
|
||||||
partials.append(partials[-1] + arclen)
|
partials.append(partials[-1] + arclen)
|
||||||
full = partials[-1]
|
full = partials[-1]
|
||||||
if full == 0:
|
if full == 0:
|
||||||
return self.get_start()
|
return len(partials), 1.0
|
||||||
# First index where the partial length is more than alpha times the full length
|
# First index where the partial length is more than alpha times the full length
|
||||||
i = next(
|
index = next(
|
||||||
(i for i, x in enumerate(partials) if x >= full * alpha),
|
(i for i, x in enumerate(partials) if x >= full * alpha),
|
||||||
len(partials) # Default
|
len(partials) - 1 # Default
|
||||||
)
|
)
|
||||||
residue = float(inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha))
|
residue = float(inverse_interpolate(
|
||||||
return self.get_nth_curve_function(i - 1)(residue)
|
partials[index - 1] / full, partials[index] / full, alpha
|
||||||
|
))
|
||||||
|
return index - 1, residue
|
||||||
|
|
||||||
|
def point_from_proportion(self, alpha: float) -> Vect3:
|
||||||
|
if alpha <= 0:
|
||||||
|
return self.get_start()
|
||||||
|
elif alpha >= 1:
|
||||||
|
return self.get_end()
|
||||||
|
index, residue = self.curve_and_prop_of_partial_point(alpha)
|
||||||
|
return self.get_nth_curve_function(index)(residue)
|
||||||
|
|
||||||
def get_anchors_and_handles(self) -> list[Vect3]:
|
def get_anchors_and_handles(self) -> list[Vect3]:
|
||||||
"""
|
"""
|
||||||
|
@ -814,14 +856,16 @@ class VMobject(Mobject):
|
||||||
return np.vstack(new_points)
|
return np.vstack(new_points)
|
||||||
|
|
||||||
def get_arc_length(self, n_sample_points: int | None = None) -> float:
|
def get_arc_length(self, n_sample_points: int | None = None) -> float:
|
||||||
if n_sample_points is None:
|
if n_sample_points is not None:
|
||||||
n_sample_points = 4 * self.get_num_curves() + 1
|
points = np.array([
|
||||||
points = np.array([
|
self.quick_point_from_proportion(a)
|
||||||
self.point_from_proportion(a)
|
for a in np.linspace(0, 1, n_sample_points)
|
||||||
for a in np.linspace(0, 1, n_sample_points)
|
])
|
||||||
])
|
return poly_line_length(points)
|
||||||
diffs = points[1:] - points[:-1]
|
points = self.get_points()
|
||||||
return sum(map(get_norm, diffs))
|
inner_len = poly_line_length(points[::2])
|
||||||
|
outer_len = poly_line_length(points)
|
||||||
|
return interpolate(inner_len, outer_len, 1 / 3)
|
||||||
|
|
||||||
def get_area_vector(self) -> Vect3:
|
def get_area_vector(self) -> Vect3:
|
||||||
# Returns a vector whose length is the area bound by
|
# Returns a vector whose length is the area bound by
|
||||||
|
|
|
@ -1021,9 +1021,10 @@ class EndScene(Exception):
|
||||||
class ThreeDScene(Scene):
|
class ThreeDScene(Scene):
|
||||||
samples = 4
|
samples = 4
|
||||||
default_frame_orientation = (-30, 70)
|
default_frame_orientation = (-30, 70)
|
||||||
|
always_depth_test = True
|
||||||
|
|
||||||
def add(self, *mobjects, set_depth_test: bool = True):
|
def add(self, *mobjects, set_depth_test: bool = True):
|
||||||
for mob in mobjects:
|
for mob in mobjects:
|
||||||
if set_depth_test and not mob.is_fixed_in_frame():
|
if set_depth_test and not mob.is_fixed_in_frame() and self.always_depth_test:
|
||||||
mob.apply_depth_test()
|
mob.apply_depth_test()
|
||||||
super().add(*mobjects)
|
super().add(*mobjects)
|
||||||
|
|
|
@ -42,6 +42,7 @@ class SceneFileWriter(object):
|
||||||
# Where should this be written
|
# Where should this be written
|
||||||
output_directory: str | None = None,
|
output_directory: str | None = None,
|
||||||
file_name: str | None = None,
|
file_name: str | None = None,
|
||||||
|
subdirectory_for_videos: bool = False,
|
||||||
open_file_upon_completion: bool = False,
|
open_file_upon_completion: bool = False,
|
||||||
show_file_location_upon_completion: bool = False,
|
show_file_location_upon_completion: bool = False,
|
||||||
quiet: bool = False,
|
quiet: bool = False,
|
||||||
|
@ -49,8 +50,8 @@ class SceneFileWriter(object):
|
||||||
progress_description_len: int = 40,
|
progress_description_len: int = 40,
|
||||||
video_codec: str = "libx264",
|
video_codec: str = "libx264",
|
||||||
pixel_format: str = "yuv420p",
|
pixel_format: str = "yuv420p",
|
||||||
saturation: float = 1.7,
|
saturation: float = 1.0,
|
||||||
gamma: float = 1.2,
|
gamma: float = 1.0,
|
||||||
):
|
):
|
||||||
self.scene: Scene = scene
|
self.scene: Scene = scene
|
||||||
self.write_to_movie = write_to_movie
|
self.write_to_movie = write_to_movie
|
||||||
|
@ -63,6 +64,7 @@ class SceneFileWriter(object):
|
||||||
self.output_directory = output_directory
|
self.output_directory = output_directory
|
||||||
self.file_name = file_name
|
self.file_name = file_name
|
||||||
self.open_file_upon_completion = open_file_upon_completion
|
self.open_file_upon_completion = open_file_upon_completion
|
||||||
|
self.subdirectory_for_videos = subdirectory_for_videos
|
||||||
self.show_file_location_upon_completion = show_file_location_upon_completion
|
self.show_file_location_upon_completion = show_file_location_upon_completion
|
||||||
self.quiet = quiet
|
self.quiet = quiet
|
||||||
self.total_frames = total_frames
|
self.total_frames = total_frames
|
||||||
|
@ -88,7 +90,10 @@ class SceneFileWriter(object):
|
||||||
image_file = add_extension_if_not_present(scene_name, ".png")
|
image_file = add_extension_if_not_present(scene_name, ".png")
|
||||||
self.image_file_path = os.path.join(image_dir, image_file)
|
self.image_file_path = os.path.join(image_dir, image_file)
|
||||||
if self.write_to_movie:
|
if self.write_to_movie:
|
||||||
movie_dir = guarantee_existence(os.path.join(out_dir, "videos"))
|
if self.subdirectory_for_videos:
|
||||||
|
movie_dir = guarantee_existence(os.path.join(out_dir, "videos"))
|
||||||
|
else:
|
||||||
|
movie_dir = guarantee_existence(out_dir)
|
||||||
movie_file = add_extension_if_not_present(scene_name, self.movie_file_extension)
|
movie_file = add_extension_if_not_present(scene_name, self.movie_file_extension)
|
||||||
self.movie_file_path = os.path.join(movie_dir, movie_file)
|
self.movie_file_path = os.path.join(movie_dir, movie_file)
|
||||||
if self.break_into_partial_movies:
|
if self.break_into_partial_movies:
|
||||||
|
|
|
@ -56,6 +56,7 @@ class ShaderWrapper(object):
|
||||||
|
|
||||||
self.init_program_code()
|
self.init_program_code()
|
||||||
self.init_program()
|
self.init_program()
|
||||||
|
self.texture_names_to_ids = dict()
|
||||||
if texture_paths is not None:
|
if texture_paths is not None:
|
||||||
self.init_textures(texture_paths)
|
self.init_textures(texture_paths)
|
||||||
self.init_vao()
|
self.init_vao()
|
||||||
|
@ -82,11 +83,10 @@ class ShaderWrapper(object):
|
||||||
self.vert_format = moderngl.detect_format(self.program, self.vert_attributes)
|
self.vert_format = moderngl.detect_format(self.program, self.vert_attributes)
|
||||||
|
|
||||||
def init_textures(self, texture_paths: dict[str, str]):
|
def init_textures(self, texture_paths: dict[str, str]):
|
||||||
names_to_ids = {
|
self.texture_names_to_ids = {
|
||||||
name: get_texture_id(image_path_to_texture(path, self.ctx))
|
name: get_texture_id(image_path_to_texture(path, self.ctx))
|
||||||
for name, path in texture_paths.items()
|
for name, path in texture_paths.items()
|
||||||
}
|
}
|
||||||
self.update_program_uniforms(names_to_ids)
|
|
||||||
|
|
||||||
def init_vao(self):
|
def init_vao(self):
|
||||||
self.vbo = None
|
self.vbo = None
|
||||||
|
@ -138,6 +138,7 @@ class ShaderWrapper(object):
|
||||||
self.mobject_uniforms,
|
self.mobject_uniforms,
|
||||||
self.depth_test,
|
self.depth_test,
|
||||||
self.render_primitive,
|
self.render_primitive,
|
||||||
|
self.texture_names_to_ids,
|
||||||
]))
|
]))
|
||||||
|
|
||||||
def refresh_id(self) -> None:
|
def refresh_id(self) -> None:
|
||||||
|
@ -224,8 +225,9 @@ class ShaderWrapper(object):
|
||||||
def update_program_uniforms(self, camera_uniforms: UniformDict):
|
def update_program_uniforms(self, camera_uniforms: UniformDict):
|
||||||
if self.program is None:
|
if self.program is None:
|
||||||
return
|
return
|
||||||
for name, value in (*self.mobject_uniforms.items(), *camera_uniforms.items()):
|
for uniforms in [self.mobject_uniforms, camera_uniforms, self.texture_names_to_ids]:
|
||||||
set_program_uniform(self.program, name, value)
|
for name, value in uniforms.items():
|
||||||
|
set_program_uniform(self.program, name, value)
|
||||||
|
|
||||||
def get_vertex_buffer_object(self, refresh: bool = True):
|
def get_vertex_buffer_object(self, refresh: bool = True):
|
||||||
if refresh:
|
if refresh:
|
||||||
|
|
|
@ -5,7 +5,11 @@ uniform mat4 perspective;
|
||||||
|
|
||||||
in vec4 color;
|
in vec4 color;
|
||||||
in float scaled_aaw;
|
in float scaled_aaw;
|
||||||
in vec3 v_point;
|
in vec3 point;
|
||||||
|
in vec3 to_cam;
|
||||||
|
in vec3 center;
|
||||||
|
in float radius;
|
||||||
|
in vec2 uv_coords;
|
||||||
|
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
@ -13,9 +17,8 @@ out vec4 frag_color;
|
||||||
#INSERT finalize_color.glsl
|
#INSERT finalize_color.glsl
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 vect = 2.0 * gl_PointCoord.xy - vec2(1.0);
|
float r = length(uv_coords.xy);
|
||||||
float r = length(vect);
|
if(r > 1.0) discard;
|
||||||
if(r > 1.0 + scaled_aaw) discard;
|
|
||||||
|
|
||||||
frag_color = color;
|
frag_color = color;
|
||||||
|
|
||||||
|
@ -24,9 +27,9 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(shading != vec3(0.0)){
|
if(shading != vec3(0.0)){
|
||||||
vec3 normal = vec3(vect, sqrt(1 - r * r));
|
vec3 point_3d = point + radius * sqrt(1 - r * r) * to_cam;
|
||||||
normal = (perspective * vec4(normal, 0.0)).xyz;
|
vec3 normal = normalize(point_3d - center);
|
||||||
frag_color = finalize_color(frag_color, v_point, normal);
|
frag_color = finalize_color(frag_color, point_3d, normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
frag_color.a *= smoothstep(1.0, 1.0 - scaled_aaw, r);
|
frag_color.a *= smoothstep(1.0, 1.0 - scaled_aaw, r);
|
||||||
|
|
44
manimlib/shaders/true_dot/geom.glsl
Normal file
44
manimlib/shaders/true_dot/geom.glsl
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
layout (points) in;
|
||||||
|
layout (triangle_strip, max_vertices = 4) out;
|
||||||
|
|
||||||
|
uniform float pixel_size;
|
||||||
|
uniform float anti_alias_width;
|
||||||
|
uniform float frame_scale;
|
||||||
|
uniform vec3 camera_position;
|
||||||
|
|
||||||
|
in vec3 v_point[1];
|
||||||
|
in float v_radius[1];
|
||||||
|
in vec4 v_rgba[1];
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
out float scaled_aaw;
|
||||||
|
out vec3 point;
|
||||||
|
out vec3 to_cam;
|
||||||
|
out vec3 center;
|
||||||
|
out float radius;
|
||||||
|
out vec2 uv_coords;
|
||||||
|
|
||||||
|
#INSERT emit_gl_Position.glsl
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
color = v_rgba[0];
|
||||||
|
radius = v_radius[0];
|
||||||
|
center = v_point[0];
|
||||||
|
scaled_aaw = (anti_alias_width * pixel_size) / v_radius[0];
|
||||||
|
|
||||||
|
to_cam = normalize(camera_position - v_point[0]);
|
||||||
|
vec3 right = v_radius[0] * normalize(cross(vec3(0, 1, 1), to_cam));
|
||||||
|
vec3 up = v_radius[0] * normalize(cross(to_cam, right));
|
||||||
|
|
||||||
|
for(int i = -1; i < 2; i += 2){
|
||||||
|
for(int j = -1; j < 2; j += 2){
|
||||||
|
point = v_point[0] + i * right + j * up;
|
||||||
|
uv_coords = vec2(i, j);
|
||||||
|
emit_gl_Position(point);
|
||||||
|
EmitVertex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndPrimitive();
|
||||||
|
}
|
|
@ -1,26 +1,16 @@
|
||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
uniform float pixel_size;
|
|
||||||
uniform float anti_alias_width;
|
|
||||||
|
|
||||||
in vec3 point;
|
in vec3 point;
|
||||||
in float radius;
|
in float radius;
|
||||||
in vec4 rgba;
|
in vec4 rgba;
|
||||||
|
|
||||||
out vec4 color;
|
|
||||||
out float scaled_aaw;
|
|
||||||
out vec3 v_point;
|
out vec3 v_point;
|
||||||
out vec3 light_pos;
|
out float v_radius;
|
||||||
|
out vec4 v_rgba;
|
||||||
|
|
||||||
#INSERT emit_gl_Position.glsl
|
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
v_point = point;
|
v_point = point;
|
||||||
color = rgba;
|
v_radius = radius;
|
||||||
scaled_aaw = (anti_alias_width * pixel_size) / radius;
|
v_rgba = rgba;
|
||||||
|
|
||||||
emit_gl_Position(point);
|
|
||||||
float z = -10 * gl_Position.z;
|
|
||||||
float scaled_radius = radius * 1.0 / (1.0 - z);
|
|
||||||
gl_PointSize = 2 * ((scaled_radius / pixel_size) + anti_alias_width);
|
|
||||||
}
|
}
|
|
@ -171,6 +171,16 @@ def match_interpolate(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def quadratic_bezier_points_for_arc(angle: float, n_components: int = 8):
|
||||||
|
n_points = 2 * n_components + 1
|
||||||
|
angles = np.linspace(0, angle, n_points)
|
||||||
|
points = np.array([np.cos(angles), np.sin(angles), np.zeros(n_points)]).T
|
||||||
|
# Adjust handles
|
||||||
|
theta = angle / n_components
|
||||||
|
points[1::2] /= np.cos(theta / 2)
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
def approx_smooth_quadratic_bezier_handles(
|
def approx_smooth_quadratic_bezier_handles(
|
||||||
points: FloatArray
|
points: FloatArray
|
||||||
) -> FloatArray:
|
) -> FloatArray:
|
||||||
|
|
|
@ -136,6 +136,18 @@ def array_is_constant(arr: np.ndarray) -> bool:
|
||||||
return len(arr) > 0 and (arr == arr[0]).all()
|
return len(arr) > 0 and (arr == arr[0]).all()
|
||||||
|
|
||||||
|
|
||||||
|
def cartesian_product(*arrays: np.ndarray):
|
||||||
|
"""
|
||||||
|
Copied from https://stackoverflow.com/a/11146645
|
||||||
|
"""
|
||||||
|
la = len(arrays)
|
||||||
|
dtype = np.result_type(*arrays)
|
||||||
|
arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)
|
||||||
|
for i, a in enumerate(np.ix_(*arrays)):
|
||||||
|
arr[..., i] = a
|
||||||
|
return arr.reshape(-1, la)
|
||||||
|
|
||||||
|
|
||||||
def hash_obj(obj: object) -> int:
|
def hash_obj(obj: object) -> int:
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
return hash(tuple(sorted([
|
return hash(tuple(sorted([
|
||||||
|
|
|
@ -21,6 +21,7 @@ if TYPE_CHECKING:
|
||||||
from moderngl.framebuffer import Framebuffer
|
from moderngl.framebuffer import Framebuffer
|
||||||
|
|
||||||
|
|
||||||
|
# Global maps updated as textures are allocated
|
||||||
ID_TO_TEXTURE: dict[int, moderngl.Texture] = dict()
|
ID_TO_TEXTURE: dict[int, moderngl.Texture] = dict()
|
||||||
PROGRAM_UNIFORM_MIRRORS: dict[int, dict[str, float | tuple]] = dict()
|
PROGRAM_UNIFORM_MIRRORS: dict[int, dict[str, float | tuple]] = dict()
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ def get_shader_program(
|
||||||
vertex_shader: str,
|
vertex_shader: str,
|
||||||
fragment_shader: Optional[str] = None,
|
fragment_shader: Optional[str] = None,
|
||||||
geometry_shader: Optional[str] = None,
|
geometry_shader: Optional[str] = None,
|
||||||
) -> moderngl.Program:
|
) -> moderngl.Program:
|
||||||
return ctx.program(
|
return ctx.program(
|
||||||
vertex_shader=vertex_shader,
|
vertex_shader=vertex_shader,
|
||||||
fragment_shader=fragment_shader,
|
fragment_shader=fragment_shader,
|
||||||
|
@ -74,7 +75,7 @@ def set_program_uniform(
|
||||||
of previously set uniforms for that program so that it
|
of previously set uniforms for that program so that it
|
||||||
doesn't needlessly reset it, requiring an exchange with gpu
|
doesn't needlessly reset it, requiring an exchange with gpu
|
||||||
memory, if it sees the same value again.
|
memory, if it sees the same value again.
|
||||||
|
|
||||||
Returns True if changed the program, False if it left it as is.
|
Returns True if changed the program, False if it left it as is.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -134,7 +135,6 @@ def get_colormap_code(rgb_list: Sequence[float]) -> str:
|
||||||
return f"vec3[{len(rgb_list)}]({data})"
|
return f"vec3[{len(rgb_list)}]({data})"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]:
|
def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -61,6 +61,13 @@ def normalize(
|
||||||
return np.zeros(len(vect))
|
return np.zeros(len(vect))
|
||||||
|
|
||||||
|
|
||||||
|
def poly_line_length(points):
|
||||||
|
"""
|
||||||
|
Return the sum of the lengths between adjacent points
|
||||||
|
"""
|
||||||
|
diffs = points[1:] - points[:-1]
|
||||||
|
return np.sqrt((diffs**2).sum(1)).sum()
|
||||||
|
|
||||||
# Operations related to rotation
|
# Operations related to rotation
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,7 +206,7 @@ def normalize_along_axis(
|
||||||
) -> np.ndarray:
|
) -> np.ndarray:
|
||||||
norms = np.sqrt((array * array).sum(axis))
|
norms = np.sqrt((array * array).sum(axis))
|
||||||
norms[norms == 0] = 1
|
norms[norms == 0] = 1
|
||||||
return (array.T / norms).T
|
return array / norms[:, np.newaxis]
|
||||||
|
|
||||||
|
|
||||||
def get_unit_normal(
|
def get_unit_normal(
|
||||||
|
|
|
@ -16,7 +16,7 @@ def num_tex_symbols(tex: str) -> int:
|
||||||
# \begin{array}{cc}, etc.
|
# \begin{array}{cc}, etc.
|
||||||
pattern = "|".join(
|
pattern = "|".join(
|
||||||
rf"(\\{s})" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
|
rf"(\\{s})" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
|
||||||
for s in ["begin", "end", "phantom", "text"]
|
for s in ["begin", "end", "phantom"]
|
||||||
)
|
)
|
||||||
tex = re.sub(pattern, "", tex)
|
tex = re.sub(pattern, "", tex)
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ TEX_TO_SYMBOL_COUNT = {
|
||||||
R"\div": 2,
|
R"\div": 2,
|
||||||
R"\doteq": 2,
|
R"\doteq": 2,
|
||||||
R"\dotfill": 0,
|
R"\dotfill": 0,
|
||||||
|
R"\dots": 3,
|
||||||
R"\emph": 0,
|
R"\emph": 0,
|
||||||
R"\exp": 3,
|
R"\exp": 3,
|
||||||
R"\fbox": 4,
|
R"\fbox": 4,
|
||||||
|
@ -102,6 +103,7 @@ TEX_TO_SYMBOL_COUNT = {
|
||||||
R"\makebox": 0,
|
R"\makebox": 0,
|
||||||
R"\mapsto": 2,
|
R"\mapsto": 2,
|
||||||
R"\markright": 0,
|
R"\markright": 0,
|
||||||
|
R"\mathds": 0,
|
||||||
R"\max": 3,
|
R"\max": 3,
|
||||||
R"\mbox": 0,
|
R"\mbox": 0,
|
||||||
R"\medskip": 0,
|
R"\medskip": 0,
|
||||||
|
@ -160,6 +162,7 @@ TEX_TO_SYMBOL_COUNT = {
|
||||||
R"\sup": 3,
|
R"\sup": 3,
|
||||||
R"\tan": 3,
|
R"\tan": 3,
|
||||||
R"\tanh": 4,
|
R"\tanh": 4,
|
||||||
|
R"\text": 0,
|
||||||
R"\textbf": 0,
|
R"\textbf": 0,
|
||||||
R"\textfraction": 2,
|
R"\textfraction": 2,
|
||||||
R"\textstyle": 0,
|
R"\textstyle": 0,
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Window(PygletWindow):
|
||||||
self,
|
self,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
size: tuple[int, int] = (1280, 720),
|
size: tuple[int, int] = (1280, 720),
|
||||||
samples = 0
|
samples: int = 0
|
||||||
):
|
):
|
||||||
scene.window = self
|
scene.window = self
|
||||||
super().__init__(size=size, samples=samples)
|
super().__init__(size=size, samples=samples)
|
||||||
|
@ -47,9 +47,12 @@ class Window(PygletWindow):
|
||||||
self.to_default_position()
|
self.to_default_position()
|
||||||
|
|
||||||
def to_default_position(self):
|
def to_default_position(self):
|
||||||
self.size = self.default_size
|
|
||||||
self.position = self.default_position
|
self.position = self.default_position
|
||||||
self.swap_buffers()
|
# Hack. Sometimes, namely when configured to open in a separate window,
|
||||||
|
# the window needs to be resized to display correctly.
|
||||||
|
w, h = self.default_size
|
||||||
|
self.size = (w - 1, h - 1)
|
||||||
|
self.size = (w, h)
|
||||||
|
|
||||||
def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]:
|
def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]:
|
||||||
custom_position = get_customization()["window_position"]
|
custom_position = get_customization()["window_position"]
|
||||||
|
|
Loading…
Add table
Reference in a new issue