From f2d71e652193f2f0d4f7b545103130ac95e09689 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 09:20:05 -0800 Subject: [PATCH 01/24] Don't rotate Laptop into place --- manimlib/mobject/svg/drawings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 0f2764a3..4b0449ae 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -251,8 +251,6 @@ class Laptop(VGroup): self.axis = axis self.add(body, screen_plate, axis) - self.rotate(5 * np.pi / 12, LEFT, about_point=ORIGIN) - self.rotate(np.pi / 6, DOWN, about_point=ORIGIN) class VideoIcon(SVGMobject): From 5952f9ea747365dbef82d53a3a600b336f1ee61c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 09:48:23 -0800 Subject: [PATCH 02/24] Make sure rgbas will be resized if bigger than Mobject.data --- 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 19f758fd..213bf303 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1320,7 +1320,7 @@ class Mobject(object): data = mob.data if mob.has_points() > 0 else mob._data_defaults if color is not None: rgbs = np.array(list(map(color_to_rgb, listify(color)))) - if 1 < len(rgbs) < len(data): + if 1 < len(rgbs): rgbs = resize_with_interpolation(rgbs, len(data)) data[name][:, :3] = rgbs if opacity is not None: From 424db4b3e492a5a9e51c8b62b75ded2f6ef9b1cb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 12:20:25 -0800 Subject: [PATCH 03/24] Ensure view matrix is not computed more than it needs to be --- manimlib/camera/camera_frame.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/manimlib/camera/camera_frame.py b/manimlib/camera/camera_frame.py index 9c01af73..e0bc120b 100644 --- a/manimlib/camera/camera_frame.py +++ b/manimlib/camera/camera_frame.py @@ -29,6 +29,9 @@ class CameraFrame(Mobject): ): super().__init__(**kwargs) + self.uniforms["orientation"] = Rotation.identity().as_quat() + self.uniforms["fovy"] = fovy + self.view_matrix = np.identity(4) self.default_orientation = Rotation.identity() @@ -36,8 +39,10 @@ class CameraFrame(Mobject): self.set_width(frame_shape[0], stretch=True) self.set_height(frame_shape[1], stretch=True) self.move_to(center_point) - self.uniforms["orientation"] = Rotation.identity().as_quat() - self.uniforms["fovy"] = fovy + + def note_changed_data(self, recurse_up: bool = True): + super().note_changed_data(recurse_up) + self.get_view_matrix(refresh=True) def set_orientation(self, rotation: Rotation): self.uniforms["orientation"][:] = rotation.as_quat() @@ -77,20 +82,32 @@ class CameraFrame(Mobject): def get_inverse_camera_rotation_matrix(self): return self.get_orientation().as_matrix().T - def get_view_matrix(self): + def get_view_matrix(self, refresh=False): """ Returns a 4x4 for the affine transformation mapping a point into the camera's internal coordinate system """ - shift = Matrix44.from_translation(-self.get_center()).T - rotation = Matrix44.from_quaternion(self.uniforms["orientation"]).T - scale = Matrix44(np.identity(3) / self.get_scale()) - self.view_matrix[:] = shift * rotation * scale + if refresh: + shift = np.identity(4) + rotation = np.identity(4) + scale = np.identity(4) + + shift[:3, 3] = -self.get_center() + rotation[:3, :3] = self.get_inverse_camera_rotation_matrix() + scale[:3, :3] /= self.get_scale() + + self.view_matrix = np.dot(scale, np.dot(rotation, shift)) + return self.view_matrix def get_inv_view_matrix(self): return np.linalg.inv(self.get_view_matrix()) + def interpolate(self, *args, **kwargs): + super().interpolate(*args, **kwargs) + self.get_view_matrix(refresh=True) + + @Mobject.affects_data def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): rot = Rotation.from_rotvec(angle * normalize(axis)) self.set_orientation(rot * self.get_orientation()) From 92e4d43ca3cb80a52b4c00d024cd6ab02b908a97 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 12:29:49 -0800 Subject: [PATCH 04/24] Make sure camera location is not computed more times than it needs to be --- manimlib/camera/camera_frame.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/manimlib/camera/camera_frame.py b/manimlib/camera/camera_frame.py index e0bc120b..cd8b6792 100644 --- a/manimlib/camera/camera_frame.py +++ b/manimlib/camera/camera_frame.py @@ -32,8 +32,9 @@ class CameraFrame(Mobject): self.uniforms["orientation"] = Rotation.identity().as_quat() self.uniforms["fovy"] = fovy - self.view_matrix = np.identity(4) self.default_orientation = Rotation.identity() + self.view_matrix = np.identity(4) + self.camera_location = OUT # This will be updated by set_points self.set_points(np.array([ORIGIN, LEFT, RIGHT, DOWN, UP])) self.set_width(frame_shape[0], stretch=True) @@ -43,6 +44,7 @@ class CameraFrame(Mobject): def note_changed_data(self, recurse_up: bool = True): super().note_changed_data(recurse_up) self.get_view_matrix(refresh=True) + self.get_implied_camera_location(refresh=True) def set_orientation(self, rotation: Rotation): self.uniforms["orientation"][:] = rotation.as_quat() @@ -103,9 +105,9 @@ class CameraFrame(Mobject): def get_inv_view_matrix(self): return np.linalg.inv(self.get_view_matrix()) + @Mobject.affects_data def interpolate(self, *args, **kwargs): super().interpolate(*args, **kwargs) - self.get_view_matrix(refresh=True) @Mobject.affects_data def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): @@ -198,10 +200,12 @@ class CameraFrame(Mobject): def get_field_of_view(self) -> float: return self.uniforms["fovy"] - def get_implied_camera_location(self) -> np.ndarray: - to_camera = self.get_inverse_camera_rotation_matrix()[2] - dist = self.get_focal_distance() - return self.get_center() + dist * to_camera + def get_implied_camera_location(self, refresh=False) -> np.ndarray: + if refresh: + to_camera = self.get_inverse_camera_rotation_matrix()[2] + dist = self.get_focal_distance() + self.camera_location = self.get_center() + dist * to_camera + return self.camera_location def to_fixed_frame_point(self, point: Vect3, relative: bool = False): view = self.get_view_matrix() From 60aae748a7dd9183153ccc649b1f1a522cf780f9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 12:49:02 -0800 Subject: [PATCH 05/24] Make sure animations will trigger a refresh for joint products --- manimlib/animation/creation.py | 6 +----- manimlib/mobject/types/vectorized_mobject.py | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index 66716df8..ac73891d 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -98,11 +98,7 @@ class DrawBorderThenFill(Animation): self.mobject = vmobject def begin(self) -> None: - # Trigger triangulation calculation - for submob in self.mobject.get_family(): - if not submob._use_winding_fill: - submob.get_triangulation() - + self.mobject.set_animating_status(True) self.outline = self.get_outline() super().begin() self.mobject.match_style(self.outline) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 27051f79..a0185041 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1239,6 +1239,15 @@ class VMobject(Mobject): super().apply_points_function(*args, **kwargs) self.refresh_joint_products() + def set_animating_status(self, is_animating: bool, recurse: bool = True): + super().set_animating_status(is_animating, recurse) + if is_animating: + for submob in self.get_family(recurse): + submob.get_joint_products(refresh=True) + if not submob._use_winding_fill: + submob.get_triangulation() + return self + # For shaders def init_shader_data(self, ctx: Context): dtype = self.shader_dtype From a4d9b101ded655f2f9cf1db7fb256d687d7ee29c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 12:53:25 -0800 Subject: [PATCH 06/24] Whoops, make sure deepcopy actually returns --- manimlib/mobject/mobject.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 213bf303..06deeec5 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -599,6 +599,7 @@ class Mobject(object): result = copy.deepcopy(self) result._shaders_initialized = False result._data_has_changed = True + return result def copy(self, deep: bool = False): if deep: From 1e46847a697915d16891a957ec0589ab9b29d39f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 14:32:32 -0800 Subject: [PATCH 07/24] Use Iterator type for Mobject.__iter__ --- manimlib/mobject/mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index e64e4b6b..e414790f 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -48,7 +48,7 @@ from manimlib.utils.space_ops import rotation_matrix_transpose from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Callable, Iterable, Union, Tuple, Optional, Self + from typing import Callable, Iterable, Iterator, Union, Tuple, Optional, Self import numpy.typing as npt from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array, UniformDict from moderngl.context import Context @@ -350,7 +350,7 @@ class Mobject(object): return GroupClass(*self.split().__getitem__(value)) return self.split().__getitem__(value) - def __iter__(self) -> Iterable[Self]: + def __iter__(self) -> Iterator[Self]: return iter(self.split()) def __len__(self) -> int: From 7e78e769667760227dc2a42c1ceec498e976fb69 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 14:46:28 -0800 Subject: [PATCH 08/24] Only call become at the end of Transform if the rate func ends at 1 --- manimlib/animation/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/animation/transform.py b/manimlib/animation/transform.py index 012bfad9..58c4886e 100644 --- a/manimlib/animation/transform.py +++ b/manimlib/animation/transform.py @@ -70,7 +70,7 @@ class Transform(Animation): def finish(self) -> None: super().finish() self.mobject.unlock_data() - if self.target_mobject is not None: + if self.target_mobject is not None and self.rate_func(1) == 1: self.mobject.become(self.target_mobject) def create_target(self) -> Mobject: From 077f264890d16ad9353149f09e3119e8fb7a38e4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 14:48:26 -0800 Subject: [PATCH 09/24] In Mobject.become, match needs_new_bounding_box status --- manimlib/mobject/mobject.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index e414790f..ac66d291 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -695,6 +695,7 @@ class Mobject(object): sm1.texture_paths = sm2.texture_paths sm1.depth_test = sm2.depth_test sm1.render_primitive = sm2.render_primitive + sm1.needs_new_bounding_box = sm2.needs_new_bounding_box # Make sure named family members carry over for attr, value in list(mobject.__dict__.items()): if isinstance(value, Mobject) and value in family2: From f42b3bfa3ee1fde8ea07ba6fcdc44b15912c6c8a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 14:49:07 -0800 Subject: [PATCH 10/24] In UpdatersExample, use Tex.make_number_changable --- example_scenes.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/example_scenes.py b/example_scenes.py index bf817fed..33bc000b 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -268,16 +268,8 @@ class UpdatersExample(Scene): # that of the newly constructed object brace = always_redraw(Brace, square, UP) - text, number = label = VGroup( - Text("Width = "), - DecimalNumber( - 0, - show_ellipsis=True, - num_decimal_places=2, - include_sign=True, - ) - ) - label.arrange(RIGHT) + label = TexText("Width = 0.00") + number = label.make_number_changable("0.00") # This ensures that the method deicmal.next_to(square) # is called on every frame From 3b0c9581897d1a55a558e7649f35938b0b493030 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 15:37:03 -0800 Subject: [PATCH 11/24] Check case of scale = 0 in get_view_matrix --- manimlib/camera/camera_frame.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/manimlib/camera/camera_frame.py b/manimlib/camera/camera_frame.py index cd8b6792..deafefe3 100644 --- a/manimlib/camera/camera_frame.py +++ b/manimlib/camera/camera_frame.py @@ -92,13 +92,15 @@ class CameraFrame(Mobject): if refresh: shift = np.identity(4) rotation = np.identity(4) - scale = np.identity(4) + scale_mat = np.identity(4) shift[:3, 3] = -self.get_center() rotation[:3, :3] = self.get_inverse_camera_rotation_matrix() - scale[:3, :3] /= self.get_scale() + scale = self.get_scale() + if scale > 0: + scale_mat[:3, :3] /= self.get_scale() - self.view_matrix = np.dot(scale, np.dot(rotation, shift)) + self.view_matrix = np.dot(scale_mat, np.dot(rotation, shift)) return self.view_matrix From 93e65fa3e783a45ee153b437a674536abf233267 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 15:37:23 -0800 Subject: [PATCH 12/24] Prevent needless extra copying in Mobject.copy --- manimlib/mobject/mobject.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index ac66d291..4f1cfa24 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -617,7 +617,6 @@ class Mobject(object): # copy.copy is only a shallow copy, so the internal # data which are numpy arrays or other mobjects still # need to be further copied. - result.data = self.data.copy() result.uniforms = { key: value.copy() if isinstance(value, np.ndarray) else value for key, value in self.uniforms.items() @@ -636,15 +635,14 @@ class Mobject(object): result.non_time_updaters = list(self.non_time_updaters) result.time_based_updaters = list(self.time_based_updaters) result._data_has_changed = True + result._shaders_initialized = False family = self.get_family() for attr, value in self.__dict__.items(): if isinstance(value, Mobject) and value is not self: if value in family: setattr(result, attr, result.family[self.family.index(value)]) - if isinstance(value, np.ndarray): - setattr(result, attr, value.copy()) - if isinstance(value, ShaderWrapper): + elif isinstance(value, np.ndarray): setattr(result, attr, value.copy()) return result From d9c85aac469aec373cf750d745ad167bf910f332 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 15:37:30 -0800 Subject: [PATCH 13/24] Add dict_eq --- manimlib/utils/dict_ops.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/manimlib/utils/dict_ops.py b/manimlib/utils/dict_ops.py index 6dcaa7d1..36d27d7b 100644 --- a/manimlib/utils/dict_ops.py +++ b/manimlib/utils/dict_ops.py @@ -1,4 +1,5 @@ import itertools as it +import numpy as np def merge_dicts_recursively(*dicts): @@ -29,3 +30,19 @@ def soft_dict_update(d1, d2): for key, value in list(d2.items()): if key not in d1: d1[key] = value + + +def dict_eq(d1, d2): + if len(d1) != len(d2): + return False + for key in d1: + value1 = d1[key] + value2 = d2[key] + if type(value1) != type(value2): + return False + if type(d1[key]) == np.ndarray: + if any(d1[key] != d2[key]): + return False + elif d1[key] != d2[key]: + return False + return True From fca5770b9f729e2edaeb754947e36e3cebdfb1e4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 16:06:53 -0800 Subject: [PATCH 14/24] Fix Camera.blit --- manimlib/camera/camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 557ea618..fe5c3187 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -133,7 +133,7 @@ class Camera(object): gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, dst_fbo.glo) gl.glBlitFramebuffer( *src_fbo.viewport, - *src_fbo.viewport, + *dst_fbo.viewport, gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR ) From d3a4d81a63ae0f7f1b57bf6a1b500e38756cc18e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 16:08:43 -0800 Subject: [PATCH 15/24] Remove commended code --- manimlib/camera/camera.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index fe5c3187..08fe1abc 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -138,15 +138,6 @@ class Camera(object): ) def get_raw_fbo_data(self, dtype: str = 'f1') -> bytes: - # # Copy blocks from fbo into draw_fbo using Blit - # gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo.glo) - # gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.draw_fbo.glo) - # src_viewport = self.fbo.viewport - # gl.glBlitFramebuffer( - # *src_viewport, - # *self.draw_fbo.viewport, - # gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR - # ) self.blit(self.fbo, self.draw_fbo) return self.draw_fbo.read( viewport=self.draw_fbo.viewport, From f858a439dd8a64121bc32e0b282daa35f68b764f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 20:15:48 -0800 Subject: [PATCH 16/24] Make alignment between VMobjects conducive to smoother interpolation --- manimlib/mobject/types/vectorized_mobject.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 8ecac23b..01164287 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -890,6 +890,11 @@ class VMobject(Mobject): # Figure out what the subpaths are, and align subpaths1 = self.get_subpaths() subpaths2 = vmobject.get_subpaths() + for subpaths in [subpaths1, subpaths2]: + subpaths.sort(key=lambda sp: -sum( + get_norm(p2 - p1) + for p1, p2 in zip(sp, sp[1:]) + )) n_subpaths = max(len(subpaths1), len(subpaths2)) # Start building new ones @@ -898,7 +903,7 @@ class VMobject(Mobject): def get_nth_subpath(path_list, n): if n >= len(path_list): - return [path_list[-1][-1]] + return np.vstack([path_list[0][:-1], path_list[0][::-1]]) return path_list[n] for n in range(n_subpaths): From c8d1ee5c88448f682f368713b064b16b7d8dda92 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 20:16:05 -0800 Subject: [PATCH 17/24] No longer any need for specialized invisible_copy --- manimlib/mobject/types/vectorized_mobject.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 01164287..481c6128 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -927,14 +927,6 @@ class VMobject(Mobject): mob.get_joint_products() return self - def invisible_copy(self) -> Self: - result = self.copy() - if not result.has_fill() or result.get_num_points() == 0: - return result - result.append_vectorized_mobject(self.copy().reverse_points()) - result.set_opacity(0) - return result - def insert_n_curves(self, n: int, recurse: bool = True) -> Self: for mob in self.get_family(recurse): if mob.get_num_curves() > 0: From bc38165d448307a3f54c2c9fd96cc76f0e50531f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 20:16:20 -0800 Subject: [PATCH 18/24] Allow for matched_pairs arg to TransformMatchingStrings --- manimlib/animation/transform_matching_parts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/animation/transform_matching_parts.py b/manimlib/animation/transform_matching_parts.py index 6f44c16d..b832cf0e 100644 --- a/manimlib/animation/transform_matching_parts.py +++ b/manimlib/animation/transform_matching_parts.py @@ -131,9 +131,10 @@ class TransformMatchingStrings(TransformMatchingParts): target: StringMobject, matched_keys: Iterable[str] = [], key_map: dict[str, str] = dict(), + matched_pairs: Iterable[tuple[Mobject, Mobject]] = [], **kwargs, ): - matched_pairs = [ + matched_pairs = list(matched_pairs) + [ *[(source[key], target[key]) for key in matched_keys], *[(source[key1], target[key2]) for key1, key2 in key_map.items()], *[ From 7c087838a3f22ac17e0b4b9ca07f31263fad3345 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 31 Jan 2023 21:33:36 -0800 Subject: [PATCH 19/24] Change backstroke defaults in OpeningScene --- example_scenes.py | 4 ++-- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example_scenes.py b/example_scenes.py index 33bc000b..9aa3e108 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -31,7 +31,7 @@ class OpeningManimExample(Scene): ) linear_transform_words.arrange(RIGHT) linear_transform_words.to_edge(UP) - linear_transform_words.set_stroke(BLACK, 10, background=True) + linear_transform_words.set_backstroke(width=5) self.play( ShowCreation(grid), @@ -52,7 +52,7 @@ class OpeningManimExample(Scene): this is the map $z \\rightarrow z^2$ """) complex_map_words.to_corner(UR) - complex_map_words.set_stroke(BLACK, 5, background=True) + complex_map_words.set_backstroke(width=5) self.play( FadeOut(grid), diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 81103c18..3297d62b 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -89,7 +89,7 @@ vec3 get_perp(int index, vec4 joint_product, vec3 point, vec3 tangent, float aaw */ float buff = 0.5 * v_stroke_width[index] + aaw; // Add correction for sharp angles to prevent weird bevel effects - if(joint_product.w < -0.9) buff *= 10 * (joint_product.w + 1.0); + if(joint_product.w < -0.75) buff *= 4 * (joint_product.w + 1.0); vec3 normal = get_joint_unit_normal(joint_product); // Set global unit normal unit_normal = normal; From b1fb3e1d54eb7a454c379d916bdceb9806f5cd6b Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 11:19:22 -0800 Subject: [PATCH 20/24] Add render mode and early discard for fill canvas vao --- manimlib/utils/shaders.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py index 846c08d5..4f589d5b 100644 --- a/manimlib/utils/shaders.py +++ b/manimlib/utils/shaders.py @@ -159,6 +159,7 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tu void main() { color = texture(Texture, v_textcoord); + if(color.a == 0) discard; if(distance(color.rgb, null_rgb) < MIN_DIST_TO_NULL) discard; // Un-blend from the null value @@ -180,5 +181,6 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tu simple_program, ctx.buffer(verts.astype('f4').tobytes()), 'texcoord', + mode=moderngl.TRIANGLE_STRIP ) return (texture_fbo, fill_texture_vao, null_rgb) From 0c9afb65d9c7bee290f1eb41562170a42bf363c8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 11:19:40 -0800 Subject: [PATCH 21/24] Small clean up to render calls --- manimlib/shader_wrapper.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 1f522fe7..2c949057 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -280,12 +280,10 @@ class FillShaderWrapper(ShaderWrapper): self.fill_canvas = get_fill_canvas(self.ctx) def render(self): - vao = self.vao - assert(vao is not None) winding = (len(self.vert_indices) == 0) - vao.program['winding'].value = winding + self.program['winding'].value = winding if not winding: - vao.render() + super().render() return original_fbo = self.ctx.fbo @@ -301,14 +299,13 @@ class FillShaderWrapper(ShaderWrapper): gl.GL_ONE, gl.GL_ONE, ) gl.glBlendEquationSeparate(gl.GL_FUNC_ADD, gl.GL_MAX) - self.ctx.blend_equation = moderngl.FUNC_ADD, moderngl.MAX - vao.render() + super().render() original_fbo.use() gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glBlendEquation(gl.GL_FUNC_ADD) - texture_vao.render(moderngl.TRIANGLE_STRIP) + texture_vao.render() gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) From 04733ac32ea50cf2ecca4e1212ee5e86579f7110 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 11:20:09 -0800 Subject: [PATCH 22/24] Default to fully opaque background rectangle --- manimlib/mobject/mobject.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 4f1cfa24..4425ac36 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1436,11 +1436,9 @@ class Mobject(object): def add_background_rectangle( self, color: ManimColor | None = None, - opacity: float = 0.75, + opacity: float = 1.0, **kwargs ) -> Self: - # TODO, this does not behave well when the mobject has points, - # since it gets displayed on top from manimlib.mobject.shape_matchers import BackgroundRectangle self.background_rectangle = BackgroundRectangle( self, color=color, From b351c9f1c8881b2c154f1d97ad22db05592f4726 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 11:20:42 -0800 Subject: [PATCH 23/24] Add smaller h_buff to matrix in OpeningManimExample --- example_scenes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_scenes.py b/example_scenes.py index 9aa3e108..3134d484 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -26,7 +26,7 @@ class OpeningManimExample(Scene): matrix = [[1, 1], [0, 1]] linear_transform_words = VGroup( Text("This is what the matrix"), - IntegerMatrix(matrix, include_background_rectangle=True), + IntegerMatrix(matrix, include_background_rectangle=True, h_buff=1.0), Text("looks like") ) linear_transform_words.arrange(RIGHT) From 280090a7c906f6d6cef9f9a8a04186de27f15b26 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 11:24:46 -0800 Subject: [PATCH 24/24] Small reorganization to VMobject.get_shader_wrapper_list, and default to fill border being drawn up front --- manimlib/mobject/types/vectorized_mobject.py | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 481c6128..8af35fe1 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1282,27 +1282,29 @@ class VMobject(Mobject): # Build up data lists fill_datas = [] - fill_border_datas = [] fill_indices = [] + fill_border_datas = [] stroke_datas = [] back_stroke_datas = [] for submob in family: submob.get_joint_products() + indices = submob.get_outer_vert_indices() has_fill = submob.has_fill() has_stroke = submob.has_stroke() - indices = submob.get_outer_vert_indices() - if has_stroke: - lst = back_stroke_datas if submob.stroke_behind else stroke_datas - lst.append(submob.data[stroke_names][indices]) - if has_fill: + back_stroke = has_stroke and submob.stroke_behind + front_stroke = has_stroke and not submob.stroke_behind + if back_stroke: + back_stroke_datas.append(submob.data[stroke_names][indices]) + if front_stroke: + stroke_datas.append(submob.data[stroke_names][indices]) + if has_fill and self._use_winding_fill: data = submob.data[fill_names] data["base_point"][:] = data["point"][0] - if self._use_winding_fill: - fill_datas.append(data[indices]) - else: - fill_datas.append(data) - fill_indices.append(submob.get_triangulation()) - if not has_stroke and has_fill: + fill_datas.append(data[indices]) + if has_fill and not self._use_winding_fill: + fill_datas.append(submob.data[fill_names]) + fill_indices.append(submob.get_triangulation()) + if has_fill and not front_stroke: # Add fill border names = list(stroke_names) names[names.index('stroke_rgba')] = 'fill_rgba' @@ -1313,11 +1315,9 @@ class VMobject(Mobject): fill_border_datas.append(border_stroke_data[indices]) shader_wrappers = [ - self.back_stroke_shader_wrapper.read_in( - [*back_stroke_datas, *fill_border_datas] - ), + self.back_stroke_shader_wrapper.read_in(back_stroke_datas), self.fill_shader_wrapper.read_in(fill_datas, fill_indices or None), - self.stroke_shader_wrapper.read_in(stroke_datas), + self.stroke_shader_wrapper.read_in([*fill_border_datas, *stroke_datas]), ] # TODO, account for submob uniforms separately? self.uniforms.update(family[0].uniforms)