Kill CONFIG in vectorized_mobject.py

This commit is contained in:
Grant Sanderson 2022-12-15 09:19:05 -08:00
parent 97a5861ccf
commit a715a5bc3f

View file

@ -42,57 +42,67 @@ from manimlib.shader_wrapper import ShaderWrapper
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable, Iterable, Sequence
from typing import Callable, Iterable, Sequence, Tuple
import numpy.typing as npt
from manimlib.constants import ManimColor
from manimlib.constants import ManimColor, np_vector
class VMobject(Mobject):
CONFIG = {
"fill_color": None,
"fill_opacity": 0.0,
"stroke_color": None,
"stroke_opacity": 1.0,
"stroke_width": DEFAULT_STROKE_WIDTH,
"draw_stroke_behind_fill": False,
# Indicates that it will not be displayed, but
# that it should count in parent mobject's path
"pre_function_handle_to_anchor_scale_factor": 0.01,
"make_smooth_after_applying_functions": False,
"background_image_file": None,
# This is within a pixel
# TODO, do we care about accounting for
# varying zoom levels?
"tolerance_for_point_equality": 1e-8,
"n_points_per_curve": 3,
"long_lines": False,
# For shaders
"stroke_shader_folder": "quadratic_bezier_stroke",
"fill_shader_folder": "quadratic_bezier_fill",
# Could also be "bevel", "miter", "round"
"joint_type": "auto",
"flat_stroke": False,
"render_primitive": moderngl.TRIANGLES,
"fill_dtype": [
('point', np.float32, (3,)),
('unit_normal', np.float32, (3,)),
('color', np.float32, (4,)),
('vert_index', np.float32, (1,)),
],
"stroke_dtype": [
("point", np.float32, (3,)),
("prev_point", np.float32, (3,)),
("next_point", np.float32, (3,)),
('unit_normal', np.float32, (3,)),
("stroke_width", np.float32, (1,)),
("color", np.float32, (4,)),
]
}
n_points_per_curve: int = 3
stroke_shader_folder: str = "quadratic_bezier_stroke"
fill_shader_folder: str = "quadratic_bezier_fill"
fill_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
('point', np.float32, (3,)),
('unit_normal', np.float32, (3,)),
('color', np.float32, (4,)),
('vert_index', np.float32, (1,)),
]
stroke_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
("point", np.float32, (3,)),
("prev_point", np.float32, (3,)),
("next_point", np.float32, (3,)),
('unit_normal', np.float32, (3,)),
("stroke_width", np.float32, (1,)),
("color", np.float32, (4,)),
]
render_primitive: int = moderngl.TRIANGLES
pre_function_handle_to_anchor_scale_factor: float = 0.01
make_smooth_after_applying_functions: bool = False
# TODO, do we care about accounting for varying zoom levels?
tolerance_for_point_equality: float = 1e-8
def __init__(
self,
color: ManimColor = None, # If set, this will override stroke_color and fill_color
fill_color: ManimColor = WHITE,
fill_opacity: float = 0.0,
stroke_color: ManimColor = GREY_C,
stroke_opacity: float = 1.0,
stroke_width: float = DEFAULT_STROKE_WIDTH,
draw_stroke_behind_fill: bool = False,
background_image_file: str | None = None,
long_lines: bool = False,
# Could also be "bevel", "miter", "round"
joint_type: str = "auto",
flat_stroke: bool = False,
**kwargs
):
self.fill_color = color or fill_color
self.fill_opacity = fill_opacity
self.stroke_color = color or stroke_color
self.stroke_opacity = stroke_opacity
self.stroke_width = stroke_width
self.draw_stroke_behind_fill = draw_stroke_behind_fill
self.background_image_file = background_image_file
self.long_lines = long_lines
self.joint_type = joint_type
self.flat_stroke = flat_stroke
def __init__(self, **kwargs):
self.needs_new_triangulation = True
self.triangulation = np.zeros(0, dtype='i4')
super().__init__(**kwargs)
def get_group_class(self):
@ -108,32 +118,40 @@ class VMobject(Mobject):
"unit_normal": np.zeros((1, 3))
})
# These are here just to make type checkers happy
def get_family(self, recurse: bool = True) -> list[VMobject]:
return super().get_family(recurse)
def family_members_with_points(self) -> list[VMobject]:
return super().family_members_with_points()
def replicate(self, n: int) -> VGroup:
return super().replicate(n)
def get_grid(self, *args, **kwargs) -> VGroup:
return super().get_grid(*args, **kwargs)
# Colors
def init_colors(self):
self.set_fill(
color=self.fill_color or self.color,
color=self.fill_color,
opacity=self.fill_opacity,
)
self.set_stroke(
color=self.stroke_color or self.color,
color=self.stroke_color,
width=self.stroke_width,
opacity=self.stroke_opacity,
background=self.draw_stroke_behind_fill,
)
self.set_gloss(self.gloss)
self.set_flat_stroke(self.flat_stroke)
self.color = self.get_color()
return self
def set_rgba_array(
self,
rgba_array: npt.ArrayLike,
name: str = None,
rgba_array: np_vector,
name: str | None = None,
recurse: bool = False
):
if name is None:
@ -147,7 +165,7 @@ class VMobject(Mobject):
def set_fill(
self,
color: ManimColor | Iterable[ManimColor] | None = None,
color: ManimColor | Iterable[ManimColor] = None,
opacity: float | Iterable[float] | None = None,
recurse: bool = True
):
@ -156,7 +174,7 @@ class VMobject(Mobject):
def set_stroke(
self,
color: ManimColor | Iterable[ManimColor] | None = None,
color: ManimColor | Iterable[ManimColor] = None,
width: float | Iterable[float] | None = None,
opacity: float | Iterable[float] | None = None,
background: bool | None = None,
@ -196,10 +214,10 @@ class VMobject(Mobject):
self,
fill_color: ManimColor | Iterable[ManimColor] | None = None,
fill_opacity: float | Iterable[float] | None = None,
fill_rgba: npt.ArrayLike | None = None,
fill_rgba: np_vector | None = None,
stroke_color: ManimColor | Iterable[ManimColor] | None = None,
stroke_opacity: float | Iterable[float] | None = None,
stroke_rgba: npt.ArrayLike | None = None,
stroke_rgba: np_vector | None = None,
stroke_width: float | Iterable[float] | None = None,
stroke_background: bool = True,
reflectiveness: float | None = None,
@ -218,7 +236,7 @@ class VMobject(Mobject):
)
if stroke_rgba is not None:
mob.data['stroke_rgba'] = resize_with_interpolation(stroke_rgba, len(fill_rgba))
mob.data['stroke_rgba'] = resize_with_interpolation(stroke_rgba, len(stroke_rgba))
mob.set_stroke(
width=stroke_width,
background=stroke_background,
@ -305,7 +323,7 @@ class VMobject(Mobject):
for rgba in self.data['fill_rgba']
]
def get_fill_opacities(self) -> np.ndarray:
def get_fill_opacities(self) -> np_vector:
return self.data['fill_rgba'][:, 3]
def get_stroke_colors(self) -> list[str]:
@ -314,10 +332,10 @@ class VMobject(Mobject):
for rgba in self.data['stroke_rgba']
]
def get_stroke_opacities(self) -> np.ndarray:
def get_stroke_opacities(self) -> np_vector:
return self.data['stroke_rgba'][:, 3]
def get_stroke_widths(self) -> np.ndarray:
def get_stroke_widths(self) -> np_vector:
return self.data['stroke_width'][:, 0]
# TODO, it's weird for these to return the first of various lists
@ -339,7 +357,7 @@ class VMobject(Mobject):
def get_stroke_color(self) -> str:
return self.get_stroke_colors()[0]
def get_stroke_width(self) -> float | np.ndarray:
def get_stroke_width(self) -> float | np_vector:
return self.get_stroke_widths()[0]
def get_stroke_opacity(self) -> float:
@ -380,9 +398,9 @@ class VMobject(Mobject):
# Points
def set_anchors_and_handles(
self,
anchors1: np.ndarray,
handles: np.ndarray,
anchors2: np.ndarray
anchors1: np_vector,
handles: np_vector,
anchors2: np_vector
):
assert(len(anchors1) == len(handles) == len(anchors2))
nppc = self.n_points_per_curve
@ -393,26 +411,26 @@ class VMobject(Mobject):
self.set_points(new_points)
return self
def start_new_path(self, point: np.ndarray):
def start_new_path(self, point: np_vector):
assert(self.get_num_points() % self.n_points_per_curve == 0)
self.append_points([point])
return self
def add_cubic_bezier_curve(
self,
anchor1: npt.ArrayLike,
handle1: npt.ArrayLike,
handle2: npt.ArrayLike,
anchor2: npt.ArrayLike
anchor1: np_vector,
handle1: np_vector,
handle2: np_vector,
anchor2: np_vector
):
new_points = get_quadratic_approximation_of_cubic(anchor1, handle1, handle2, anchor2)
self.append_points(new_points)
def add_cubic_bezier_curve_to(
self,
handle1: npt.ArrayLike,
handle2: npt.ArrayLike,
anchor: npt.ArrayLike
handle1: np_vector,
handle2: np_vector,
anchor: np_vector
):
"""
Add cubic bezier curve to the path.
@ -426,14 +444,14 @@ class VMobject(Mobject):
else:
self.append_points(quadratic_approx)
def add_quadratic_bezier_curve_to(self, handle: np.ndarray, anchor: np.ndarray):
def add_quadratic_bezier_curve_to(self, handle: np_vector, anchor: np_vector):
self.throw_error_if_no_points()
if self.has_new_path_started():
self.append_points([handle, anchor])
else:
self.append_points([self.get_last_point(), handle, anchor])
def add_line_to(self, point: np.ndarray):
def add_line_to(self, point: np_vector):
end = self.get_points()[-1]
alphas = np.linspace(0, 1, self.n_points_per_curve)
if self.long_lines:
@ -455,7 +473,7 @@ class VMobject(Mobject):
self.append_points(points)
return self
def add_smooth_curve_to(self, point: np.ndarray):
def add_smooth_curve_to(self, point: np_vector):
if self.has_new_path_started():
self.add_line_to(point)
else:
@ -464,7 +482,7 @@ class VMobject(Mobject):
self.add_quadratic_bezier_curve_to(new_handle, point)
return self
def add_smooth_cubic_curve_to(self, handle: np.ndarray, point: np.ndarray):
def add_smooth_cubic_curve_to(self, handle: np_vector, point: np_vector):
self.throw_error_if_no_points()
if self.get_num_points() == 1:
new_handle = self.get_points()[-1]
@ -475,10 +493,10 @@ class VMobject(Mobject):
def has_new_path_started(self) -> bool:
return self.get_num_points() % self.n_points_per_curve == 1
def get_last_point(self) -> np.ndarray:
def get_last_point(self) -> np_vector:
return self.get_points()[-1]
def get_reflection_of_last_handle(self) -> np.ndarray:
def get_reflection_of_last_handle(self) -> np_vector:
points = self.get_points()
return 2 * points[-1] - points[-2]
@ -513,12 +531,12 @@ class VMobject(Mobject):
vmob.set_points(np.vstack(new_points))
return self
def add_points_as_corners(self, points: Iterable[np.ndarray]):
def add_points_as_corners(self, points: Iterable[np_vector]):
for point in points:
self.add_line_to(point)
return points
def set_points_as_corners(self, points: Iterable[np.ndarray]):
def set_points_as_corners(self, points: Iterable[np_vector]):
nppc = self.n_points_per_curve
points = np.array(points)
self.set_anchors_and_handles(*[
@ -529,7 +547,7 @@ class VMobject(Mobject):
def set_points_smoothly(
self,
points: Iterable[np.ndarray],
points: Iterable[np_vector],
true_smooth: bool = False
):
self.set_points_as_corners(points)
@ -584,7 +602,7 @@ class VMobject(Mobject):
self.change_anchor_mode("jagged")
return self
def add_subpath(self, points: Iterable[np.ndarray]):
def add_subpath(self, points: Iterable[np_vector]):
assert(len(points) % self.n_points_per_curve == 0)
self.append_points(points)
return self
@ -600,11 +618,11 @@ class VMobject(Mobject):
return self
#
def consider_points_equals(self, p0: np.ndarray, p1: np.ndarray) -> bool:
def consider_points_equals(self, p0: np_vector, p1: np_vector) -> bool:
return get_norm(p1 - p0) < self.tolerance_for_point_equality
# Information about the curve
def get_bezier_tuples_from_points(self, points: Sequence[np.ndarray]):
def get_bezier_tuples_from_points(self, points: Sequence[np_vector]):
nppc = self.n_points_per_curve
remainder = len(points) % nppc
points = points[:len(points) - remainder]
@ -618,8 +636,8 @@ class VMobject(Mobject):
def get_subpaths_from_points(
self,
points: Sequence[np.ndarray]
) -> list[Sequence[np.ndarray]]:
points: Sequence[np_vector]
) -> list[Sequence[np_vector]]:
nppc = self.n_points_per_curve
diffs = points[nppc - 1:-1:nppc] - points[nppc::nppc]
splits = (diffs * diffs).sum(1) > self.tolerance_for_point_equality
@ -636,28 +654,28 @@ class VMobject(Mobject):
if (i2 - i1) >= nppc
]
def get_subpaths(self) -> list[Sequence[np.ndarray]]:
def get_subpaths(self) -> list[Sequence[np_vector]]:
return self.get_subpaths_from_points(self.get_points())
def get_nth_curve_points(self, n: int) -> np.ndarray:
def get_nth_curve_points(self, n: int) -> np_vector:
assert(n < self.get_num_curves())
nppc = self.n_points_per_curve
return self.get_points()[nppc * n:nppc * (n + 1)]
def get_nth_curve_function(self, n: int) -> Callable[[float], np.ndarray]:
def get_nth_curve_function(self, n: int) -> Callable[[float], np_vector]:
return bezier(self.get_nth_curve_points(n))
def get_num_curves(self) -> int:
return self.get_num_points() // self.n_points_per_curve
def quick_point_from_proportion(self, alpha: float) -> np.ndarray:
def quick_point_from_proportion(self, alpha: float) -> np_vector:
# Assumes all curves have the same length, so is inaccurate
num_curves = self.get_num_curves()
n, residue = integer_interpolate(0, num_curves, alpha)
curve_func = self.get_nth_curve_function(n)
return curve_func(residue)
def point_from_proportion(self, alpha: float) -> np.ndarray:
def point_from_proportion(self, alpha: float) -> np_vector:
if alpha <= 0:
return self.get_start()
elif alpha >= 1:
@ -679,7 +697,7 @@ class VMobject(Mobject):
residue = inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha)
return self.get_nth_curve_function(i - 1)(residue)
def get_anchors_and_handles(self) -> list[np.ndarray]:
def get_anchors_and_handles(self) -> list[np_vector]:
"""
returns anchors1, handles, anchors2,
where (anchors1[i], handles[i], anchors2[i])
@ -693,14 +711,14 @@ class VMobject(Mobject):
for i in range(nppc)
]
def get_start_anchors(self) -> np.ndarray:
def get_start_anchors(self) -> np_vector:
return self.get_points()[0::self.n_points_per_curve]
def get_end_anchors(self) -> np.ndarray:
def get_end_anchors(self) -> np_vector:
nppc = self.n_points_per_curve
return self.get_points()[nppc - 1::nppc]
def get_anchors(self) -> np.ndarray:
def get_anchors(self) -> np_vector:
points = self.get_points()
if len(points) == 1:
return points
@ -709,7 +727,7 @@ class VMobject(Mobject):
self.get_end_anchors(),
))))
def get_points_without_null_curves(self, atol: float = 1e-9) -> np.ndarray:
def get_points_without_null_curves(self, atol: float = 1e-9) -> np_vector:
nppc = self.n_points_per_curve
points = self.get_points()
distinct_curves = reduce(op.or_, [
@ -729,7 +747,7 @@ class VMobject(Mobject):
norms = np.array([get_norm(d) for d in diffs])
return norms.sum()
def get_area_vector(self) -> np.ndarray:
def get_area_vector(self) -> np_vector:
# Returns a vector whose length is the area bound by
# the polygon formed by the anchor points, pointing
# in a direction perpendicular to the polygon according
@ -749,7 +767,7 @@ class VMobject(Mobject):
sum((p0[:, 0] + p1[:, 0]) * (p1[:, 1] - p0[:, 1])), # Add up (x1 + x2)*(y2 - y1)
])
def get_unit_normal(self, recompute: bool = False) -> np.ndarray:
def get_unit_normal(self, recompute: bool = False) -> np_vector:
if not recompute:
return self.data["unit_normal"][0]
@ -834,7 +852,7 @@ class VMobject(Mobject):
mob.set_points(new_points)
return self
def insert_n_curves_to_point_list(self, n: int, points: np.ndarray):
def insert_n_curves_to_point_list(self, n: int, points: np_vector):
nppc = self.n_points_per_curve
if len(points) == 1:
return np.repeat(points, nppc * n, 0)
@ -936,7 +954,7 @@ class VMobject(Mobject):
mob.needs_new_triangulation = True
return self
def get_triangulation(self, normal_vector: np.ndarray | None = None):
def get_triangulation(self, normal_vector: np_vector | None = None):
# Figure out how to triangulate the interior to know
# how to send the points as to the vertex shader.
# First triangles come directly from the points
@ -1005,7 +1023,7 @@ class VMobject(Mobject):
return wrapper
@triggers_refreshed_triangulation
def set_points(self, points: npt.ArrayLike):
def set_points(self, points: np_vector):
super().set_points(points)
return self
@ -1018,7 +1036,7 @@ class VMobject(Mobject):
@triggers_refreshed_triangulation
def apply_function(
self,
function: Callable[[np.ndarray], np.ndarray],
function: Callable[[np_vector], np_vector],
make_smooth: bool = False,
**kwargs
):
@ -1027,7 +1045,7 @@ class VMobject(Mobject):
self.make_approximately_smooth()
return self
def flip(self, axis: np.ndarray = UP, **kwargs):
def flip(self, axis: np_vector = UP, **kwargs):
super().flip(axis, **kwargs)
self.refresh_unit_normal()
self.refresh_triangulation()
@ -1154,17 +1172,22 @@ class VGroup(VMobject):
class VectorizedPoint(Point, VMobject):
CONFIG = {
"color": BLACK,
"fill_opacity": 0,
"stroke_width": 0,
"artificial_width": 0.01,
"artificial_height": 0.01,
}
def __init__(self, location: np.ndarray = ORIGIN, **kwargs):
Point.__init__(self, **kwargs)
VMobject.__init__(self, **kwargs)
def __init__(
self,
location: np.ndarray = ORIGIN,
color: ManimColor = BLACK,
fill_opacity: float = 0.0,
stroke_width: float = 0.0,
**kwargs
):
Point.__init__(self, location, **kwargs)
VMobject.__init__(
self,
color=color,
fill_opacity=fill_opacity,
stroke_width=stroke_width,
**kwargs
)
self.set_points(np.array([location]))
@ -1179,23 +1202,22 @@ class CurvesAsSubmobjects(VGroup):
class DashedVMobject(VMobject):
CONFIG = {
"num_dashes": 15,
"positive_space_ratio": 0.5,
"color": WHITE
}
def __init__(self, vmobject: VMobject, **kwargs):
def __init__(
self,
vmobject: VMobject,
num_dashes: int = 15,
positive_space_ratio: float = 0.5,
**kwargs
):
super().__init__(**kwargs)
num_dashes = self.num_dashes
ps_ratio = self.positive_space_ratio
if num_dashes > 0:
# End points of the unit interval for division
alphas = np.linspace(0, 1, num_dashes + 1)
# This determines the length of each "dash"
full_d_alpha = (1.0 / num_dashes)
partial_d_alpha = full_d_alpha * ps_ratio
partial_d_alpha = full_d_alpha * positive_space_ratio
# Rescale so that the last point of vmobject will
# be the end of the last dash
@ -1215,7 +1237,7 @@ class VHighlight(VGroup):
self,
vmobject: VMobject,
n_layers: int = 5,
color_bounds: tuple[ManimColor] = (GREY_C, GREY_E),
color_bounds: Tuple[ManimColor] = (GREY_C, GREY_E),
max_stroke_addition: float = 5.0,
):
outline = vmobject.replicate(n_layers)