From 576a26493e5c4d6e5a198630b37c5ad3eb11464c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 15 Feb 2023 20:54:59 -0800 Subject: [PATCH 01/65] Use quick_point_from_proportion in MoveAlongPath --- manimlib/animation/movement.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/manimlib/animation/movement.py b/manimlib/animation/movement.py index e5671df2..410c15c3 100644 --- a/manimlib/animation/movement.py +++ b/manimlib/animation/movement.py @@ -11,6 +11,7 @@ if TYPE_CHECKING: import numpy as np from manimlib.mobject.mobject import Mobject + from manimlib.mobject.types.vectorized_mobject import VMobject class Homotopy(Animation): @@ -105,7 +106,7 @@ class MoveAlongPath(Animation): def __init__( self, mobject: Mobject, - path: Mobject, + path: VMobject, suspend_mobject_updating: bool = False, **kwargs ): @@ -113,5 +114,5 @@ class MoveAlongPath(Animation): super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs) def interpolate_mobject(self, alpha: float) -> None: - point = self.path.point_from_proportion(alpha) + point = self.path.quick_point_from_proportion(alpha) self.mobject.move_to(point) From dcb58c1f4fdc20b9832c43852b07604a774c7ac5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 15 Feb 2023 20:55:26 -0800 Subject: [PATCH 02/65] Remove arg_creator arg from LaggedStartMap Anything that enables is better done just with LaggedStart --- manimlib/animation/composition.py | 1 - 1 file changed, 1 deletion(-) diff --git a/manimlib/animation/composition.py b/manimlib/animation/composition.py index e4ad5656..aadeac65 100644 --- a/manimlib/animation/composition.py +++ b/manimlib/animation/composition.py @@ -167,7 +167,6 @@ class LaggedStartMap(LaggedStart): self, anim_func: Callable[[Mobject], Animation], group: Mobject, - arg_creator: Callable[[Mobject], tuple] | None = None, run_time: float = 2.0, lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO, **kwargs From 3a05352f73a2fc882af6d8e2badf285aa7b296db Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 16 Feb 2023 15:02:15 -0800 Subject: [PATCH 03/65] Add poly_line_length function --- manimlib/utils/space_ops.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/manimlib/utils/space_ops.py b/manimlib/utils/space_ops.py index fdc2f52b..8a696dd0 100644 --- a/manimlib/utils/space_ops.py +++ b/manimlib/utils/space_ops.py @@ -61,6 +61,13 @@ def normalize( 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 From c372ef4aaa77925a887b4b086d32feab0b0ff7d9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 16 Feb 2023 15:02:30 -0800 Subject: [PATCH 04/65] Faster VMobject.get_arc_length --- manimlib/mobject/types/vectorized_mobject.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 72b287af..b88166be 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -17,6 +17,7 @@ from manimlib.utils.bezier import bezier 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 smooth_quadratic_path +from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import inverse_interpolate from manimlib.utils.bezier import find_intersection @@ -38,6 +39,7 @@ from manimlib.utils.space_ops import get_unit_normal from manimlib.utils.space_ops import line_intersects_path from manimlib.utils.space_ops import midpoint from manimlib.utils.space_ops import normalize_along_axis +from manimlib.utils.space_ops import poly_line_length from manimlib.utils.space_ops import z_to_vector from manimlib.shader_wrapper import ShaderWrapper from manimlib.shader_wrapper import FillShaderWrapper @@ -814,14 +816,16 @@ class VMobject(Mobject): return np.vstack(new_points) def get_arc_length(self, n_sample_points: int | None = None) -> float: - if n_sample_points is None: - n_sample_points = 4 * self.get_num_curves() + 1 - points = np.array([ - self.point_from_proportion(a) - for a in np.linspace(0, 1, n_sample_points) - ]) - diffs = points[1:] - points[:-1] - return sum(map(get_norm, diffs)) + if n_sample_points is not None: + points = np.array([ + self.quick_point_from_proportion(a) + for a in np.linspace(0, 1, n_sample_points) + ]) + return poly_line_length(points) + points = self.get_points() + 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: # Returns a vector whose length is the area bound by From 3f2d15986a18d8964bbe714d3f7fa1cf8bfca334 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 16 Feb 2023 16:47:59 -0800 Subject: [PATCH 05/65] Make TrueDot shading in 3d work --- manimlib/shaders/true_dot/frag.glsl | 17 ++++++----- manimlib/shaders/true_dot/geom.glsl | 44 +++++++++++++++++++++++++++++ manimlib/shaders/true_dot/vert.glsl | 18 +++--------- 3 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 manimlib/shaders/true_dot/geom.glsl diff --git a/manimlib/shaders/true_dot/frag.glsl b/manimlib/shaders/true_dot/frag.glsl index 8f82ee4c..b3d42656 100644 --- a/manimlib/shaders/true_dot/frag.glsl +++ b/manimlib/shaders/true_dot/frag.glsl @@ -5,7 +5,11 @@ uniform mat4 perspective; in vec4 color; 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; @@ -13,9 +17,8 @@ out vec4 frag_color; #INSERT finalize_color.glsl void main() { - vec2 vect = 2.0 * gl_PointCoord.xy - vec2(1.0); - float r = length(vect); - if(r > 1.0 + scaled_aaw) discard; + float r = length(uv_coords.xy); + if(r > 1.0) discard; frag_color = color; @@ -24,9 +27,9 @@ void main() { } if(shading != vec3(0.0)){ - vec3 normal = vec3(vect, sqrt(1 - r * r)); - normal = (perspective * vec4(normal, 0.0)).xyz; - frag_color = finalize_color(frag_color, v_point, normal); + vec3 point_3d = point + radius * sqrt(1 - r * r) * to_cam; + vec3 normal = normalize(point_3d - center); + frag_color = finalize_color(frag_color, point_3d, normal); } frag_color.a *= smoothstep(1.0, 1.0 - scaled_aaw, r); diff --git a/manimlib/shaders/true_dot/geom.glsl b/manimlib/shaders/true_dot/geom.glsl new file mode 100644 index 00000000..af7b36b2 --- /dev/null +++ b/manimlib/shaders/true_dot/geom.glsl @@ -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(); +} \ No newline at end of file diff --git a/manimlib/shaders/true_dot/vert.glsl b/manimlib/shaders/true_dot/vert.glsl index 3dca6f24..55658063 100644 --- a/manimlib/shaders/true_dot/vert.glsl +++ b/manimlib/shaders/true_dot/vert.glsl @@ -1,26 +1,16 @@ #version 330 -uniform float pixel_size; -uniform float anti_alias_width; - in vec3 point; in float radius; in vec4 rgba; -out vec4 color; -out float scaled_aaw; out vec3 v_point; -out vec3 light_pos; +out float v_radius; +out vec4 v_rgba; -#INSERT emit_gl_Position.glsl void main(){ v_point = point; - color = rgba; - scaled_aaw = (anti_alias_width * pixel_size) / radius; - - 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); + v_radius = radius; + v_rgba = rgba; } \ No newline at end of file From a79d4a862f016e82d234d28eccd0510804f882f8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 24 Feb 2023 08:22:24 -0500 Subject: [PATCH 06/65] Keep track of dots in DieFace --- manimlib/mobject/svg/drawings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 4b0449ae..59b831c0 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -622,5 +622,6 @@ class DieFace(VGroup): arrangement.space_out_submobjects(dot_coalesce_factor) super().__init__(square, arrangement) + self.dots = arrangement self.value = value self.index = value From 1eb819363d1e17c79df52d25bec752c1975387a7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 24 Feb 2023 08:23:01 -0500 Subject: [PATCH 07/65] Fix issue with ticks going beyond number line --- manimlib/mobject/number_line.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/number_line.py b/manimlib/mobject/number_line.py index 413c280a..64e8525f 100644 --- a/manimlib/mobject/number_line.py +++ b/manimlib/mobject/number_line.py @@ -94,7 +94,8 @@ class NumberLine(Line): x_max = self.x_max else: 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: ticks = VGroup() From b216b8f7e399fd8f2adeffd421a787e5ed0a879f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 24 Feb 2023 08:23:24 -0500 Subject: [PATCH 08/65] Reset default in set_style to stroke_background = False --- manimlib/mobject/types/vectorized_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index b88166be..503008e6 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -236,7 +236,7 @@ class VMobject(Mobject): stroke_opacity: float | Iterable[float] | None = None, stroke_rgba: Vect4 | None = None, stroke_width: float | Iterable[float] | None = None, - stroke_background: bool = True, + stroke_background: bool = False, shading: Tuple[float, float, float] | None = None, recurse: bool = True ) -> Self: From 6d0b58659732976e2e78709ac4bbe77439a82a90 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 10 Mar 2023 11:04:25 -0800 Subject: [PATCH 09/65] Prevent index out of range error for ShowSubmobjectsOneByOne --- manimlib/animation/creation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index ac73891d..7dd9505c 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -12,6 +12,7 @@ from manimlib.utils.bezier import integer_interpolate from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import double_smooth from manimlib.utils.rate_functions import smooth +from manimlib.utils.simple_functions import clip from typing import TYPE_CHECKING @@ -206,7 +207,7 @@ class ShowSubmobjectsOneByOne(ShowIncreasingSubsets): super().__init__(group, int_func=int_func, **kwargs) 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: self.mobject.set_submobjects([]) else: From 392019fc6ea33700d97cd6c81133e1e3bc60fdf4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 10 Mar 2023 11:04:49 -0800 Subject: [PATCH 10/65] Specify type in bind_to_graph --- manimlib/mobject/coordinate_systems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 615eed7d..bfa3f2b1 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -239,7 +239,7 @@ class CoordinateSystem(ABC): def bind_graph_to_func( self, graph: VMobject, - func: Callable[[Vect3], Vect3], + func: Callable[[VectN], VectN], jagged: bool = False, get_discontinuities: Optional[Callable[[], Vect3]] = None ) -> VMobject: From b644bb51de5b06b564995ae456bc6b0bd95ced52 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 10 Mar 2023 11:05:07 -0800 Subject: [PATCH 11/65] Update submobjects before parents --- manimlib/mobject/mobject.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 9f4f2a18..752e12f8 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -790,13 +790,13 @@ class Mobject(object): def update(self, dt: float = 0, recurse: bool = True) -> Self: if not self.has_updaters or self.updating_suspended: return self + if recurse: + for submob in self.submobjects: + submob.update(dt, recurse) for updater in self.time_based_updaters: updater(self, dt) for updater in self.non_time_updaters: updater(self) - if recurse: - for submob in self.submobjects: - submob.update(dt, recurse) return self def get_time_based_updaters(self) -> list[TimeBasedUpdater]: From d1e2a7a157a7ea5076cd4ab3c6167ba749e51507 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 10 Mar 2023 11:05:30 -0800 Subject: [PATCH 12/65] Make sure \text{...} is counted correctly in num_tex_symbols --- manimlib/utils/tex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/utils/tex.py b/manimlib/utils/tex.py index 791cdc5c..719d228a 100644 --- a/manimlib/utils/tex.py +++ b/manimlib/utils/tex.py @@ -16,7 +16,7 @@ def num_tex_symbols(tex: str) -> int: # \begin{array}{cc}, etc. pattern = "|".join( rf"(\\{s})" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?" - for s in ["begin", "end", "phantom", "text"] + for s in ["begin", "end", "phantom"] ) tex = re.sub(pattern, "", tex) From 80fb1a98a92fe2ff9dc875a3ccc1a642a1fcb987 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 10 Mar 2023 11:05:50 -0800 Subject: [PATCH 13/65] Add \text to TEX_TO_SYMBOL_COUNT --- manimlib/utils/tex_to_symbol_count.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/utils/tex_to_symbol_count.py b/manimlib/utils/tex_to_symbol_count.py index c314b5dd..23431d23 100644 --- a/manimlib/utils/tex_to_symbol_count.py +++ b/manimlib/utils/tex_to_symbol_count.py @@ -160,6 +160,7 @@ TEX_TO_SYMBOL_COUNT = { R"\sup": 3, R"\tan": 3, R"\tanh": 4, + R"\text": 0, R"\textbf": 0, R"\textfraction": 2, R"\textstyle": 0, From 0d415036a93ed15b60c223f0ec8d4c3067bc08af Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 14 Mar 2023 10:34:33 -0700 Subject: [PATCH 14/65] Ensure exact integers are displays for large values in Integer --- manimlib/mobject/numbers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/numbers.py b/manimlib/mobject/numbers.py index a5a24708..1a524fa8 100644 --- a/manimlib/mobject/numbers.py +++ b/manimlib/mobject/numbers.py @@ -98,6 +98,8 @@ class DecimalNumber(VMobject): formatter = self.get_complex_formatter() else: formatter = self.get_formatter() + if self.num_decimal_places == 0 and isinstance(number, float): + number = int(number) num_string = formatter.format(number) rounded_num = np.round(number, self.num_decimal_places) @@ -149,7 +151,7 @@ class DecimalNumber(VMobject): ":", "+" if config["include_sign"] else "", "," if config["group_with_commas"] else "", - f".{ndp}f", + f".{ndp}f" if ndp > 0 else "d", "}", ]) From 6b24860bbfa3f46d6363c51e068f6726f2e756bb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 14 Mar 2023 10:34:44 -0700 Subject: [PATCH 15/65] Tweak default configuration for Underline --- manimlib/mobject/shape_matchers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/shape_matchers.py b/manimlib/mobject/shape_matchers.py index b79491d9..97fdb649 100644 --- a/manimlib/mobject/shape_matchers.py +++ b/manimlib/mobject/shape_matchers.py @@ -110,6 +110,7 @@ class Underline(Line): buff: float = SMALL_BUFF, stroke_color=WHITE, stroke_width: float | Sequence[float] = [0, 3, 3, 0], + stretch_factor=1.2, **kwargs ): super().__init__( @@ -119,5 +120,6 @@ class Underline(Line): **kwargs ) 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) From de8e9e5ec1f2c8b43ba536f8c72ea8625a58ae11 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 14 Mar 2023 10:35:04 -0700 Subject: [PATCH 16/65] Fix error with setting bubble direction --- manimlib/mobject/svg/drawings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 59b831c0..927554b8 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -358,7 +358,7 @@ class Bubble(SVGMobject): stroke_width: float = 3.0, **kwargs ): - self.direction = direction + self.direction = LEFT # Possibly updated below by self.flip() self.bubble_center_adjustment_factor = bubble_center_adjustment_factor self.content_scale_factor = content_scale_factor @@ -380,7 +380,7 @@ class Bubble(SVGMobject): if direction[0] > 0: self.flip() - self.content = Mobject() + self.content = VMobject() def get_tip(self): # TODO, find a better way @@ -403,10 +403,10 @@ class Bubble(SVGMobject): self.direction = -np.array(self.direction) return self - def pin_to(self, mobject): + def pin_to(self, mobject, auto_flip=False): mob_center = mobject.get_center() 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() boundary_point = mobject.get_bounding_box_point(UP - self.direction) vector_from_center = 1.0 * (boundary_point - mob_center) From f33b8d1d2f9d37e51eff4ec113373f0c3948d17d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:19:32 -0700 Subject: [PATCH 17/65] Add stretch_factor in FlashUnder Underline --- manimlib/animation/indication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/animation/indication.py b/manimlib/animation/indication.py index cd01c359..4078078b 100644 --- a/manimlib/animation/indication.py +++ b/manimlib/animation/indication.py @@ -278,7 +278,7 @@ class FlashAround(VShowPassingFlash): class FlashUnder(FlashAround): 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): From 5d9a7f49e638a43823966b80efb0fb85ffc9fa7f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:19:48 -0700 Subject: [PATCH 18/65] Add taper_width argument to FlashAround --- manimlib/animation/indication.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/animation/indication.py b/manimlib/animation/indication.py index 4078078b..6e95bb7c 100644 --- a/manimlib/animation/indication.py +++ b/manimlib/animation/indication.py @@ -258,6 +258,7 @@ class FlashAround(VShowPassingFlash): self, mobject: Mobject, time_width: float = 1.0, + taper_width: float = 0.0, stroke_width: float = 4.0, color: ManimColor = YELLOW, buff: float = SMALL_BUFF, @@ -270,7 +271,7 @@ class FlashAround(VShowPassingFlash): path.insert_n_curves(n_inserted_curves) path.set_points(path.get_points_without_null_curves()) 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: return SurroundingRectangle(mobject, buff=buff) From 16f5890fd3c18a1e4594345b9d8b6b972c96b3ed Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:20:28 -0700 Subject: [PATCH 19/65] Add CoordianteSystem.get_area_under_graph This is not perfect, since one could optionally add a different color for negative area. --- manimlib/mobject/coordinate_systems.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index bfa3f2b1..552b4703 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -22,6 +22,7 @@ from manimlib.mobject.types.dot_cloud import DotCloud from manimlib.mobject.types.surface import ParametricSurface from manimlib.mobject.types.vectorized_mobject import VGroup 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.simple_functions import binary_search from manimlib.utils.space_ops import angle_of_vector @@ -398,9 +399,24 @@ class CoordinateSystem(ABC): rect.set_fill(negative_color) return result - def get_area_under_graph(self, graph, x_range, fill_color=BLUE, fill_opacity=1): - # TODO - pass + def get_area_under_graph(self, graph, x_range, fill_color=BLUE, fill_opacity=0.5): + if not hasattr(graph, "x_range"): + 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): From ce7422f8af888827229de9f72255bdd115102b88 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:21:02 -0700 Subject: [PATCH 20/65] Add ThreeDAxes.get_graph and .get_parametric_surface --- manimlib/mobject/coordinate_systems.py | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 552b4703..f3c9d31f 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -565,20 +565,47 @@ class ThreeDAxes(Axes): axis.add(label) 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() yu = self.y_axis.get_unit_size() zu = self.z_axis.get_unit_size() 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( lambda u, v: [xu * u + x0, yu * v + y0, zu * func(u, v) + z0], - u_range=self.x_range[:2], - v_range=self.y_range[:2], + u_range=u_range, + v_range=v_range, color=color, opacity=opacity, **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): default_axis_config: dict = dict( From fa1080d59a0717a98bc3892a50d04e57050cb43c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:21:32 -0700 Subject: [PATCH 21/65] Be sure reverse_points changes data in place --- manimlib/mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 752e12f8..f2570d5f 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -224,7 +224,7 @@ class Mobject(object): @affects_family_data def reverse_points(self) -> Self: for mob in self.get_family(): - mob.data = mob.data[::-1] + mob.data[:] = mob.data[::-1] return self @affects_family_data From f01b990c2ea19eeca9ab1fce38cf8f05ce399da5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:21:45 -0700 Subject: [PATCH 22/65] Add default Mobject.match_style --- manimlib/mobject/mobject.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index f2570d5f..9af7b0c0 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1598,6 +1598,12 @@ class Mobject(object): def match_color(self, mobject: Mobject) -> Self: 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: return self.rescale_to_fit( mobject.length_over_dim(dim), dim, From 63f6e9d84ff5685e78dc923e401ac980202263cb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:21:57 -0700 Subject: [PATCH 23/65] Add Dartboard --- manimlib/mobject/svg/drawings.py | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 927554b8..f620b47a 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -1,6 +1,7 @@ from __future__ import annotations import numpy as np +import itertools as it from manimlib.animation.composition import AnimationGroup 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 GREEN from manimlib.constants import GREEN_SCREEN +from manimlib.constants import GREEN_E from manimlib.constants import GREY from manimlib.constants import GREY_A from manimlib.constants import GREY_B @@ -26,6 +28,7 @@ from manimlib.constants import ORIGIN from manimlib.constants import OUT from manimlib.constants import PI from manimlib.constants import RED +from manimlib.constants import RED_E from manimlib.constants import RIGHT 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 WHITE from manimlib.constants import YELLOW +from manimlib.constants import TAU from manimlib.mobject.boolean_ops import Difference from manimlib.mobject.geometry import Arc 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 Rectangle from manimlib.mobject.geometry import Square +from manimlib.mobject.geometry import AnnularSector from manimlib.mobject.mobject import Mobject from manimlib.mobject.numbers import Integer from manimlib.mobject.svg.svg_mobject import SVGMobject @@ -579,7 +584,6 @@ class Piano3D(VGroup): key.set_color(BLACK) - class DieFace(VGroup): def __init__( self, @@ -590,7 +594,7 @@ class DieFace(VGroup): stroke_width: float = 2.0, fill_color: ManimColor = GREY_E, dot_radius: float = 0.08, - dot_color: ManimColor = BLUE_B, + dot_color: ManimColor = WHITE, dot_coalesce_factor: float = 0.5 ): dot = Dot(radius=dot_radius, fill_color=dot_color) @@ -625,3 +629,47 @@ class DieFace(VGroup): self.dots = arrangement self.value = 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) From bae3b98c0bd4a718f552ca2e46f8a6f0c2678e48 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:22:15 -0700 Subject: [PATCH 24/65] Fixes to Surface --- manimlib/mobject/types/surface.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/manimlib/mobject/types/surface.py b/manimlib/mobject/types/surface.py index aaac1ebe..51ae324e 100644 --- a/manimlib/mobject/types/surface.py +++ b/manimlib/mobject/types/surface.py @@ -130,11 +130,13 @@ class Surface(Mobject): def get_unit_normals(self) -> Vect3Array: nu, nv = self.resolution indices = np.arange(nu * nv) + if len(indices) == 0: + return np.zeros((3, 0)) - left = indices - 1 + left = indices - 1 right = indices + 1 - up = indices - nv - down = indices + nv + up = indices - nv + down = indices + nv left[0] = indices[0] right[-1] = indices[-1] @@ -166,7 +168,7 @@ class Surface(Mobject): nu, nv = smobject.resolution self.data['point'][:] = self.get_partial_points_array( - self.data['point'], a, b, + smobject.data['point'], a, b, (nu, nv, 3), axis=axis ) @@ -183,7 +185,7 @@ class Surface(Mobject): if len(points) == 0: return points nu, nv = resolution[:2] - points = points.reshape(resolution) + points = points.reshape(resolution).copy() max_index = resolution[axis] - 1 lower_index, lower_residue = integer_interpolate(0, max_index, a) upper_index, upper_residue = integer_interpolate(0, max_index, b) From 6f8ea7433d72ea38ac379c5d663ca61d17c36264 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:22:34 -0700 Subject: [PATCH 25/65] Small formatting tweaks --- manimlib/mobject/types/vectorized_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 503008e6..88dbfa66 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -425,7 +425,7 @@ class VMobject(Mobject): self, anti_alias_width: float = 0, fill_border_width: float = 0, - recurse: bool=True + recurse: bool = True ) -> Self: super().apply_depth_test(recurse) self.set_anti_alias_width(anti_alias_width) @@ -436,7 +436,7 @@ class VMobject(Mobject): self, anti_alias_width: float = 1.0, fill_border_width: float = 0.5, - recurse: bool=True + recurse: bool = True ) -> Self: super().apply_depth_test(recurse) self.set_anti_alias_width(anti_alias_width) From 3e641119526b079d667ee21830b58710ae7e5016 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:22:55 -0700 Subject: [PATCH 26/65] Change default on VMobject to no depth test --- manimlib/mobject/types/vectorized_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 88dbfa66..1d9b3b2c 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -438,7 +438,7 @@ class VMobject(Mobject): fill_border_width: float = 0.5, recurse: bool = True ) -> Self: - super().apply_depth_test(recurse) + super().deactivate_depth_test(recurse) self.set_anti_alias_width(anti_alias_width) self.set_fill(border_width=fill_border_width) return self From cb02066f22a12f2c2a95e89d29c25936e384329c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:23:19 -0700 Subject: [PATCH 27/65] Add always_depth_test option to ThreeDScene, default to true --- manimlib/scene/scene.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index ed4bd5fc..79745ca6 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -1021,9 +1021,10 @@ class EndScene(Exception): class ThreeDScene(Scene): samples = 4 default_frame_orientation = (-30, 70) + always_depth_test = True def add(self, *mobjects, set_depth_test: bool = True): 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() super().add(*mobjects) From 162fd4a92be68018eea9434d3e1644cc3266c70e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:24:50 -0700 Subject: [PATCH 28/65] Change defaults for where videos are saved Save them directly to the relevant output directory, rather than to a "videos" subdirectory within it. --- manimlib/scene/scene_file_writer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/manimlib/scene/scene_file_writer.py b/manimlib/scene/scene_file_writer.py index 47698d0d..79398484 100644 --- a/manimlib/scene/scene_file_writer.py +++ b/manimlib/scene/scene_file_writer.py @@ -42,6 +42,7 @@ class SceneFileWriter(object): # Where should this be written output_directory: str | None = None, file_name: str | None = None, + subdirectory_for_videos: bool = False, open_file_upon_completion: bool = False, show_file_location_upon_completion: bool = False, quiet: bool = False, @@ -63,6 +64,7 @@ class SceneFileWriter(object): self.output_directory = output_directory self.file_name = file_name 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.quiet = quiet self.total_frames = total_frames @@ -88,7 +90,10 @@ class SceneFileWriter(object): image_file = add_extension_if_not_present(scene_name, ".png") self.image_file_path = os.path.join(image_dir, image_file) 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) self.movie_file_path = os.path.join(movie_dir, movie_file) if self.break_into_partial_movies: From 0609c1bfa85295d94f4f45200d644a72315a9f42 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:25:03 -0700 Subject: [PATCH 29/65] Change default saturation and gamma to each be 1 --- manimlib/scene/scene_file_writer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/scene/scene_file_writer.py b/manimlib/scene/scene_file_writer.py index 79398484..eca49b05 100644 --- a/manimlib/scene/scene_file_writer.py +++ b/manimlib/scene/scene_file_writer.py @@ -50,8 +50,8 @@ class SceneFileWriter(object): progress_description_len: int = 40, video_codec: str = "libx264", pixel_format: str = "yuv420p", - saturation: float = 1.7, - gamma: float = 1.2, + saturation: float = 1.0, + gamma: float = 1.0, ): self.scene: Scene = scene self.write_to_movie = write_to_movie From 21d20541b5167c0ec73c66e942fbb9c37bbe74c7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:25:44 -0700 Subject: [PATCH 30/65] Add texture_names_to_ids to ShaderWrapper --- manimlib/shader_wrapper.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 4e811147..9c412173 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -56,6 +56,7 @@ class ShaderWrapper(object): self.init_program_code() self.init_program() + self.texture_names_to_ids = dict() if texture_paths is not None: self.init_textures(texture_paths) self.init_vao() @@ -82,11 +83,10 @@ class ShaderWrapper(object): self.vert_format = moderngl.detect_format(self.program, self.vert_attributes) 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)) for name, path in texture_paths.items() } - self.update_program_uniforms(names_to_ids) def init_vao(self): self.vbo = None @@ -224,8 +224,9 @@ class ShaderWrapper(object): def update_program_uniforms(self, camera_uniforms: UniformDict): if self.program is None: return - for name, value in (*self.mobject_uniforms.items(), *camera_uniforms.items()): - set_program_uniform(self.program, name, value) + for uniforms in [self.mobject_uniforms, camera_uniforms, self.texture_names_to_ids]: + for name, value in uniforms.items(): + set_program_uniform(self.program, name, value) def get_vertex_buffer_object(self, refresh: bool = True): if refresh: From 4f42ebeb4f7ecb6b2a45319e290f4aa33373400e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Jun 2023 09:26:20 -0700 Subject: [PATCH 31/65] Small formatting tweaks --- manimlib/utils/shaders.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py index 7cc1ba5c..ec915b25 100644 --- a/manimlib/utils/shaders.py +++ b/manimlib/utils/shaders.py @@ -56,7 +56,7 @@ def get_shader_program( vertex_shader: str, fragment_shader: Optional[str] = None, geometry_shader: Optional[str] = None, - ) -> moderngl.Program: +) -> moderngl.Program: return ctx.program( vertex_shader=vertex_shader, fragment_shader=fragment_shader, @@ -74,7 +74,7 @@ def set_program_uniform( of previously set uniforms for that program so that it doesn't needlessly reset it, requiring an exchange with gpu memory, if it sees the same value again. - + Returns True if changed the program, False if it left it as is. """ @@ -134,7 +134,6 @@ def get_colormap_code(rgb_list: Sequence[float]) -> str: return f"vec3[{len(rgb_list)}]({data})" - @lru_cache() def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]: """ From 7ff45b46378fd6a364cddbbd940b940f0b1fc45b Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 23 Jun 2023 10:57:57 -0700 Subject: [PATCH 32/65] Having changing decimal match fixed_in_frame status --- manimlib/mobject/numbers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/numbers.py b/manimlib/mobject/numbers.py index 1a524fa8..a4722fff 100644 --- a/manimlib/mobject/numbers.py +++ b/manimlib/mobject/numbers.py @@ -171,6 +171,7 @@ class DecimalNumber(VMobject): self.set_submobjects_from_number(number) self.move_to(move_to_point, self.edge_to_fix) self.set_style(**style) + self.fix_in_frame(self._is_fixed_in_frame) return self def _handle_scale_side_effects(self, scale_factor: float) -> Self: From 7954ba14ef2fbd90f68e501ee833e2530ea082b1 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:35:42 -0700 Subject: [PATCH 33/65] Use rate_function appropriately in ShowIncreasingSubsets --- manimlib/animation/creation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index 7dd9505c..ea992801 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -190,6 +190,7 @@ class ShowIncreasingSubsets(Animation): def interpolate_mobject(self, alpha: float) -> None: n_submobs = len(self.all_submobs) + alpha = self.rate_func(alpha) index = int(self.int_func(alpha * n_submobs)) self.update_submobject_list(index) From 2337be2318b1a28e0dd6a94266979271cd76d70a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:36:36 -0700 Subject: [PATCH 34/65] Remove num_sampled_graph_points_per_tick in Axes __init__ --- manimlib/mobject/coordinate_systems.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index f3c9d31f..f0ee6a89 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -437,6 +437,7 @@ class Axes(VGroup, CoordinateSystem): **kwargs ): CoordinateSystem.__init__(self, x_range, y_range, **kwargs) + kwargs.pop("num_sampled_graph_points_per_tick", None) VGroup.__init__(self, **kwargs) axis_config = dict(**axis_config, unit_size=unit_size) From ddf2f7d9bd72faabd6c66b6fc23f944bae838eed Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:36:45 -0700 Subject: [PATCH 35/65] Fix typo --- manimlib/mobject/coordinate_systems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index f0ee6a89..1c4429f2 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -536,7 +536,7 @@ class ThreeDAxes(Axes): axis_config=merge_dicts_recursively( self.default_axis_config, self.default_z_axis_config, - kwargs.get("axes_config", {}), + kwargs.get("axis_config", {}), z_axis_config ), length=depth, From fc522e5278bf3f7b56c170962d7c85f9ff13a52f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:37:13 -0700 Subject: [PATCH 36/65] Change width_to_tip_len -> tip_len_to_width --- manimlib/mobject/geometry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 57c7bec1..602c7abe 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -673,13 +673,13 @@ class Arrow(Line): stroke_width: float = 5, buff: float = 0.25, 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_width_to_length_ratio: float = 8.0, **kwargs, ): 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_width_to_length_ratio = max_width_to_length_ratio super().__init__( @@ -705,8 +705,8 @@ class Arrow(Line): def insert_tip_anchor(self) -> Self: prev_end = self.get_end() arc_len = self.get_arc_length() - tip_len = self.get_stroke_width() * self.width_to_tip_len * self.tip_width_ratio if tip_len >= self.max_tip_length_to_length_ratio * arc_len: + tip_len = self.get_stroke_width() * self.tip_width_ratio * self.tip_len_to_width alpha = self.max_tip_length_to_length_ratio else: alpha = tip_len / arc_len From d21b05ae0d5ffd373d7ee72ab892589f6b26afed Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:37:54 -0700 Subject: [PATCH 37/65] Arrow fix --- manimlib/mobject/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 602c7abe..7dbb6ac8 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -705,8 +705,8 @@ class Arrow(Line): def insert_tip_anchor(self) -> Self: prev_end = self.get_end() arc_len = self.get_arc_length() - if tip_len >= self.max_tip_length_to_length_ratio * arc_len: 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 or arc_len == 0: alpha = self.max_tip_length_to_length_ratio else: alpha = tip_len / arc_len From 4be7f611ec424ab9cf57651cde3ff04c3085c787 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:38:55 -0700 Subject: [PATCH 38/65] Fix issues with stroke opacities passed as numpy arrays --- manimlib/mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 9af7b0c0..8ea51746 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1334,7 +1334,7 @@ class Mobject(object): rgbs = resize_with_interpolation(rgbs, len(data)) data[name][:, :3] = rgbs if opacity is not None: - if isinstance(opacity, list): + if not isinstance(opacity, (float, int)): opacity = resize_with_interpolation(np.array(opacity), len(data)) data[name][:, 3] = opacity return self From f2ad9a70f7c6af3cf28cb6b50dad358b9e645b19 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:39:27 -0700 Subject: [PATCH 39/65] Small format fix --- manimlib/mobject/types/dot_cloud.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/types/dot_cloud.py b/manimlib/mobject/types/dot_cloud.py index 43dbcd14..bd94d0dc 100644 --- a/manimlib/mobject/types/dot_cloud.py +++ b/manimlib/mobject/types/dot_cloud.py @@ -22,6 +22,7 @@ DEFAULT_GLOW_DOT_RADIUS = 0.2 DEFAULT_GRID_HEIGHT = 6 DEFAULT_BUFF_RATIO = 0.5 + class DotCloud(PMobject): shader_folder: str = "true_dot" render_primitive: int = moderngl.POINTS From eafd09549df2ae74d78492ca353b04589cdd3c3e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:40:01 -0700 Subject: [PATCH 40/65] Handle make_jagged for empty VMobjects --- manimlib/mobject/types/vectorized_mobject.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 1d9b3b2c..268de367 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -457,6 +457,9 @@ class VMobject(Mobject): anchors: Vect3Array, handles: Vect3Array, ) -> Self: + if len(anchors) == 0: + self.clear_points() + return self assert(len(anchors) == len(handles) + 1) points = resize_array(self.get_points(), 2 * len(anchors) - 1) points[0::2] = anchors @@ -644,6 +647,8 @@ class VMobject(Mobject): def change_anchor_mode(self, mode: str) -> Self: assert(mode in ("jagged", "approx_smooth", "true_smooth")) + if self.get_num_points() == 0: + return self subpaths = self.get_subpaths() self.clear_points() for subpath in subpaths: From c8cf83eedf90fae385ae63b9d0b722913015c15e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:40:15 -0700 Subject: [PATCH 41/65] Add cartesian_product --- manimlib/utils/iterables.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/manimlib/utils/iterables.py b/manimlib/utils/iterables.py index 3b7dbfba..90cd4c4b 100644 --- a/manimlib/utils/iterables.py +++ b/manimlib/utils/iterables.py @@ -136,6 +136,18 @@ def array_is_constant(arr: np.ndarray) -> bool: 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: if isinstance(obj, dict): return hash(tuple(sorted([ From 13d4ab1eb079ef2c278da8a7d382a88324853ab3 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:40:24 -0700 Subject: [PATCH 42/65] Stylistic change --- manimlib/utils/space_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/utils/space_ops.py b/manimlib/utils/space_ops.py index 8a696dd0..e89c1dfe 100644 --- a/manimlib/utils/space_ops.py +++ b/manimlib/utils/space_ops.py @@ -206,7 +206,7 @@ def normalize_along_axis( ) -> np.ndarray: norms = np.sqrt((array * array).sum(axis)) norms[norms == 0] = 1 - return (array.T / norms).T + return array / norms[:, np.newaxis] def get_unit_normal( From fa798a2018065de4a0ad80e4c5532c1ed41c2d14 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 15 Aug 2023 20:40:39 -0700 Subject: [PATCH 43/65] Add \dots and \mathds to tex_to_symbol_count --- manimlib/utils/tex_to_symbol_count.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manimlib/utils/tex_to_symbol_count.py b/manimlib/utils/tex_to_symbol_count.py index 23431d23..049e9fde 100644 --- a/manimlib/utils/tex_to_symbol_count.py +++ b/manimlib/utils/tex_to_symbol_count.py @@ -49,6 +49,7 @@ TEX_TO_SYMBOL_COUNT = { R"\div": 2, R"\doteq": 2, R"\dotfill": 0, + R"\dots": 3, R"\emph": 0, R"\exp": 3, R"\fbox": 4, @@ -102,6 +103,7 @@ TEX_TO_SYMBOL_COUNT = { R"\makebox": 0, R"\mapsto": 2, R"\markright": 0, + R"\mathds": 0, R"\max": 3, R"\mbox": 0, R"\medskip": 0, From 87e4a71ca3d747acdecbfbaf22b41e49dfb47e34 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 18:46:11 -0400 Subject: [PATCH 44/65] Add surround method for Rectangles and SurroundingRectangles --- manimlib/mobject/geometry.py | 8 +++++++- manimlib/mobject/mobject.py | 3 +++ manimlib/mobject/shape_matchers.py | 21 ++++++++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 7dbb6ac8..a14dfd61 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -7,7 +7,7 @@ import numpy as np 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 MED_SMALL_BUFF +from manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF from manimlib.constants import DEGREES, PI, TAU from manimlib.mobject.mobject import Mobject from manimlib.mobject.types.vectorized_mobject import DashedVMobject @@ -1046,6 +1046,12 @@ class Rectangle(Polygon): self.set_width(width, 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): def __init__(self, side_length: float = 2.0, **kwargs): diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 8ea51746..bca93026 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1540,6 +1540,9 @@ class Mobject(object): def get_depth(self) -> float: 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: """ Meant to generalize get_x, get_y, get_z diff --git a/manimlib/mobject/shape_matchers.py b/manimlib/mobject/shape_matchers.py index 97fdb649..69bd4543 100644 --- a/manimlib/mobject/shape_matchers.py +++ b/manimlib/mobject/shape_matchers.py @@ -27,13 +27,20 @@ class SurroundingRectangle(Rectangle): color: ManimColor = YELLOW, **kwargs ): - super().__init__( - width=mobject.get_width() + 2 * buff, - height=mobject.get_height() + 2 * buff, - color=color, - **kwargs - ) - self.move_to(mobject) + super().__init__(color=color, **kwargs) + self.buff = buff + self.surround(mobject) + + def surround(self, mobject, buff=None) -> Self: + self.mobject = 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): From 0a642133ad73ccc87f32c4bbdc07ead6746f270c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 18:46:28 -0400 Subject: [PATCH 45/65] Add scale_radii method for DotCloud --- manimlib/mobject/types/dot_cloud.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/manimlib/mobject/types/dot_cloud.py b/manimlib/mobject/types/dot_cloud.py index bd94d0dc..f4a8504b 100644 --- a/manimlib/mobject/types/dot_cloud.py +++ b/manimlib/mobject/types/dot_cloud.py @@ -117,6 +117,10 @@ class DotCloud(PMobject): def get_radius(self) -> float: 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: self.uniforms["glow_factor"] = glow_factor return self From 60a4f0e167852ce3aaed9ae9668b26a73125d187 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 19:08:13 -0400 Subject: [PATCH 46/65] Factor out Arc.create_quadratic_bezier_points to quadratic_bezier_points_for_arc --- manimlib/mobject/geometry.py | 36 ++++++++++-------------------------- manimlib/utils/bezier.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index a14dfd61..06efe5f7 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -13,6 +13,7 @@ from manimlib.mobject.mobject import Mobject from manimlib.mobject.types.vectorized_mobject import DashedVMobject from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.utils.bezier import quadratic_bezier_points_for_arc from manimlib.utils.iterables import adjacent_n_tuples from manimlib.utils.iterables import adjacent_pairs from manimlib.utils.simple_functions import clip @@ -26,6 +27,7 @@ from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import normalize from manimlib.utils.space_ops import rotate_vector from manimlib.utils.space_ops import rotation_matrix_transpose +from manimlib.utils.space_ops import rotation_about_z from typing import TYPE_CHECKING @@ -213,28 +215,11 @@ class Arc(TipableVMobject): ): super().__init__(**kwargs) - self.set_points(Arc.create_quadratic_bezier_points( - angle=angle, - start_angle=start_angle, - n_components=n_components - )) + self.set_points(quadratic_bezier_points_for_arc(angle, n_components)) + self.rotate(start_angle, about_point=ORIGIN) self.scale(radius, about_point=ORIGIN) 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: """ Looks at the normals to the first two @@ -448,8 +433,8 @@ class Annulus(VMobject): ) self.radius = outer_radius - outer_path = outer_radius * Arc.create_quadratic_bezier_points(TAU, 0) - inner_path = inner_radius * Arc.create_quadratic_bezier_points(-TAU, 0) + outer_path = outer_radius * quadratic_bezier_points_for_arc(TAU) + inner_path = inner_radius * quadratic_bezier_points_for_arc(-TAU) self.add_subpath(outer_path) self.add_subpath(inner_path) self.shift(center) @@ -490,10 +475,9 @@ class Line(TipableVMobject): alpha = (PI - path_arc) / 2 center = start + radius * normalize(rotate_vector(end - start, alpha)) - raw_arc_points = Arc.create_quadratic_bezier_points( - angle=path_arc - 2 * buff / radius, - start_angle=angle_of_vector(start - center) + buff / radius, - ) + raw_arc_points = quadratic_bezier_points_for_arc(path_arc - 2 * buff / radius) + rot_matrix = rotation_about_z(angle_of_vector(start - center) + buff / radius) + raw_arc_points = raw_arc_points @ rot_matrix.T if neg: raw_arc_points = raw_arc_points[::-1] self.set_points(center + radius * raw_arc_points) @@ -817,7 +801,7 @@ class FillArrow(Line): R = (-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a) # Find arc points - points1 = Arc.create_quadratic_bezier_points(path_arc) + points1 = quadratic_bezier_points_for_arc(path_arc) points2 = np.array(points1[::-1]) points1 *= (R + thickness / 2) points2 *= (R - thickness / 2) diff --git a/manimlib/utils/bezier.py b/manimlib/utils/bezier.py index dc314a9f..accf0012 100644 --- a/manimlib/utils/bezier.py +++ b/manimlib/utils/bezier.py @@ -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( points: FloatArray ) -> FloatArray: From 690eb24562b77953b17f86102878ed54fc779fd4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 19:31:40 -0400 Subject: [PATCH 47/65] Add VMobject.add_arc_to method --- manimlib/mobject/types/vectorized_mobject.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 268de367..f01d9d9e 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -21,8 +21,9 @@ from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import inverse_interpolate 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 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 rgb_to_hex from manimlib.utils.iterables import make_even @@ -39,6 +40,7 @@ from manimlib.utils.space_ops import get_unit_normal from manimlib.utils.space_ops import line_intersects_path from manimlib.utils.space_ops import midpoint 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.shader_wrapper import ShaderWrapper @@ -548,6 +550,18 @@ class VMobject(Mobject): self.add_cubic_bezier_curve_to(new_handle, handle, point) return self + def add_arc_to(self, point: Vect3, angle: float, n_components: int = 8) -> Self: + self.throw_error_if_no_points() + 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: points = self.get_points() if len(points) == 0: From 5f41e238ba347b93cc3b56c078073badcde618f3 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 20:49:36 -0400 Subject: [PATCH 48/65] Improve VMobject.add_arc_to --- manimlib/mobject/types/vectorized_mobject.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index f01d9d9e..f03da9ac 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -11,6 +11,7 @@ from manimlib.constants import DEFAULT_STROKE_WIDTH from manimlib.constants import DEGREES from manimlib.constants import JOINT_TYPE_MAP from manimlib.constants import ORIGIN, OUT +from manimlib.constants import TAU from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Point from manimlib.utils.bezier import bezier @@ -378,7 +379,7 @@ class VMobject(Mobject): data = self.data if self.has_points() else self._data_defaults 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 return data["stroke_width"][0, 0] @@ -550,8 +551,16 @@ class VMobject(Mobject): self.add_cubic_bezier_curve_to(new_handle, handle, point) return self - def add_arc_to(self, point: Vect3, angle: float, n_components: int = 8) -> 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 * 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] From b53ab02675c01812d477188851973c99e611ce92 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 20:49:51 -0400 Subject: [PATCH 49/65] Simplify initialization of Line with path arc --- manimlib/mobject/geometry.py | 48 ++++++++++++++---------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 06efe5f7..048d6da8 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -451,6 +451,7 @@ class Line(TipableVMobject): ): super().__init__(**kwargs) self.path_arc = path_arc + self.buff = buff self.set_start_and_end_attrs(start, end) self.set_points_by_ends(self.start, self.end, buff, path_arc) @@ -461,31 +462,15 @@ class Line(TipableVMobject): buff: float = 0, path_arc: float = 0 ) -> Self: - vect = end - start - dist = get_norm(vect) - if np.isclose(dist, 0): - 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)) + self.clear_points() + self.start_new_path(start) + self.add_arc_to(end, path_arc) - raw_arc_points = quadratic_bezier_points_for_arc(path_arc - 2 * buff / radius) - rot_matrix = rotation_about_z(angle_of_vector(start - center) + buff / radius) - raw_arc_points = raw_arc_points @ rot_matrix.T - if neg: - 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]) + # Apply buffer + if buff > 0: + length = self.get_arc_length() + alpha = min(buff / length, 0.5) + self.pointwise_become_partial(self, alpha, 1 - alpha) return self def set_path_arc(self, new_value: float) -> Self: @@ -666,6 +651,7 @@ class Arrow(Line): self.tip_len_to_width = tip_len_to_width 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.n_tip_points = 3 super().__init__( start, end, stroke_color=stroke_color, @@ -694,10 +680,13 @@ class Arrow(Line): alpha = self.max_tip_length_to_length_ratio else: alpha = tip_len / arc_len - self.pointwise_become_partial(self, 0, 1 - alpha) - # Dumb that this is needed - self.start_new_path(self.point_from_proportion(1 - 1e-5)) + + if self.path_arc > 0 and self.buff > 0: + self.insert_n_curves(10) # Is this needed? + self.pointwise_become_partial(self, 0.0, 1.0 - alpha) + self.append_points([self.get_end(), self.get_end()]) self.add_line_to(prev_end) + self.n_tip_points = 3 return self @Mobject.affects_data @@ -708,8 +697,9 @@ class Arrow(Line): float(self.get_stroke_width()), self.max_width_to_length_ratio * self.get_length(), ) - self.data['stroke_width'][:-3] = self.data['stroke_width'][0] - 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 def reset_tip(self) -> Self: From 2b00a9cf8020defc38832ab0e2166860c912f7d3 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 21:15:43 -0700 Subject: [PATCH 50/65] Fix add_curve_to --- manimlib/mobject/types/vectorized_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index f03da9ac..a9b6c26e 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -559,7 +559,7 @@ class VMobject(Mobject): # Assign default value for n_components if n_components is None: - n_components = int(np.ceil(8 * angle / TAU)) + 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() From 295a0f76cc0d7ced0af8ffbd41e56f6343b52c82 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 21:15:53 -0700 Subject: [PATCH 51/65] Formatting tweak --- manimlib/mobject/types/vectorized_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index a9b6c26e..f6cdb7bf 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -775,8 +775,8 @@ class VMobject(Mobject): return self.get_subpaths_from_points(self.get_points()) def get_nth_curve_points(self, n: int) -> Vect3Array: - assert(n < self.get_num_curves()) - return self.get_points()[2 * n : 2 * n + 3] + assert n < self.get_num_curves() + return self.get_points()[2 * n:2 * n + 3] def get_nth_curve_function(self, n: int) -> Callable[[float], Vect3]: return bezier(self.get_nth_curve_points(n)) From 39e5d24858bb985923eab3e88bd1830007525bbc Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 21:16:18 -0700 Subject: [PATCH 52/65] Factor out partial results from point_from_proportion --- manimlib/mobject/types/vectorized_mobject.py | 34 +++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index f6cdb7bf..a3bc87b9 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -791,12 +791,14 @@ class VMobject(Mobject): curve_func = self.get_nth_curve_function(n) return curve_func(residue) - def point_from_proportion(self, alpha: float) -> Vect3: - if alpha <= 0: - return self.get_start() - elif alpha >= 1: - return self.get_end() - + def curve_and_prop_of_partial_point(self, alpha) -> Tuple[int, float]: + """ + If you want a point a proportion alpha along the curve, this + gives you the index of the appropriate bezier curve, together + with the proportion along that curve you'd need to travel + """ + if alpha == 0: + return (0, 0.0) partials: list[float] = [0] for tup in self.get_bezier_tuples(): if self.consider_points_equal(tup[0], tup[1]): @@ -808,14 +810,24 @@ class VMobject(Mobject): partials.append(partials[-1] + arclen) full = partials[-1] 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 - i = next( + index = next( (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)) - return self.get_nth_curve_function(i - 1)(residue) + residue = float(inverse_interpolate( + 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]: """ From a8b1791ff5939950a90feec3700fe3d3b20aed3f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 4 Sep 2023 21:16:36 -0700 Subject: [PATCH 53/65] Small tweaks to arrow tip implementation --- manimlib/mobject/geometry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 048d6da8..cbf66ee4 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -13,7 +13,9 @@ from manimlib.mobject.mobject import Mobject from manimlib.mobject.types.vectorized_mobject import DashedVMobject from manimlib.mobject.types.vectorized_mobject import VGroup 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_pairs from manimlib.utils.simple_functions import clip @@ -684,7 +686,7 @@ class Arrow(Line): if self.path_arc > 0 and self.buff > 0: self.insert_n_curves(10) # Is this needed? self.pointwise_become_partial(self, 0.0, 1.0 - alpha) - self.append_points([self.get_end(), self.get_end()]) + self.add_line_to(self.get_end()) self.add_line_to(prev_end) self.n_tip_points = 3 return self From 916ab94efd402b730b44455fb9dd1f3aff864699 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 9 Oct 2023 14:17:44 -0500 Subject: [PATCH 54/65] Remove white space --- manimlib/mobject/types/image_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/types/image_mobject.py b/manimlib/mobject/types/image_mobject.py index 80efe5aa..b49872de 100644 --- a/manimlib/mobject/types/image_mobject.py +++ b/manimlib/mobject/types/image_mobject.py @@ -25,7 +25,7 @@ class ImageMobject(Mobject): ('opacity', np.float32, (1,)), ] - def __init__( + def __init__( self, filename: str, height: float = 4.0, From f4778b57ef0254b5b05d0ea60f1fb15d31d731fd Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:31:16 -0500 Subject: [PATCH 55/65] Have Animation keep track of whether a mobject had had it's updating suspended before resuming it at the end --- manimlib/animation/animation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/animation/animation.py b/manimlib/animation/animation.py index a8ece9ca..115e9839 100644 --- a/manimlib/animation/animation.py +++ b/manimlib/animation/animation.py @@ -74,6 +74,7 @@ class Animation(object): # It is, however, okay and desirable to call # the internal updaters of self.starting_mobject, # or any others among self.get_all_mobjects() + self.mobject_was_updating = not self.mobject.updating_suspended self.mobject.suspend_updating() self.families = list(self.get_all_families_zipped()) self.interpolate(0) @@ -81,7 +82,7 @@ class Animation(object): def finish(self) -> None: self.interpolate(self.final_alpha_value) 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() def clean_up_from_scene(self, scene: Scene) -> None: From cbc32468bf2b23610294b534ec77ef134b20c506 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:31:52 -0500 Subject: [PATCH 56/65] Note chanted stroke and fill after DrawBorderThenFill has complete --- manimlib/animation/creation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index ea992801..d82b8a06 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -139,6 +139,8 @@ class DrawBorderThenFill(Animation): submob.pointwise_become_partial(outline, 0, subalpha) else: submob.interpolate(outline, start, subalpha) + submob.note_changed_stroke() + submob.note_changed_fill() class Write(DrawBorderThenFill): From 0d046a7eab8c5272a40fd14a656b34abc90caed7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:32:27 -0500 Subject: [PATCH 57/65] Add an option for a graph to continually update to its defining function --- manimlib/mobject/coordinate_systems.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 1c4429f2..1acde16f 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -175,6 +175,7 @@ class CoordinateSystem(ABC): self, function: Callable[[float], float], x_range: Sequence[float] | None = None, + bind: bool = False, **kwargs ) -> ParametricCurve: x_range = x_range or self.x_range @@ -195,6 +196,10 @@ class CoordinateSystem(ABC): ) graph.underlying_function = function graph.x_range = x_range + + if bind: + self.bind_graph_to_func(graph, function) + return graph def get_parametric_curve( From 2cdb85cae90b41e5fe43abc2e63a2d8d5db2ec25 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:32:47 -0500 Subject: [PATCH 58/65] Don't assign a fixed default depth to ThreeDAxes --- manimlib/mobject/coordinate_systems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 1acde16f..fcd11612 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -529,7 +529,7 @@ class ThreeDAxes(Axes): z_range: RangeSpecifier = (-4.0, 4.0, 1.0), z_axis_config: dict = dict(), z_normal: Vect3 = DOWN, - depth: float = 6.0, + depth: float | None = None, flat_stroke: bool = False, **kwargs ): From 17cd5979046db60b7b5885f10fe0a708301c76dc Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:33:26 -0500 Subject: [PATCH 59/65] Have Arrow track what stroke width it was set to --- manimlib/mobject/geometry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index cbf66ee4..2f1a4730 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -654,6 +654,7 @@ class Arrow(Line): 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.n_tip_points = 3 + self.original_stroke_width = stroke_width super().__init__( start, end, stroke_color=stroke_color, @@ -695,10 +696,11 @@ class Arrow(Line): def create_tip_with_stroke_width(self) -> Self: if self.get_num_points() < 3: return self - tip_width = self.tip_width_ratio * min( - float(self.get_stroke_width()), + stroke_width = min( + self.original_stroke_width, self.max_width_to_length_ratio * self.get_length(), ) + tip_width = self.tip_width_ratio * stroke_width 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) @@ -718,6 +720,7 @@ class Arrow(Line): *args, **kwargs ) -> Self: super().set_stroke(color=color, width=width, *args, **kwargs) + self.original_stroke_width = self.get_stroke_width() if self.has_points(): self.reset_tip() return self From 246a010799d8554f7e532daab4a48299db2378bd Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:33:56 -0500 Subject: [PATCH 60/65] Add default border width to StringMobject --- manimlib/mobject/svg/string_mobject.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/manimlib/mobject/svg/string_mobject.py b/manimlib/mobject/svg/string_mobject.py index c675254d..a723601d 100644 --- a/manimlib/mobject/svg/string_mobject.py +++ b/manimlib/mobject/svg/string_mobject.py @@ -47,6 +47,7 @@ class StringMobject(SVGMobject, ABC): self, string: str, fill_color: ManimColor = WHITE, + fill_border_width: float = 0.5, stroke_color: ManimColor = WHITE, stroke_width: float = 0, base_color: ManimColor = WHITE, @@ -65,12 +66,10 @@ class StringMobject(SVGMobject, ABC): self.use_labelled_svg = use_labelled_svg self.parse() - super().__init__( - stroke_color=stroke_color, - fill_color=fill_color, - stroke_width=stroke_width, - **kwargs - ) + super().__init__(**kwargs) + self.set_stroke(stroke_color, stroke_width) + self.set_fill(fill_color, border_width=fill_border_width) + self.note_changed_stroke() self.labels = [submob.label for submob in self.submobjects] def get_file_path(self, is_labelled: bool = False) -> str: From 8a4d7b4e8cec4239e5ef58a4ef9d4dbb0f07adab Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:34:39 -0500 Subject: [PATCH 61/65] Add a small hack to ensure Window resets properly in non-primary monitors --- manimlib/window.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/manimlib/window.py b/manimlib/window.py index 299a4c8d..152da54d 100644 --- a/manimlib/window.py +++ b/manimlib/window.py @@ -27,7 +27,7 @@ class Window(PygletWindow): self, scene: Scene, size: tuple[int, int] = (1280, 720), - samples = 0 + samples: int = 0 ): scene.window = self super().__init__(size=size, samples=samples) @@ -47,9 +47,12 @@ class Window(PygletWindow): self.to_default_position() def to_default_position(self): - self.size = self.default_size 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]: custom_position = get_customization()["window_position"] From dcf3eb8416cd096cb46f3fedf9cd3e16347c7347 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 6 Nov 2023 12:36:34 -0500 Subject: [PATCH 62/65] Ignore pyright configuration --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6bbcdb2e..71be35c4 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,7 @@ ipython_config.py # pyenv .python-version +pyrightconfig.json # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. From f8fedffa4c4aefb18bf736067fdbe62da2b9d073 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 2 Dec 2023 21:28:22 -0600 Subject: [PATCH 63/65] Use rate function on MoveAlongPath --- manimlib/animation/movement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/animation/movement.py b/manimlib/animation/movement.py index 410c15c3..c208aea9 100644 --- a/manimlib/animation/movement.py +++ b/manimlib/animation/movement.py @@ -114,5 +114,5 @@ class MoveAlongPath(Animation): super().__init__(mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs) def interpolate_mobject(self, alpha: float) -> None: - point = self.path.quick_point_from_proportion(alpha) + point = self.path.quick_point_from_proportion(self.rate_func(alpha)) self.mobject.move_to(point) From 88672a21ff84cfe4c4f82cc7d8663aa7e404eeeb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 17 Jan 2024 15:01:49 -0600 Subject: [PATCH 64/65] Include texture id in shader wrapper id This ensure that, among other things, ImageMobjects appearing in groups don't get lumped together in rendering. --- manimlib/shader_wrapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 9c412173..8fa3801e 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -138,6 +138,7 @@ class ShaderWrapper(object): self.mobject_uniforms, self.depth_test, self.render_primitive, + self.texture_names_to_ids, ])) def refresh_id(self) -> None: From 41ece958fd1fe5b0138255d3003e321df3d6ad01 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 17 Jan 2024 15:02:19 -0600 Subject: [PATCH 65/65] Explicitly call out global naure of ID_TO_TEXTURE map --- manimlib/utils/shaders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py index ec915b25..18b3d24e 100644 --- a/manimlib/utils/shaders.py +++ b/manimlib/utils/shaders.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: from moderngl.framebuffer import Framebuffer +# Global maps updated as textures are allocated ID_TO_TEXTURE: dict[int, moderngl.Texture] = dict() PROGRAM_UNIFORM_MIRRORS: dict[int, dict[str, float | tuple]] = dict()