From 7050c7e7b00a271e794adce247fb6361432c88b7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 14:11:30 -0800 Subject: [PATCH 01/38] Change type for Surface.shader_dtype --- manimlib/mobject/types/surface.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/manimlib/mobject/types/surface.py b/manimlib/mobject/types/surface.py index cc620fa04e..d03150767f 100644 --- a/manimlib/mobject/types/surface.py +++ b/manimlib/mobject/types/surface.py @@ -5,7 +5,6 @@ from manimlib.constants import GREY from manimlib.constants import OUT -from manimlib.constants import ORIGIN from manimlib.mobject.mobject import Mobject from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import interpolate @@ -26,12 +25,12 @@ class Surface(Mobject): render_primitive: int = moderngl.TRIANGLES shader_folder: str = "surface" - shader_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [ + shader_dtype: np.dtype = np.dtype([ ('point', np.float32, (3,)), ('du_point', np.float32, (3,)), ('dv_point', np.float32, (3,)), ('rgba', np.float32, (4,)), - ] + ]) pointlike_data_keys = ['point', 'du_point', 'dv_point'] def __init__( From f15ac811311dd10224b86d179206ec0ab03036c4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 14:18:35 -0800 Subject: [PATCH 02/38] Pull out helper functions from shader_wrapper.py --- manimlib/shader_wrapper.py | 45 ++------------------------------- manimlib/utils/shaders.py | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 43 deletions(-) create mode 100644 manimlib/utils/shaders.py diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 154cbfd52b..2d225a626f 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -7,16 +7,13 @@ import moderngl import numpy as np -from functools import lru_cache - -from manimlib.utils.directories import get_shader_dir -from manimlib.utils.file_ops import find_file from manimlib.utils.iterables import resize_array +from manimlib.utils.shaders import get_shader_code_from_file from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Iterable, List + from typing import List # Mobjects that should be rendered with @@ -174,41 +171,3 @@ def read_in( n_verts = new_n_verts n_points += len(data) return self - - -@lru_cache(maxsize=12) -def get_shader_code_from_file(filename: str) -> str | None: - if not filename: - return None - - try: - filepath = find_file( - filename, - directories=[get_shader_dir(), "/"], - extensions=[], - ) - except IOError: - return None - - with open(filepath, "r") as f: - result = f.read() - - # To share functionality between shaders, some functions are read in - # from other files an inserted into the relevant strings before - # passing to ctx.program for compiling - # Replace "#INSERT " lines with relevant code - insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE) - for line in insertions: - inserted_code = get_shader_code_from_file( - os.path.join("inserts", line.replace("#INSERT ", "")) - ) - result = result.replace(line, inserted_code) - return result - - -def get_colormap_code(rgb_list: Iterable[float]) -> str: - data = ",".join( - "vec3({}, {}, {})".format(*rgb) - for rgb in rgb_list - ) - return f"vec3[{len(rgb_list)}]({data})" diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py new file mode 100644 index 0000000000..2f71aefa84 --- /dev/null +++ b/manimlib/utils/shaders.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import os +import re +from functools import lru_cache + +from manimlib.utils.directories import get_shader_dir +from manimlib.utils.file_ops import find_file + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Sequence + + +@lru_cache(maxsize=12) +def get_shader_code_from_file(filename: str) -> str | None: + if not filename: + return None + + try: + filepath = find_file( + filename, + directories=[get_shader_dir(), "/"], + extensions=[], + ) + except IOError: + return None + + with open(filepath, "r") as f: + result = f.read() + + # To share functionality between shaders, some functions are read in + # from other files an inserted into the relevant strings before + # passing to ctx.program for compiling + # Replace "#INSERT " lines with relevant code + insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE) + for line in insertions: + inserted_code = get_shader_code_from_file( + os.path.join("inserts", line.replace("#INSERT ", "")) + ) + result = result.replace(line, inserted_code) + return result + + +def get_colormap_code(rgb_list: Sequence[float]) -> str: + data = ",".join( + "vec3({}, {}, {})".format(*rgb) + for rgb in rgb_list + ) + return f"vec3[{len(rgb_list)}]({data})" From 20222bc7e970fc197d6cbacc6b301a1f46e5a021 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 14:18:49 -0800 Subject: [PATCH 03/38] Update imports --- 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 266b1bb432..d80713f4f8 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -23,7 +23,6 @@ from manimlib.event_handler.event_listner import EventListner from manimlib.event_handler.event_type import EventType from manimlib.logger import log -from manimlib.shader_wrapper import get_colormap_code from manimlib.shader_wrapper import ShaderWrapper from manimlib.utils.color import color_gradient from manimlib.utils.color import color_to_rgb @@ -41,6 +40,7 @@ from manimlib.utils.bezier import interpolate from manimlib.utils.paths import straight_path from manimlib.utils.simple_functions import get_parameters +from manimlib.utils.shaders import get_colormap_code from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import rotation_matrix_transpose From 6e6a30c95a2f870cb3ac985e52102c4675ba6c55 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 14:28:53 -0800 Subject: [PATCH 04/38] Be sure joint_angles are updated immediately before being read into a shader wrapper --- 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 3a04cf01f1..fb329352d7 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1095,6 +1095,7 @@ def get_joint_angles(self, refresh: bool = False): # Compute angles, and read them into # the joint_angles array result = self.data["joint_angle"][:, 0] + result[:] = 0 dots = (vect_to_vert * vect_from_vert).sum(1) np.arccos(dots, out=result, where=((dots <= 1) & (dots >= -1))) # Assumes unit normal in the positive z direction @@ -1212,6 +1213,7 @@ def get_shader_wrapper_list(self) -> list[ShaderWrapper]: fill_datas.append(submob.data[fill_names]) fill_indices.append(submob.get_triangulation()) if submob.has_stroke(): + submob.get_joint_angles() if submob.stroke_behind: lst = back_stroke_data else: @@ -1235,8 +1237,6 @@ def get_shader_wrapper_list(self) -> list[ShaderWrapper]: return [sw for sw in shader_wrappers if len(sw.vert_data) > 0] def refresh_shader_data(self): - for submob in self.get_family(): - submob.get_joint_angles() self.get_shader_wrapper_list() From c3cd64f68cf764c14505cc2cc96b8bf1fb3ca858 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 19:33:57 -0800 Subject: [PATCH 05/38] Package reflectiveness, gloss and shadow into a single uniform "shading" --- manimlib/mobject/coordinate_systems.py | 1 - manimlib/mobject/mobject.py | 61 +++++++++++-------- manimlib/mobject/svg/drawings.py | 3 +- manimlib/mobject/three_dimensions.py | 23 +++---- manimlib/mobject/types/dot_cloud.py | 10 ++- manimlib/mobject/types/surface.py | 10 +-- manimlib/mobject/types/vectorized_mobject.py | 20 ++---- manimlib/shaders/inserts/finalize_color.glsl | 49 +++++---------- manimlib/shaders/mandelbrot_fractal/frag.glsl | 7 +-- manimlib/shaders/newton_fractal/frag.glsl | 7 +-- .../shaders/quadratic_bezier_fill/geom.glsl | 9 +-- .../shaders/quadratic_bezier_fill/vert.glsl | 5 +- .../shaders/quadratic_bezier_stroke/geom.glsl | 9 +-- .../shaders/quadratic_bezier_stroke/vert.glsl | 7 +-- manimlib/shaders/surface/vert.glsl | 22 ++----- manimlib/shaders/textured_surface/frag.glsl | 13 ++-- manimlib/shaders/textured_surface/vert.glsl | 10 +-- manimlib/shaders/true_dot/frag.glsl | 13 ++-- 18 files changed, 106 insertions(+), 173 deletions(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index b670d8ee01..e7bee7d83d 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -504,7 +504,6 @@ def __init__( z_normal: Vect3 = DOWN, depth: float = 6.0, num_axis_pieces: int = 20, - gloss: float = 0.5, **kwargs ): Axes.__init__(self, x_range, y_range, **kwargs) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index d80713f4f8..9fbefb350d 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -48,7 +48,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Callable, Iterable, Sequence, Union, Tuple + from typing import Callable, Iterable, Union, Tuple import numpy.typing as npt from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array @@ -76,12 +76,7 @@ def __init__( self, color: ManimColor = WHITE, opacity: float = 1.0, - # Larger reflectiveness makes things brighter when facing the light - reflectiveness: float = 0.0, - # Larger shadow makes faces opposite the light darker - shadow: float = 0.0, - # Makes parts bright where light gets reflected toward the camera - gloss: float = 0.0, + shading: Tuple[float, float, float] = (0.0, 0.0, 0.0), # For shaders texture_paths: dict[str, str] | None = None, # If true, the mobject will not get rotated according to camera position @@ -90,9 +85,7 @@ def __init__( ): self.color = color self.opacity = opacity - self.reflectiveness = reflectiveness - self.shadow = shadow - self.gloss = gloss + self.shading = shading self.texture_paths = texture_paths self.is_fixed_in_frame = is_fixed_in_frame self.depth_test = depth_test @@ -138,9 +131,7 @@ def init_data(self, length: int = 0): def init_uniforms(self): self.uniforms: dict[str, float | np.ndarray] = { "is_fixed_in_frame": float(self.is_fixed_in_frame), - "gloss": self.gloss, - "shadow": self.shadow, - "reflectiveness": self.reflectiveness, + "shading": np.array(self.shading, dtype=float), } def init_colors(self): @@ -1333,28 +1324,46 @@ def set_submobject_colors_by_gradient(self, *colors: ManimColor): def fade(self, darkness: float = 0.5, recurse: bool = True): self.set_opacity(1.0 - darkness, recurse=recurse) - def get_reflectiveness(self) -> float: - return self.uniforms["reflectiveness"] + def get_shading(self) -> np.ndarray: + return self.uniforms["shading"] - def set_reflectiveness(self, reflectiveness: float, recurse: bool = True): + def set_shading( + self, + reflectiveness: float | None = None, + gloss: float | None = None, + shadow: float | None = None, + recurse: bool = True + ): + """ + Larger reflectiveness makes things brighter when facing the light + Larger shadow makes faces opposite the light darker + Makes parts bright where light gets reflected toward the camera + """ for mob in self.get_family(recurse): - mob.uniforms["reflectiveness"] = reflectiveness + for i, value in enumerate([reflectiveness, gloss, shadow]): + if value is not None: + mob.uniforms["shading"][i] = value return self + def get_reflectiveness(self) -> float: + return self.get_shading()[0] + + def get_gloss(self) -> float: + return self.get_shading()[1] + def get_shadow(self) -> float: - return self.uniforms["shadow"] + return self.get_shading()[2] - def set_shadow(self, shadow: float, recurse: bool = True): - for mob in self.get_family(recurse): - mob.uniforms["shadow"] = shadow + def set_reflectiveness(self, reflectiveness: float, recurse: bool = True): + self.set_shading(reflectiveness=reflectiveness, recurse=recurse) return self - def get_gloss(self) -> float: - return self.uniforms["gloss"] - def set_gloss(self, gloss: float, recurse: bool = True): - for mob in self.get_family(recurse): - mob.uniforms["gloss"] = gloss + self.set_shading(gloss=gloss, recurse=recurse) + return self + + def set_shadow(self, shadow: float, recurse: bool = True): + self.set_shading(shadow=shadow, recurse=recurse) return self # Background rectangle diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 2ae13e9ac2..ed7e7f9d61 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -552,7 +552,7 @@ def sort_keys(self): class Piano3D(VGroup): def __init__( self, - reflectiveness: float = 1.0, + shading: Tuple[float, float, float] = (1.0, 0.2, 0.2), stroke_width: float = 0.25, stroke_color: ManimColor = BLACK, key_depth: float = 0.1, @@ -569,6 +569,7 @@ def __init__( for key in piano_2d )) self.set_stroke(stroke_color, stroke_width) + self.set_shading(*shading) self.apply_depth_test() # Elevate black keys diff --git a/manimlib/mobject/three_dimensions.py b/manimlib/mobject/three_dimensions.py index dc49f7abc1..65f3a610ef 100644 --- a/manimlib/mobject/three_dimensions.py +++ b/manimlib/mobject/three_dimensions.py @@ -23,7 +23,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Tuple, TypeVar - from manimlib.typing import ManimColor, Vect3 + from manimlib.typing import ManimColor, Vect3, Sequence T = TypeVar("T", bound=Mobject) @@ -252,7 +252,7 @@ def __init__( self, color: ManimColor = BLUE, opacity: float = 1, - gloss: float = 0.5, + shading: Tuple[float, float, float] = (0.1, 0.5, 0.1), square_resolution: Tuple[int, int] = (2, 2), side_length: float = 2, **kwargs, @@ -262,12 +262,9 @@ def __init__( side_length=side_length, color=color, opacity=opacity, + shading=shading, ) - super().__init__( - *square_to_cube_faces(face), - gloss=gloss, - **kwargs - ) + super().__init__(*square_to_cube_faces(face), **kwargs) class Prism(Cube): @@ -288,16 +285,12 @@ def __init__( self, *vmobjects: VMobject, depth_test: bool = True, - gloss: float = 0.2, - shadow: float = 0.2, - reflectiveness: float = 0.2, + shading: Tuple[float, float, float] = (0.2, 0.2, 0.2), joint_type: str = "no_joint", **kwargs ): super().__init__(*vmobjects, **kwargs) - self.set_gloss(gloss) - self.set_shadow(shadow) - self.set_reflectiveness(reflectiveness) + self.set_shading(*shading) self.set_joint_type(joint_type) if depth_test: self.apply_depth_test() @@ -342,7 +335,7 @@ def __init__( fill_opacity: float = 1, stroke_color: ManimColor = BLUE_E, stroke_width: float = 1, - reflectiveness: float = 0.2, + shading: Tuple[float, float, float] = (0.2, 0.2, 0.2), **kwargs, ): style = dict( @@ -350,7 +343,7 @@ def __init__( fill_opacity=fill_opacity, stroke_color=stroke_color, stroke_width=stroke_width, - reflectiveness=reflectiveness, + shading=shading, **kwargs ) diff --git a/manimlib/mobject/types/dot_cloud.py b/manimlib/mobject/types/dot_cloud.py index ff68462fe7..8e8e0a0e67 100644 --- a/manimlib/mobject/types/dot_cloud.py +++ b/manimlib/mobject/types/dot_cloud.py @@ -137,9 +137,13 @@ def scale( self.set_radii(scale_factor * self.get_radii()) return self - def make_3d(self, reflectiveness: float = 0.5, shadow: float = 0.2): - self.set_reflectiveness(reflectiveness) - self.set_shadow(shadow) + def make_3d( + self, + reflectiveness: float = 0.5, + gloss: float = 0.1, + shadow: float = 0.2 + ): + self.set_shading(reflectiveness, gloss, shadow) self.apply_depth_test() return self diff --git a/manimlib/mobject/types/surface.py b/manimlib/mobject/types/surface.py index d03150767f..101822f771 100644 --- a/manimlib/mobject/types/surface.py +++ b/manimlib/mobject/types/surface.py @@ -36,9 +36,7 @@ class Surface(Mobject): def __init__( self, color: ManimColor = GREY, - reflectiveness: float = 0.3, - gloss: float = 0.1, - shadow: float = 0.4, + shading: Tuple[float, float, float] = (0.3, 0.2, 0.4), depth_test: bool = True, u_range: Tuple[float, float] = (0.0, 1.0), v_range: Tuple[float, float] = (0.0, 1.0), @@ -61,9 +59,7 @@ def __init__( super().__init__( **kwargs, color=color, - reflectiveness=reflectiveness, - gloss=gloss, - shadow=shadow, + shading=shading, depth_test=depth_test, ) self.compute_triangle_indices() @@ -300,7 +296,7 @@ def __init__( self.resolution: Tuple[int, int] = uv_surface.resolution super().__init__( texture_paths=texture_paths, - gloss=uv_surface.gloss, + shading=tuple(uv_surface.shading), **kwargs ) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index fb329352d7..584dd6a8d9 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -158,7 +158,7 @@ def init_colors(self): opacity=self.stroke_opacity, background=self.stroke_behind, ) - self.set_gloss(self.gloss) + self.set_shading(*self.shading) self.set_flat_stroke(self.flat_stroke) self.color = self.get_color() return self @@ -229,9 +229,7 @@ def set_style( stroke_rgba: Vect4 | None = None, stroke_width: float | Iterable[float] | None = None, stroke_background: bool = True, - reflectiveness: float | None = None, - gloss: float | None = None, - shadow: float | None = None, + shading: Tuple[float, float, float] | None = None, recurse: bool = True ): for mob in self.get_family(recurse): @@ -260,12 +258,8 @@ def set_style( background=stroke_background, ) - if reflectiveness is not None: - mob.set_reflectiveness(reflectiveness, recurse=False) - if gloss is not None: - mob.set_gloss(gloss, recurse=False) - if shadow is not None: - mob.set_shadow(shadow, recurse=False) + if shading is not None: + mob.set_shading(*shading, recurse=False) return self def get_style(self): @@ -275,9 +269,7 @@ def get_style(self): "stroke_rgba": data['stroke_rgba'].copy(), "stroke_width": data['stroke_width'].copy(), "stroke_background": self.stroke_behind, - "reflectiveness": self.get_reflectiveness(), - "gloss": self.get_gloss(), - "shadow": self.get_shadow(), + "shading": self.get_shading(), } def match_style(self, vmobject: VMobject, recurse: bool = True): @@ -1160,7 +1152,7 @@ def apply_function( self.make_approximately_smooth() return self - def apply_points_function(self, *args, **kwargs,): + def apply_points_function(self, *args, **kwargs): super().apply_points_function(*args, **kwargs) self.refresh_joint_angles() diff --git a/manimlib/shaders/inserts/finalize_color.glsl b/manimlib/shaders/inserts/finalize_color.glsl index f02705cd22..72247062de 100644 --- a/manimlib/shaders/inserts/finalize_color.glsl +++ b/manimlib/shaders/inserts/finalize_color.glsl @@ -1,8 +1,6 @@ uniform vec3 light_source_position; uniform vec3 camera_position; -uniform float reflectiveness; -uniform float gloss; -uniform float shadow; +uniform vec3 shading; vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data){ float alpha = clamp((value - min_val) / (max_val - min_val), 0.0, 1.0); @@ -15,23 +13,16 @@ vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_ } -vec4 add_light( - vec4 color, - vec3 point, - vec3 unit_normal, - vec3 light_coords, - vec3 cam_coords, - float reflectiveness, - float gloss, - float shadow -){ - if(reflectiveness == 0.0 && gloss == 0.0 && shadow == 0.0) return color; +vec4 add_light(vec4 color, vec3 point, vec3 unit_normal){ + if(shading == vec3(0.0)) return color; + + float reflectiveness = shading.x; + float gloss = shading.y; + float shadow = shading.z; vec4 result = color; - // Assume everything has already been rotated such that camera is in the z-direction - // cam_coords = vec3(0, 0, focal_distance); - vec3 to_camera = normalize(cam_coords - point); - vec3 to_light = normalize(light_coords - point); + vec3 to_camera = normalize(camera_position - point); + vec3 to_light = normalize(light_position - point); // Note, this effectively treats surfaces as two-sided // if(dot(to_camera, unit_normal) < 0) unit_normal *= -1; @@ -48,26 +39,18 @@ vec4 add_light( result.rgb = mix(result.rgb, vec3(1.0), bright_factor); if (light_to_normal < 0){ // Darken - result.rgb = mix(result.rgb, vec3(0.0), -light_to_normal * shadow); + result.rgb = mix( + result.rgb, + vec3(0.0), + max(-light_to_normal, 0) * shadow + ); } return result; } -vec4 finalize_color( - vec4 color, - vec3 point, - vec3 unit_normal, - vec3 light_coords, - vec3 cam_coords, - float reflectiveness, - float gloss, - float shadow -){ +vec4 finalize_color(vec4 color, vec3 point, vec3 unit_normal){ ///// INSERT COLOR FUNCTION HERE ///// // The line above may be replaced by arbitrary code snippets, as per // the method Mobject.set_color_by_code - return add_light( - color, point, unit_normal, light_coords, cam_coords, - reflectiveness, gloss, shadow - ); + return add_light(color, point, unit_normal); } \ No newline at end of file diff --git a/manimlib/shaders/mandelbrot_fractal/frag.glsl b/manimlib/shaders/mandelbrot_fractal/frag.glsl index a75c838bf9..10e62f396b 100644 --- a/manimlib/shaders/mandelbrot_fractal/frag.glsl +++ b/manimlib/shaders/mandelbrot_fractal/frag.glsl @@ -64,11 +64,6 @@ void main() { frag_color = finalize_color( vec4(color, opacity), xyz_coords, - vec3(0.0, 0.0, 1.0), - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow + vec3(0.0, 0.0, 1.0) ); } \ No newline at end of file diff --git a/manimlib/shaders/newton_fractal/frag.glsl b/manimlib/shaders/newton_fractal/frag.glsl index c976038f10..64d7d2fa5a 100644 --- a/manimlib/shaders/newton_fractal/frag.glsl +++ b/manimlib/shaders/newton_fractal/frag.glsl @@ -144,11 +144,6 @@ void main() { frag_color = finalize_color( color, xyz_coords, - vec3(0.0, 0.0, 1.0), - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow + vec3(0.0, 0.0, 1.0) ); } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 27d838351c..b27b935943 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -33,14 +33,9 @@ void emit_vertex_wrapper(vec3 point, int index, vec3 unit_normal){ color = finalize_color( v_color[index], point, - unit_normal, - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow + unit_normal ); - gl_Position = get_gl_Position(point); + gl_Position = get_gl_Position(position_point_into_frame(point)); EmitVertex(); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 3b6976e8d0..3d2e2c9acd 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -10,11 +10,8 @@ out float v_orientation; out vec4 v_color; out float v_vert_index; -// Analog of import for manim only -#INSERT get_gl_Position.glsl - void main(){ - verts = position_point_into_frame(point); + verts = point; v_orientation = orientation; v_color = fill_rgba; v_vert_index = vert_index; diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index a51ed2dd29..f290710be2 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -177,14 +177,9 @@ void main() { color = finalize_color( v_color[vert_index], corners[i], - unit_normal, - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow + unit_normal ); - gl_Position = get_gl_Position(corners[i]); + gl_Position = get_gl_Position(position_point_into_frame(corners[i])); EmitVertex(); } EndPrimitive(); diff --git a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl index f2de716ad0..613edb15d4 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl @@ -1,5 +1,7 @@ #version 330 +uniform vec2 frame_shape; + in vec3 point; in vec4 stroke_rgba; in float stroke_width; @@ -15,11 +17,8 @@ out float v_vert_index; const float STROKE_WIDTH_CONVERSION = 0.01; -#INSERT get_gl_Position.glsl - void main(){ - verts = position_point_into_frame(point); - + verts = point; v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0; v_joint_angle = joint_angle; v_color = stroke_rgba; diff --git a/manimlib/shaders/surface/vert.glsl b/manimlib/shaders/surface/vert.glsl index 9e9a722a01..95b8e10828 100644 --- a/manimlib/shaders/surface/vert.glsl +++ b/manimlib/shaders/surface/vert.glsl @@ -7,32 +7,18 @@ in vec3 du_point; in vec3 dv_point; in vec4 rgba; -out vec3 xyz_coords; -out vec3 v_normal; out vec4 v_color; #INSERT get_gl_Position.glsl -#INSERT get_rotated_surface_unit_normal_vector.glsl +#INSERT get_unit_normal.glsl #INSERT finalize_color.glsl void main(){ - xyz_coords = position_point_into_frame(point); - v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point); - v_color = rgba; - gl_Position = get_gl_Position(xyz_coords); + gl_Position = get_gl_Position(position_point_into_frame(point)); + vec3 normal = get_unit_normal(point, du_point, dv_point); + v_color = finalize_color(rgba, point, normal); if(clip_plane.xyz != vec3(0.0, 0.0, 0.0)){ gl_ClipDistance[0] = dot(vec4(point, 1.0), clip_plane); } - - v_color = finalize_color( - rgba, - xyz_coords, - v_normal, - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow - ); } \ No newline at end of file diff --git a/manimlib/shaders/textured_surface/frag.glsl b/manimlib/shaders/textured_surface/frag.glsl index 65d4aa8f20..7d8010e681 100644 --- a/manimlib/shaders/textured_surface/frag.glsl +++ b/manimlib/shaders/textured_surface/frag.glsl @@ -4,7 +4,7 @@ uniform sampler2D LightTexture; uniform sampler2D DarkTexture; uniform float num_textures; -in vec3 xyz_coords; +in vec3 v_point; in vec3 v_normal; in vec2 v_im_coords; in float v_opacity; @@ -20,7 +20,7 @@ void main() { if(num_textures == 2.0){ vec4 dark_color = texture(DarkTexture, v_im_coords); float dp = dot( - normalize(light_source_position - xyz_coords), + normalize(light_source_position - v_point), normalize(v_normal) ); float alpha = smoothstep(-dark_shift, dark_shift, dp); @@ -29,13 +29,8 @@ void main() { frag_color = finalize_color( color, - xyz_coords, - normalize(v_normal), - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow + v_point, + normalize(v_normal) ); frag_color.a = v_opacity; } \ No newline at end of file diff --git a/manimlib/shaders/textured_surface/vert.glsl b/manimlib/shaders/textured_surface/vert.glsl index 1127622d8b..02b9afce3b 100644 --- a/manimlib/shaders/textured_surface/vert.glsl +++ b/manimlib/shaders/textured_surface/vert.glsl @@ -6,18 +6,18 @@ in vec3 dv_point; in vec2 im_coords; in float opacity; -out vec3 xyz_coords; +out vec3 v_point; out vec3 v_normal; out vec2 v_im_coords; out float v_opacity; #INSERT get_gl_Position.glsl -#INSERT get_rotated_surface_unit_normal_vector.glsl +#INSERT get_unit_normal.glsl void main(){ - xyz_coords = position_point_into_frame(point); - v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point); + v_point = point; + v_normal = get_unit_normal(point, du_point, dv_point); v_im_coords = im_coords; v_opacity = opacity; - gl_Position = get_gl_Position(xyz_coords); + gl_Position = get_gl_Position(position_point_into_frame(point)); } \ No newline at end of file diff --git a/manimlib/shaders/true_dot/frag.glsl b/manimlib/shaders/true_dot/frag.glsl index caf221ba11..a34d9ed05a 100644 --- a/manimlib/shaders/true_dot/frag.glsl +++ b/manimlib/shaders/true_dot/frag.glsl @@ -11,6 +11,10 @@ in float scaled_aaw; out vec4 frag_color; +// This include a delaration of +// uniform vec3 shading +// uniform vec3 camera_position +// uniform vec3 light_position #INSERT finalize_color.glsl void main() { @@ -21,17 +25,12 @@ void main() { discard; } frag_color = color; - if(gloss > 0 || shadow > 0){ + if(shading != vec3(0.0)){ vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius))); frag_color = finalize_color( frag_color, vec3(point.xy, 0.0), - normal, - light_source_position, - camera_position, - reflectiveness, - gloss, - shadow + normal ); } if(glow_factor > 0){ From 1f613953d6d0beaf9ee6d6ff9def60d3d0fea5c6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 19:34:20 -0800 Subject: [PATCH 06/38] Don't pre-rotate light source --- manimlib/camera/camera.py | 6 ++---- manimlib/shaders/inserts/finalize_color.glsl | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 3dbab48da4..027f218a66 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -490,9 +490,7 @@ def refresh_perspective_uniforms(self) -> None: # Orient light rotation = frame.get_inverse_camera_rotation_matrix() offset = frame.get_center() - light_pos = np.dot( - rotation, self.light_source.get_location() + offset - ) + light_pos = self.light_source.get_location() cam_pos = self.frame.get_implied_camera_location() # TODO self.perspective_uniforms = { @@ -501,7 +499,7 @@ def refresh_perspective_uniforms(self) -> None: "camera_offset": tuple(offset), "camera_rotation": tuple(np.array(rotation).T.flatten()), "camera_position": tuple(cam_pos), - "light_source_position": tuple(light_pos), + "light_position": tuple(light_pos), "focal_distance": frame.get_focal_distance(), } diff --git a/manimlib/shaders/inserts/finalize_color.glsl b/manimlib/shaders/inserts/finalize_color.glsl index 72247062de..02b79cc3ab 100644 --- a/manimlib/shaders/inserts/finalize_color.glsl +++ b/manimlib/shaders/inserts/finalize_color.glsl @@ -1,4 +1,4 @@ -uniform vec3 light_source_position; +uniform vec3 light_position; uniform vec3 camera_position; uniform vec3 shading; From 8ac0aa484bdb7341a235ef6b2ad70c04b0efeb42 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 19:34:37 -0800 Subject: [PATCH 07/38] Slight tweak to get_unit_normal --- manimlib/shaders/inserts/get_unit_normal.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/shaders/inserts/get_unit_normal.glsl b/manimlib/shaders/inserts/get_unit_normal.glsl index 879871d883..f2327375e4 100644 --- a/manimlib/shaders/inserts/get_unit_normal.glsl +++ b/manimlib/shaders/inserts/get_unit_normal.glsl @@ -1,7 +1,7 @@ vec3 get_unit_normal(vec3 p0, vec3 p1, vec3 p2){ float tol = 1e-6; vec3 v1 = normalize(p1 - p0); - vec3 v2 = normalize(p2 - p1); + vec3 v2 = normalize(p2 - p0); vec3 cp = cross(v1, v2); float cp_norm = length(cp); From 8d277af47c8d3c514f34360e463e71975e5af4b0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 21:46:43 -0800 Subject: [PATCH 08/38] Go back the convention of positioning stroke vertices in space before geom shader --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 2 +- manimlib/shaders/quadratic_bezier_stroke/vert.glsl | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index f290710be2..e55bc16205 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -179,7 +179,7 @@ void main() { corners[i], unit_normal ); - gl_Position = get_gl_Position(position_point_into_frame(corners[i])); + gl_Position = get_gl_Position(corners[i]); EmitVertex(); } EndPrimitive(); diff --git a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl index 613edb15d4..d44a4b47f7 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl @@ -1,10 +1,9 @@ #version 330 -uniform vec2 frame_shape; - in vec3 point; in vec4 stroke_rgba; in float stroke_width; +in vec3 joint_normal; in float joint_angle; // Bezier control point @@ -17,8 +16,10 @@ out float v_vert_index; const float STROKE_WIDTH_CONVERSION = 0.01; +#INSERT get_gl_Position.glsl + void main(){ - verts = point; + verts = position_point_into_frame(point); v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0; v_joint_angle = joint_angle; v_color = stroke_rgba; From ed2dbfd9b92ebb10a984c233a3676e10f3269908 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 16 Jan 2023 21:48:11 -0800 Subject: [PATCH 09/38] Remove unused imports --- manimlib/mobject/types/vectorized_mobject.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 584dd6a8d9..a2cefba76f 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -6,11 +6,11 @@ import numpy as np from manimlib.constants import GREY_A, GREY_C, GREY_E -from manimlib.constants import BLACK, WHITE +from manimlib.constants import BLACK 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, UP +from manimlib.constants import ORIGIN, OUT from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Point from manimlib.utils.bezier import bezier @@ -18,7 +18,6 @@ from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points from manimlib.utils.bezier import integer_interpolate -from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import inverse_interpolate from manimlib.utils.bezier import find_intersection from manimlib.utils.bezier import partial_quadratic_bezier_points @@ -40,13 +39,12 @@ from manimlib.utils.space_ops import midpoint from manimlib.utils.space_ops import normalize_along_axis from manimlib.utils.space_ops import z_to_vector -from manimlib.utils.simple_functions import arr_clip from manimlib.shader_wrapper import ShaderWrapper from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Callable, Iterable, Sequence, Tuple + from typing import Callable, Iterable, Tuple from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array, Vect4Array DEFAULT_STROKE_COLOR = GREY_A From 4de0d098ead1c06e404bf316ae6ee09d5afcf359 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 11:35:50 -0800 Subject: [PATCH 10/38] Allow cross to take an 'out' array --- manimlib/utils/space_ops.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/manimlib/utils/space_ops.py b/manimlib/utils/space_ops.py index 0a8ad79873..fdc2f52bc3 100644 --- a/manimlib/utils/space_ops.py +++ b/manimlib/utils/space_ops.py @@ -22,7 +22,11 @@ from manimlib.typing import Vect2, Vect3, Vect4, VectN, Matrix3x3, Vect3Array, Vect2Array -def cross(v1: Vect3 | List[float], v2: Vect3 | List[float]) -> Vect3 | Vect3Array: +def cross( + v1: Vect3 | List[float], + v2: Vect3 | List[float], + out: np.ndarray | None = None +) -> Vect3 | Vect3Array: is2d = isinstance(v1, np.ndarray) and len(v1.shape) == 2 if is2d: x1, y1, z1 = v1[:, 0], v1[:, 1], v1[:, 2] @@ -30,12 +34,14 @@ def cross(v1: Vect3 | List[float], v2: Vect3 | List[float]) -> Vect3 | Vect3Arra else: x1, y1, z1 = v1 x2, y2, z2 = v2 - result = np.array([ + if out is None: + out = np.empty(np.shape(v1)) + out.T[:] = [ y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2, - ]) - return result.T if is2d else result + ] + return out def get_norm(vect: VectN | List[float]) -> float: From abbe131e8dede299c66f1489a30294271a9911e4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 11:36:47 -0800 Subject: [PATCH 11/38] Track full cross product and dot product of tangent vectors at joints And alter the convention of what flat_stroke means to be more sensible in 3d --- manimlib/mobject/types/vectorized_mobject.py | 68 ++++++------ .../shaders/quadratic_bezier_stroke/geom.glsl | 104 ++++++++++-------- .../shaders/quadratic_bezier_stroke/vert.glsl | 12 +- 3 files changed, 101 insertions(+), 83 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index a2cefba76f..bca78fa156 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -31,6 +31,7 @@ from manimlib.utils.iterables import resize_preserving_order from manimlib.utils.iterables import arrays_match from manimlib.utils.space_ops import angle_between_vectors +from manimlib.utils.space_ops import cross from manimlib.utils.space_ops import cross2d from manimlib.utils.space_ops import earclip_triangulation from manimlib.utils.space_ops import get_norm @@ -57,13 +58,13 @@ class VMobject(Mobject): ('point', np.float32, (3,)), ('stroke_rgba', np.float32, (4,)), ('stroke_width', np.float32, (1,)), - ('joint_angle', np.float32, (1,)), + ('joint_product', np.float32, (4,)), ('fill_rgba', np.float32, (4,)), ('orientation', np.float32, (1,)), ('vert_index', np.float32, (1,)), ]) fill_data_names = ['point', 'fill_rgba', 'orientation', 'vert_index'] - stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_angle'] + stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_product'] fill_render_primitive: int = moderngl.TRIANGLES stroke_render_primitive: int = moderngl.TRIANGLE_STRIP @@ -86,7 +87,7 @@ def __init__( long_lines: bool = False, # Could also be "no_joint", "bevel", "miter" joint_type: str = "auto", - flat_stroke: bool = False, + flat_stroke: bool = True, use_simple_quadratic_approx: bool = False, # Measured in pixel widths anti_alias_width: float = 1.0, @@ -107,7 +108,7 @@ def __init__( self.needs_new_triangulation = True self.triangulation = np.zeros(0, dtype='i4') - self.needs_new_joint_angles = True + self.needs_new_joint_products = True self.outer_vert_indices = np.zeros(0, dtype='i4') super().__init__(**kwargs) @@ -1042,25 +1043,30 @@ def get_triangulation(self): self.needs_new_triangulation = False return tri_indices - def refresh_joint_angles(self): + def refresh_joint_products(self): for mob in self.get_family(): - mob.needs_new_joint_angles = True + mob.needs_new_joint_products = True return self - def get_joint_angles(self, refresh: bool = False): - if not self.needs_new_joint_angles and not refresh: - return self.data["joint_angle"] - if "joint_angle" in self.locked_data_keys: - return self.data["joint_angle"] + def recompute_joint_products(self, refresh: bool = False): + """ + The 'joint product' is a 4-vector holding the cross and dot + product between tangent vectors at a joint + """ + if not self.needs_new_joint_products and not refresh: + return self.data["joint_product"] - self.needs_new_joint_angles = False + if "joint_product" in self.locked_data_keys: + return self.data["joint_product"] + + self.needs_new_joint_products = False points = self.get_points() if(len(points) < 3): - return self.data["joint_angle"] + return self.data["joint_product"] - # Unit tangent vectors + # Find all the unit tangent vectors at each joint a0, h, a1 = points[0:-1:2], points[1::2], points[2::2] a0_to_h = normalize_along_axis(h - a0, 1) h_to_a1 = normalize_along_axis(a1 - h, 1) @@ -1073,24 +1079,24 @@ def get_joint_angles(self, refresh: bool = False): vect_from_vert[0:-1:2] = a0_to_h vect_from_vert[1::2] = h_to_a1 + # Joint up closed loops, or mark unclosed paths as such ends = self.get_subpath_end_indices() starts = [0, *(e + 2 for e in ends[:-1])] for start, end in zip(starts, ends): - if start > len(a0_to_h): - continue if self.consider_points_equal(points[start], points[end]): - vect_to_vert[start] = h_to_a1[end // 2 - 1] - vect_from_vert[end] = a0_to_h[start // 2] - - # Compute angles, and read them into - # the joint_angles array - result = self.data["joint_angle"][:, 0] - result[:] = 0 - dots = (vect_to_vert * vect_from_vert).sum(1) - np.arccos(dots, out=result, where=((dots <= 1) & (dots >= -1))) - # Assumes unit normal in the positive z direction - result *= np.sign(cross2d(vect_to_vert, vect_from_vert)) - return result + vect_to_vert[start] = vect_from_vert[end - 1] + vect_from_vert[end] = vect_to_vert[start + 1] + else: + vect_to_vert[start] = vect_from_vert[start] + vect_from_vert[end] = vect_to_vert[end] + + # Compute dot and cross products + cross( + vect_to_vert, vect_from_vert, + out=self.data["joint_product"][:, :3] + ) + self.data["joint_product"][:, 3] = (vect_to_vert * vect_from_vert).sum(1) + return self.data["joint_product"] def triggers_refreshed_triangulation(func: Callable): @wraps(func) @@ -1098,7 +1104,7 @@ def wrapper(self, *args, refresh=True, **kwargs): func(self, *args, **kwargs) if refresh: self.refresh_triangulation() - self.refresh_joint_angles() + self.refresh_joint_products() return wrapper @triggers_refreshed_triangulation @@ -1152,7 +1158,7 @@ def apply_function( def apply_points_function(self, *args, **kwargs): super().apply_points_function(*args, **kwargs) - self.refresh_joint_angles() + self.refresh_joint_products() # For shaders def init_shader_data(self): @@ -1203,7 +1209,7 @@ def get_shader_wrapper_list(self) -> list[ShaderWrapper]: fill_datas.append(submob.data[fill_names]) fill_indices.append(submob.get_triangulation()) if submob.has_stroke(): - submob.get_joint_angles() + submob.recompute_joint_products() if submob.stroke_behind: lst = back_stroke_data else: diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index e55bc16205..73726347e2 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -10,7 +10,7 @@ uniform float joint_type; in vec3 verts[3]; -in float v_joint_angle[3]; +in vec4 v_joint_product[3]; in float v_stroke_width[3]; in vec4 v_color[3]; in float v_vert_index[3]; @@ -38,8 +38,24 @@ const float ANGLE_THRESHOLD = 1e-3; #INSERT finalize_color.glsl +vec3 get_joint_normal(vec4 joint_product){ + vec3 result = joint_product.xyz; + float norm = length(result); + if(norm < 1e-8){ + // If it's too short, use the middle joint angle + result = v_joint_product[1].xyz; + norm = length(result); + } + if(norm < 1e-8){ + // If that's also to short, just return unit z vector + return vec3(0.0, 0.0, 1.0); + } + return result / norm; +} + + void create_joint( - float angle, + float cos_angle, vec3 unit_tan, float buff, vec3 static_c0, @@ -47,16 +63,20 @@ void create_joint( vec3 static_c1, out vec3 changing_c1 ){ - float shift; - // if(abs(angle) < ANGLE_THRESHOLD || abs(angle) > 0.99 * PI || int(joint_type) == NO_JOINT){ - if(abs(angle) < ANGLE_THRESHOLD || int(joint_type) == NO_JOINT){ + if(cos_angle > (1.0 - ANGLE_THRESHOLD) || int(joint_type) == NO_JOINT){ // No joint - shift = 0; - }else if(int(joint_type) == MITER_JOINT){ - shift = buff * (-1.0 - cos(angle)) / sin(angle); + changing_c0 = static_c0; + changing_c1 = static_c1; + return; + } + + float shift; + float sin_angle = sqrt(1.0 - cos_angle * cos_angle); + if(int(joint_type) == MITER_JOINT){ + shift = buff * (-1.0 - cos_angle) / sin_angle; }else{ // For a Bevel joint - shift = buff * (1.0 - cos(angle)) / sin(angle); + shift = buff * (1.0 - cos_angle) / sin_angle; } changing_c0 = static_c0 - shift * unit_tan; changing_c1 = static_c1 + shift * unit_tan; @@ -75,28 +95,35 @@ void get_corners( // Unit tangent vectors at p0 and p2 vec3 v01, vec3 v12, - float stroke_width0, - float stroke_width2, - // Unit normal to the whole curve - vec3 normal, // Anti-alias width float aaw, - float angle_from_prev, - float angle_to_next, out vec3 corners[6] ){ - float buff0 = 0.5 * stroke_width0 + aaw; - float buff2 = 0.5 * stroke_width2 + aaw; + float buff0 = 0.5 * v_stroke_width[0] + aaw; + float buff2 = 0.5 * v_stroke_width[2] + aaw; - // Add correction for sharp angles to prevent weird bevel effects (Needed?) - float thresh = 5 * PI / 6; - if(angle_from_prev > thresh) buff0 *= 2 * sin(angle_from_prev); - if(angle_to_next > thresh) buff2 *= 2 * sin(angle_to_next); + // Add correction for sharp angles to prevent weird bevel effects + if(v_joint_product[0].w < -0.5) buff0 *= -2 * v_joint_product[0].w; + if(v_joint_product[2].w < -0.5) buff2 *= -2 * v_joint_product[0].w; + + // Unit normal and joint angles + vec3 normal0 = get_joint_normal(v_joint_product[0]); + vec3 normal2 = get_joint_normal(v_joint_product[2]); + // Chose the normal in the positive z direction + normal0 *= sign(normal0.z); + normal2 *= sign(normal2.z); // Perpendicular vectors to the left of the curve - vec3 p0_perp = buff0 * normalize(cross(normal, v01)); - vec3 p2_perp = buff2 * normalize(cross(normal, v12)); + vec3 p0_perp; + vec3 p2_perp; + if(bool(flat_stroke)){ + p0_perp = buff0 * normalize(cross(normal0, v01)); + p2_perp = buff2 * normalize(cross(normal2, v12)); + }else{ + p0_perp = buff0 * normal0; + p2_perp = buff2 * normal2; + } vec3 p1_perp = 0.5 * (p0_perp + p2_perp); // The order of corners should be for a triangle_strip. @@ -106,20 +133,19 @@ void get_corners( vec3 c3 = p1 - p1_perp; vec3 c4 = p2 + p2_perp; vec3 c5 = p2 - p2_perp; - float orientation = dot(normal, cross(v01, v12)); // Move the inner middle control point to make // room for the curve - if(orientation > 0.0) c2 = 0.5 * (c0 + c4); + float orientation = dot(normal0, cross(v01, v12)); + if(orientation >= 0.0) c2 = 0.5 * (c0 + c4); else if(orientation < 0.0) c3 = 0.5 * (c1 + c5); // Account for previous and next control points - create_joint(angle_from_prev, v01, buff0, c1, c1, c0, c0); - create_joint(angle_to_next, -v12, buff2, c5, c5, c4, c4); + create_joint(v_joint_product[0].w, v01, buff0, c1, c1, c0, c0); + create_joint(v_joint_product[2].w, -v12, buff2, c5, c5, c4, c4); corners = vec3[6](c0, c1, c2, c3, c4, c5); } - void main() { // We use the triangle strip primative, but // actually only need every other strip element @@ -129,12 +155,6 @@ void main() { // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; - // TODO, track true unit normal globally (probably as a uniform) - vec3 unit_normal = vec3(0.0, 0.0, 1.0); - if(bool(flat_stroke)){ - unit_normal = camera_rotation * vec3(0.0, 0.0, 1.0); - } - vec3 p0 = verts[0]; vec3 p1 = verts[1]; vec3 p2 = verts[2]; @@ -144,6 +164,7 @@ void main() { float angle = acos(clamp(dot(v01, v12), -1, 1)); is_linear = float(abs(angle) < ANGLE_THRESHOLD); + // If the curve is flat, put the middle control in the midpoint if (bool(is_linear)) p1 = 0.5 * (p0 + p2); @@ -158,16 +179,7 @@ void main() { uv_anti_alias_width = uv_scale_factor * scaled_aaw; vec3 corners[6]; - get_corners( - p0, p1, p2, v01, v12, - v_stroke_width[0], - v_stroke_width[2], - unit_normal, - scaled_aaw, - v_joint_angle[0], - v_joint_angle[2], - corners - ); + get_corners(p0, p1, p2, v01, v12, scaled_aaw, corners); // Emit each corner for(int i = 0; i < 6; i++){ @@ -177,9 +189,9 @@ void main() { color = finalize_color( v_color[vert_index], corners[i], - unit_normal + vec3(0.0, 0.0, 1.0) // TODO ); - gl_Position = get_gl_Position(corners[i]); + gl_Position = get_gl_Position(position_point_into_frame(corners[i])); EmitVertex(); } EndPrimitive(); diff --git a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl index d44a4b47f7..e673f8dcac 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl @@ -1,27 +1,27 @@ #version 330 +uniform vec2 frame_shape; + in vec3 point; in vec4 stroke_rgba; in float stroke_width; in vec3 joint_normal; -in float joint_angle; +in vec4 joint_product; // Bezier control point out vec3 verts; -out float v_joint_angle; +out vec4 v_joint_product; out float v_stroke_width; out vec4 v_color; out float v_vert_index; const float STROKE_WIDTH_CONVERSION = 0.01; -#INSERT get_gl_Position.glsl - void main(){ - verts = position_point_into_frame(point); + verts = point; v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0; - v_joint_angle = joint_angle; + v_joint_product = joint_product; v_color = stroke_rgba; v_vert_index = gl_VertexID; } \ No newline at end of file From b16f0981f6e922fdbcecd5f938ef3a42cd067c4f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 11:37:06 -0800 Subject: [PATCH 12/38] No need to set flat stroke defaults in Polygon/Polyline --- manimlib/mobject/geometry.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 407841a0d1..a2adf25cda 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -918,10 +918,9 @@ class Polygon(VMobject): def __init__( self, *vertices: Vect3, - flat_stroke: bool = True, **kwargs ): - super().__init__(flat_stroke=flat_stroke, **kwargs) + super().__init__(**kwargs) self.set_points_as_corners([*vertices, vertices[0]]) def get_vertices(self) -> Vect3Array: @@ -967,10 +966,9 @@ class Polyline(VMobject): def __init__( self, *vertices: Vect3, - flat_stroke: bool = True, **kwargs ): - super().__init__(flat_stroke=flat_stroke, **kwargs) + super().__init__(**kwargs) self.set_points_as_corners(vertices) From 1b3bc7a27c4f56e460826fe6aa5a350c334fda10 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 13:16:58 -0800 Subject: [PATCH 13/38] For linearity, check cosine of angle instead of angle --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 73726347e2..5963cf270c 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -29,8 +29,10 @@ const int AUTO_JOINT = 1; const int BEVEL_JOINT = 2; const int MITER_JOINT = 3; -const float PI = 3.141592653; -const float ANGLE_THRESHOLD = 1e-3; +// When the cosine of the angle between +// two vectors is larger than this, we +// consider them aligned +const float COS_THRESHOLD = 0.999; #INSERT get_gl_Position.glsl @@ -63,7 +65,7 @@ void create_joint( vec3 static_c1, out vec3 changing_c1 ){ - if(cos_angle > (1.0 - ANGLE_THRESHOLD) || int(joint_type) == NO_JOINT){ + if(cos_angle > COS_THRESHOLD || int(joint_type) == NO_JOINT){ // No joint changing_c0 = static_c0; changing_c1 = static_c1; @@ -161,9 +163,8 @@ void main() { vec3 v01 = normalize(p1 - p0); vec3 v12 = normalize(p2 - p1); - float angle = acos(clamp(dot(v01, v12), -1, 1)); - is_linear = float(abs(angle) < ANGLE_THRESHOLD); - + float cos_angle = v_joint_product[1].w; + is_linear = float(cos_angle > COS_THRESHOLD); // If the curve is flat, put the middle control in the midpoint if (bool(is_linear)) p1 = 0.5 * (p0 + p2); From 7fe84d926309176c3df1c43ffb3c34257cbdabc9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 13:18:32 -0800 Subject: [PATCH 14/38] Don't recompute cross(v01, v12) --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 5963cf270c..9f0231b39e 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -137,13 +137,15 @@ void get_corners( vec3 c5 = p2 - p2_perp; // Move the inner middle control point to make // room for the curve - float orientation = dot(normal0, cross(v01, v12)); - if(orientation >= 0.0) c2 = 0.5 * (c0 + c4); + float orientation = dot(normal0, v_joint_product[1].xyz); + if(orientation >= 0.0) c2 = 0.5 * (c0 + c4); else if(orientation < 0.0) c3 = 0.5 * (c1 + c5); // Account for previous and next control points - create_joint(v_joint_product[0].w, v01, buff0, c1, c1, c0, c0); - create_joint(v_joint_product[2].w, -v12, buff2, c5, c5, c4, c4); + if(bool(flat_stroke)){ + create_joint(v_joint_product[0].w, v01, buff0, c1, c1, c0, c0); + create_joint(v_joint_product[2].w, -v12, buff2, c5, c5, c4, c4); + } corners = vec3[6](c0, c1, c2, c3, c4, c5); } From 870e88f8c9ef95af11c70011006aae39d89f2b89 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 15:46:09 -0800 Subject: [PATCH 15/38] First attempt at finding uv coords from 3d space instead of 2d --- manimlib/shaders/inserts/get_xy_to_uv.glsl | 139 ++++++++++++++++++ .../shaders/quadratic_bezier_stroke/geom.glsl | 17 ++- 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xy_to_uv.glsl index 5f3e2deb55..f7df5107a2 100644 --- a/manimlib/shaders/inserts/get_xy_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xy_to_uv.glsl @@ -27,6 +27,27 @@ vec2 xs_on_clean_parabola(vec2 b0, vec2 b1, vec2 b2){ } +vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ + /* + Given three control points for a quadratic bezier, + this returns the two values (x0, x2) such that the + section of the parabola y = x^2 between those values + is isometric to the given quadratic bezier. + + Adapated from https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html + */ + vec3 dd = 2 * b1 - b0 - b2; + + float u0 = dot(b1 - b0, dd); + float u2 = dot(b2 - b1, dd); + vec3 cp = cross(b2 - b0, dd); + float sgn = sign(cp.z); + float denom = sgn * length(cp); + + return vec2(u0 / denom, u2 / denom); +} + + mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){ /* Returns an orthogonal matrix which will map @@ -55,6 +76,124 @@ mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){ } +mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 dst2){ + /* + Return an affine transform which maps the triangle (src0, src1, src2) + onto the triangle (dst0, dst1, dst2) + */ + mat4 src_mat = mat4( + src0.x, src0.y, src0.z, 1.0, + src1.x, src1.y, src1.z, 1.0, + src2.x, src2.y, src2.z, 1.0, + 1.0, 1.0, 1.0, 1.0 + ); + mat4 dst_mat = mat4( + dst0.x, dst0.y, dst0.z, 1.0, + dst1.x, dst1.y, dst1.z, 1.0, + dst2.x, dst2.y, dst2.z, 1.0, + 1.0, 1.0, 1.0, 1.0 + ); + return dst_mat * inverse(src_mat); +} + + +mat4 map_point_pairs(vec3 src0, vec3 src1, vec3 dest0, vec3 dest1){ + /* + Returns an orthogonal matrix which will map + src0 onto dest0 and src1 onto dest1. + */ + mat4 shift1 = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + -src0.x, -src0.y, -src0.z, 1.0 + ); + mat4 shift2 = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + dest0.x, dest0.y, dest0.z, 1.0 + ); + + // Find rotation matrix between unit vectors in each direction + vec3 src_v = src1 - src0; + vec3 dst_v = dest1 - dest0; + float src_len = length(src_v); + float dst_len = length(dst_v); + float scale = dst_len / src_len; + src_v /= src_len; + dst_v /= dst_len; + + vec3 cp = cross(src_v, dst_v); + float dp = dot(src_v, dst_v); + + float s = length(cp); // Sine of the angle between them + float c = dp; // Cosine of the angle between them + + if(s < 1e-8){ + // No rotation needed + return shift2 * shift1; + } + + vec3 axis = cp / s; // Axis of rotation + float oc = 1.0 - c; + float ax = axis.x; + float ay = axis.y; + float az = axis.z; + + // Rotation matrix about axis, with a given angle corresponding to s and c. + mat4 rotate = scale * mat4( + oc * ax * ax + c, oc * ax * ay + az * s, oc * az * ax - ay * s, 0.0, + oc * ax * ay - az * s, oc * ay * ay + c, oc * ay * az + ax * s, 0.0, + oc * az * ax + ay * s, oc * ay * az - ax * s, oc * az * az + c, 0.0, + 0.0, 0.0, 0.0, 1.0 / scale + ); + + return shift2 * rotate * shift1; +} + + +mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is_linear){ + /* + Returns a matrix for an affine transformation which maps a set of quadratic + bezier controls points into a new coordinate system such that the bezier curve + coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis. + */ + vec3 dest0; + vec3 dest1; + vec3 dest2; + vec3 src1; + is_linear = temp_is_linear; + // Portions of the parabola y = x^2 where abs(x) exceeds + // this value are treated as straight lines. + float thresh = 2.0; + if (!bool(is_linear)){ + vec2 xs = xs_on_clean_parabola(b0, b1, b2); + float x0 = xs.x; + float x2 = xs.y; + if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){ + is_linear = 1.0; + }else{ + dest0 = vec3(x0, x0 * x0, 0.0); + dest1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); + dest2 = vec3(x2, x2 * x2, 0.0); + src1 = b1; + } + } + // Check if is_linear status changed above + if (bool(is_linear)){ + dest0 = vec3(0.0, 0.0, 0.0); + dest1 = vec3(0.0, 1.0, 0.0); + dest2 = vec3(1.0, 0.0, 0.0); + vec3 v = b2 - b0; + src1 = b0 + length(v) * normalize(cross(v, vec3(0, 0, 1))); + } + + // return map_point_pairs(b0, b2, dest0, dest1); + return map_triangles(b0, src1, b2, dest0, dest1, dest2); +} + + mat3 get_xy_to_uv(vec2 b0, vec2 b1, vec2 b2, float temp_is_linear, out float is_linear){ /* Returns a matrix for an affine transformation which maps a set of quadratic diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 9f0231b39e..558543e18f 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -112,9 +112,9 @@ void get_corners( // Unit normal and joint angles vec3 normal0 = get_joint_normal(v_joint_product[0]); vec3 normal2 = get_joint_normal(v_joint_product[2]); - // Chose the normal in the positive z direction - normal0 *= sign(normal0.z); - normal2 *= sign(normal2.z); + + // Make sure normals point in the same direction + if(dot(normal0, normal2) < 0) normal2 *= -1; // Perpendicular vectors to the left of the curve vec3 p0_perp; @@ -125,6 +125,9 @@ void get_corners( }else{ p0_perp = buff0 * normal0; p2_perp = buff2 * normal2; + // vec3 to_cam = transpose(camera_rotation)[2]; + // p0_perp = buff0 * to_cam; + // p2_perp = buff2 * to_cam; } vec3 p1_perp = 0.5 * (p0_perp + p2_perp); @@ -175,9 +178,10 @@ void main() { // coincides with y = x^2, between some values x0 and x2. Or, in // the case of a linear curve (bezier degree 1), just put it on // the segment from (0, 0) to (1, 0) - mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); + // mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); + mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); - float uv_scale_factor = length(xy_to_uv[0].xy); + float uv_scale_factor = length(xyz_to_uv[0].xyz); float scaled_aaw = anti_alias_width * (frame_shape.y / pixel_shape.y); uv_anti_alias_width = uv_scale_factor * scaled_aaw; @@ -187,7 +191,8 @@ void main() { // Emit each corner for(int i = 0; i < 6; i++){ int vert_index = i / 2; - uv_coords = (xy_to_uv * vec3(corners[i].xy, 1)).xy; + // uv_coords = (xy_to_uv * vec3(corners[i].xy, 1)).xy; + uv_coords = (xyz_to_uv * vec4(corners[i], 1)).xy; uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index]; color = finalize_color( v_color[vert_index], From c563ec20366ce8fea1e9df195d593ba2c3eb9443 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 17:20:30 -0800 Subject: [PATCH 16/38] Simplifications --- manimlib/shaders/inserts/get_xy_to_uv.glsl | 27 +++++++++------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xy_to_uv.glsl index f7df5107a2..d2d1708e31 100644 --- a/manimlib/shaders/inserts/get_xy_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xy_to_uv.glsl @@ -41,8 +41,7 @@ vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ float u0 = dot(b1 - b0, dd); float u2 = dot(b2 - b1, dd); vec3 cp = cross(b2 - b0, dd); - float sgn = sign(cp.z); - float denom = sgn * length(cp); + float denom = length(cp); return vec2(u0 / denom, u2 / denom); } @@ -82,16 +81,16 @@ mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 d onto the triangle (dst0, dst1, dst2) */ mat4 src_mat = mat4( - src0.x, src0.y, src0.z, 1.0, - src1.x, src1.y, src1.z, 1.0, - src2.x, src2.y, src2.z, 1.0, - 1.0, 1.0, 1.0, 1.0 + src0, 1.0, + src1, 1.0, + src2, 1.0, + vec4(1.0) ); mat4 dst_mat = mat4( - dst0.x, dst0.y, dst0.z, 1.0, - dst1.x, dst1.y, dst1.z, 1.0, - dst2.x, dst2.y, dst2.z, 1.0, - 1.0, 1.0, 1.0, 1.0 + dst0, 1.0, + dst1, 1.0, + dst2, 1.0, + vec4(1.0) ); return dst_mat * inverse(src_mat); } @@ -162,7 +161,6 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is vec3 dest0; vec3 dest1; vec3 dest2; - vec3 src1; is_linear = temp_is_linear; // Portions of the parabola y = x^2 where abs(x) exceeds // this value are treated as straight lines. @@ -177,20 +175,17 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is dest0 = vec3(x0, x0 * x0, 0.0); dest1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); dest2 = vec3(x2, x2 * x2, 0.0); - src1 = b1; } } // Check if is_linear status changed above if (bool(is_linear)){ dest0 = vec3(0.0, 0.0, 0.0); - dest1 = vec3(0.0, 1.0, 0.0); dest2 = vec3(1.0, 0.0, 0.0); - vec3 v = b2 - b0; - src1 = b0 + length(v) * normalize(cross(v, vec3(0, 0, 1))); + return map_point_pairs(b0, b2, dest0, dest2); } // return map_point_pairs(b0, b2, dest0, dest1); - return map_triangles(b0, src1, b2, dest0, dest1, dest2); + return map_triangles(b0, b1, b2, dest0, dest1, dest2); } From c2587de691db07c6733d83b738b4a0651c7c22d4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 17:22:00 -0800 Subject: [PATCH 17/38] Apply xyz_to_uv to pre-positioned points --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 558543e18f..b38787df6e 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -179,7 +179,12 @@ void main() { // the case of a linear curve (bezier degree 1), just put it on // the segment from (0, 0) to (1, 0) // mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); - mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); + mat4 xyz_to_uv = get_xyz_to_uv( + position_point_into_frame(p0), + position_point_into_frame(p1), + position_point_into_frame(p2), + is_linear, is_linear + ); float uv_scale_factor = length(xyz_to_uv[0].xyz); float scaled_aaw = anti_alias_width * (frame_shape.y / pixel_shape.y); @@ -191,15 +196,15 @@ void main() { // Emit each corner for(int i = 0; i < 6; i++){ int vert_index = i / 2; - // uv_coords = (xy_to_uv * vec3(corners[i].xy, 1)).xy; - uv_coords = (xyz_to_uv * vec4(corners[i], 1)).xy; + vec3 pos = corners[i]; + uv_coords = (xyz_to_uv * vec4(pos, 1)).xy; uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index]; color = finalize_color( v_color[vert_index], corners[i], vec3(0.0, 0.0, 1.0) // TODO ); - gl_Position = get_gl_Position(position_point_into_frame(corners[i])); + gl_Position = get_gl_Position(pos); EmitVertex(); } EndPrimitive(); From b7831ef3f1479c16e75a71f7f5ef0dc61f7175d4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 17:27:20 -0800 Subject: [PATCH 18/38] Go back to computing xyz-to-uv before repositioning --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index b38787df6e..7673de9113 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -179,12 +179,7 @@ void main() { // the case of a linear curve (bezier degree 1), just put it on // the segment from (0, 0) to (1, 0) // mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); - mat4 xyz_to_uv = get_xyz_to_uv( - position_point_into_frame(p0), - position_point_into_frame(p1), - position_point_into_frame(p2), - is_linear, is_linear - ); + mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); float uv_scale_factor = length(xyz_to_uv[0].xyz); float scaled_aaw = anti_alias_width * (frame_shape.y / pixel_shape.y); @@ -196,15 +191,14 @@ void main() { // Emit each corner for(int i = 0; i < 6; i++){ int vert_index = i / 2; - vec3 pos = corners[i]; - uv_coords = (xyz_to_uv * vec4(pos, 1)).xy; + uv_coords = (xyz_to_uv * vec4(corners[i], 1)).xy; uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index]; color = finalize_color( v_color[vert_index], corners[i], vec3(0.0, 0.0, 1.0) // TODO ); - gl_Position = get_gl_Position(pos); + gl_Position = get_gl_Position(position_point_into_frame(corners[i])); EmitVertex(); } EndPrimitive(); From 9ed8dd5439f259113407b3c07621b9e6e87217b6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 17:39:32 -0800 Subject: [PATCH 19/38] Clean up --- manimlib/shaders/inserts/get_xy_to_uv.glsl | 66 ++++++++----------- .../shaders/quadratic_bezier_stroke/geom.glsl | 1 - 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xy_to_uv.glsl index d2d1708e31..1b09ce32f3 100644 --- a/manimlib/shaders/inserts/get_xy_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xy_to_uv.glsl @@ -47,10 +47,10 @@ vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ } -mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){ +mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dst0, vec2 dst1){ /* Returns an orthogonal matrix which will map - src0 onto dest0 and src1 onto dest1. + src0 onto dst0 and src1 onto dst1. */ mat3 shift1 = mat3( 1.0, 0.0, 0.0, @@ -60,11 +60,11 @@ mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){ mat3 shift2 = mat3( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, - dest0.x, dest0.y, 1.0 + dst0.x, dst0.y, 1.0 ); // Compute complex division dest_vect / src_vect to determine rotation - vec2 complex_rot = complex_div(dest1 - dest0, src1 - src0); + vec2 complex_rot = complex_div(dst1 - dst0, src1 - src0); mat3 rotate = mat3( complex_rot.x, complex_rot.y, 0.0, -complex_rot.y, complex_rot.x, 0.0, @@ -96,27 +96,19 @@ mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 d } -mat4 map_point_pairs(vec3 src0, vec3 src1, vec3 dest0, vec3 dest1){ +mat4 map_point_pairs(vec3 src0, vec3 src1, vec3 dst0, vec3 dst1){ /* Returns an orthogonal matrix which will map - src0 onto dest0 and src1 onto dest1. + src0 onto dst0 and src1 onto dst1. */ - mat4 shift1 = mat4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - -src0.x, -src0.y, -src0.z, 1.0 - ); - mat4 shift2 = mat4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - dest0.x, dest0.y, dest0.z, 1.0 - ); + mat4 shift1 = mat4(1.0); + shift1[3].xyz = -src0; + mat4 shift2 = mat4(1.0); + shift2[3].xzy = dst0; // Find rotation matrix between unit vectors in each direction vec3 src_v = src1 - src0; - vec3 dst_v = dest1 - dest0; + vec3 dst_v = dst1 - dst0; float src_len = length(src_v); float dst_len = length(dst_v); float scale = dst_len / src_len; @@ -158,9 +150,9 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is bezier controls points into a new coordinate system such that the bezier curve coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis. */ - vec3 dest0; - vec3 dest1; - vec3 dest2; + vec3 dst0; + vec3 dst1; + vec3 dst2; is_linear = temp_is_linear; // Portions of the parabola y = x^2 where abs(x) exceeds // this value are treated as straight lines. @@ -172,20 +164,20 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){ is_linear = 1.0; }else{ - dest0 = vec3(x0, x0 * x0, 0.0); - dest1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); - dest2 = vec3(x2, x2 * x2, 0.0); + dst0 = vec3(x0, x0 * x0, 0.0); + dst1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); + dst2 = vec3(x2, x2 * x2, 0.0); } } // Check if is_linear status changed above if (bool(is_linear)){ - dest0 = vec3(0.0, 0.0, 0.0); - dest2 = vec3(1.0, 0.0, 0.0); - return map_point_pairs(b0, b2, dest0, dest2); + dst0 = vec3(0.0, 0.0, 0.0); + dst2 = vec3(1.0, 0.0, 0.0); + return map_point_pairs(b0, b2, dst0, dst2); } - // return map_point_pairs(b0, b2, dest0, dest1); - return map_triangles(b0, b1, b2, dest0, dest1, dest2); + // return map_point_pairs(b0, b2, dst0, dst1); + return map_triangles(b0, b1, b2, dst0, dst1, dst2); } @@ -195,8 +187,8 @@ mat3 get_xy_to_uv(vec2 b0, vec2 b1, vec2 b2, float temp_is_linear, out float is_ bezier controls points into a new coordinate system such that the bezier curve coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis. */ - vec2 dest0; - vec2 dest1; + vec2 dst0; + vec2 dst1; is_linear = temp_is_linear; // Portions of the parabola y = x^2 where abs(x) exceeds // this value are treated as straight lines. @@ -208,15 +200,15 @@ mat3 get_xy_to_uv(vec2 b0, vec2 b1, vec2 b2, float temp_is_linear, out float is_ if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){ is_linear = 1.0; }else{ - dest0 = vec2(x0, x0 * x0); - dest1 = vec2(x2, x2 * x2); + dst0 = vec2(x0, x0 * x0); + dst1 = vec2(x2, x2 * x2); } } // Check if is_linear status changed above if (bool(is_linear)){ - dest0 = vec2(0, 0); - dest1 = vec2(1, 0); + dst0 = vec2(0, 0); + dst1 = vec2(1, 0); } - return map_point_pairs(b0, b2, dest0, dest1); + return map_point_pairs(b0, b2, dst0, dst1); } diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 7673de9113..c0c5e142e8 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -178,7 +178,6 @@ void main() { // coincides with y = x^2, between some values x0 and x2. Or, in // the case of a linear curve (bezier degree 1), just put it on // the segment from (0, 0) to (1, 0) - // mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); float uv_scale_factor = length(xyz_to_uv[0].xyz); From 5e1a02d2ce86a0e3760e79d59ae17e3597b48c3c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 17:45:25 -0800 Subject: [PATCH 20/38] Use xyz-to-uv matrix for fill --- manimlib/shaders/quadratic_bezier_fill/geom.glsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index b27b935943..40985863d7 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -83,13 +83,13 @@ void emit_pentagon( } // Compute xy_to_uv matrix, and potentially re-evaluate bezier degree - mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); - uv_anti_alias_width = aaw * length(xy_to_uv[0].xy); + mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); + uv_anti_alias_width = aaw * length(xyz_to_uv[0].xyz); for(int i = 0; i < 5; i++){ int j = int(sign(i - 1) + 1); // Maps i = [0, 1, 2, 3, 4] onto j = [0, 0, 1, 2, 2] vec3 corner = corners[i]; - uv_coords = (xy_to_uv * vec3(corner.xy, 1.0)).xy; + uv_coords = (xyz_to_uv * vec4(corner, 1.0)).xy; emit_vertex_wrapper(corner, j, unit_normal); } EndPrimitive(); From c7e32e847d60c56f098cf64f8c679372ef18d9af Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 17:46:23 -0800 Subject: [PATCH 21/38] Delete no-longer used functions --- manimlib/shaders/inserts/get_xy_to_uv.glsl | 90 ---------------------- 1 file changed, 90 deletions(-) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xy_to_uv.glsl index 1b09ce32f3..1880919b09 100644 --- a/manimlib/shaders/inserts/get_xy_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xy_to_uv.glsl @@ -1,32 +1,3 @@ -float cross2d(vec2 v, vec2 w){ - return v.x * w.y - w.x * v.y; -} - - -vec2 complex_div(vec2 v, vec2 w){ - return vec2(dot(v, w), cross2d(w, v)) / dot(w, w); -} - - -vec2 xs_on_clean_parabola(vec2 b0, vec2 b1, vec2 b2){ - /* - Given three control points for a quadratic bezier, - this returns the two values (x0, x2) such that the - section of the parabola y = x^2 between those values - is isometric to the given quadratic bezier. - - Adapated from https://github.com/raphlinus/raphlinus.github.io/blob/master/_posts/2019-12-23-flatten-quadbez.md - */ - vec2 dd = normalize(2 * b1 - b0 - b2); - - float u0 = dot(b1 - b0, dd); - float u2 = dot(b2 - b1, dd); - float cp = cross2d(b2 - b0, dd); - - return vec2(u0 / cp, u2 / cp); -} - - vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ /* Given three control points for a quadratic bezier, @@ -47,34 +18,6 @@ vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ } -mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dst0, vec2 dst1){ - /* - Returns an orthogonal matrix which will map - src0 onto dst0 and src1 onto dst1. - */ - mat3 shift1 = mat3( - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - -src0.x, -src0.y, 1.0 - ); - mat3 shift2 = mat3( - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - dst0.x, dst0.y, 1.0 - ); - - // Compute complex division dest_vect / src_vect to determine rotation - vec2 complex_rot = complex_div(dst1 - dst0, src1 - src0); - mat3 rotate = mat3( - complex_rot.x, complex_rot.y, 0.0, - -complex_rot.y, complex_rot.x, 0.0, - 0.0, 0.0, 1.0 - ); - - return shift2 * rotate * shift1; -} - - mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 dst2){ /* Return an affine transform which maps the triangle (src0, src1, src2) @@ -179,36 +122,3 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is // return map_point_pairs(b0, b2, dst0, dst1); return map_triangles(b0, b1, b2, dst0, dst1, dst2); } - - -mat3 get_xy_to_uv(vec2 b0, vec2 b1, vec2 b2, float temp_is_linear, out float is_linear){ - /* - Returns a matrix for an affine transformation which maps a set of quadratic - bezier controls points into a new coordinate system such that the bezier curve - coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis. - */ - vec2 dst0; - vec2 dst1; - is_linear = temp_is_linear; - // Portions of the parabola y = x^2 where abs(x) exceeds - // this value are treated as straight lines. - float thresh = 2.0; - if (!bool(is_linear)){ - vec2 xs = xs_on_clean_parabola(b0, b1, b2); - float x0 = xs.x; - float x2 = xs.y; - if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){ - is_linear = 1.0; - }else{ - dst0 = vec2(x0, x0 * x0); - dst1 = vec2(x2, x2 * x2); - } - } - // Check if is_linear status changed above - if (bool(is_linear)){ - dst0 = vec2(0, 0); - dst1 = vec2(1, 0); - } - - return map_point_pairs(b0, b2, dst0, dst1); -} From 0b72bc5d08a46f64688d7e1f51acdab6841b3a44 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 10:04:05 -0800 Subject: [PATCH 22/38] Fix joint normal issue --- .../shaders/quadratic_bezier_stroke/geom.glsl | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index c0c5e142e8..0a6969316f 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -40,24 +40,20 @@ const float COS_THRESHOLD = 0.999; #INSERT finalize_color.glsl -vec3 get_joint_normal(vec4 joint_product){ - vec3 result = joint_product.xyz; - float norm = length(result); - if(norm < 1e-8){ - // If it's too short, use the middle joint angle +vec3 get_joint_unit_normal(vec4 joint_product){ + vec3 result; + if(joint_product.w < COS_THRESHOLD){ + result = joint_product.xyz; + }else{ result = v_joint_product[1].xyz; - norm = length(result); } - if(norm < 1e-8){ - // If that's also to short, just return unit z vector - return vec3(0.0, 0.0, 1.0); - } - return result / norm; + float norm = length(result); + return (norm > 1e-10) ? result / norm : vec3(0.0, 0.0, 1.0); } void create_joint( - float cos_angle, + vec4 joint_product, vec3 unit_tan, float buff, vec3 static_c0, @@ -65,6 +61,7 @@ void create_joint( vec3 static_c1, out vec3 changing_c1 ){ + float cos_angle = joint_product.w; if(cos_angle > COS_THRESHOLD || int(joint_type) == NO_JOINT){ // No joint changing_c0 = static_c0; @@ -73,7 +70,7 @@ void create_joint( } float shift; - float sin_angle = sqrt(1.0 - cos_angle * cos_angle); + float sin_angle = length(joint_product.xyz) * sign(joint_product.z); if(int(joint_type) == MITER_JOINT){ shift = buff * (-1.0 - cos_angle) / sin_angle; }else{ @@ -106,28 +103,25 @@ void get_corners( float buff2 = 0.5 * v_stroke_width[2] + aaw; // Add correction for sharp angles to prevent weird bevel effects - if(v_joint_product[0].w < -0.5) buff0 *= -2 * v_joint_product[0].w; - if(v_joint_product[2].w < -0.5) buff2 *= -2 * v_joint_product[0].w; + if(v_joint_product[0].w < -0.5) buff0 *= 2 * (v_joint_product[0].w + 1.0); + if(v_joint_product[2].w < -0.5) buff2 *= 2 * (v_joint_product[0].w + 1.0); // Unit normal and joint angles - vec3 normal0 = get_joint_normal(v_joint_product[0]); - vec3 normal2 = get_joint_normal(v_joint_product[2]); + vec3 normal0 = get_joint_unit_normal(v_joint_product[0]); + vec3 normal2 = get_joint_unit_normal(v_joint_product[2]); // Make sure normals point in the same direction - if(dot(normal0, normal2) < 0) normal2 *= -1; + if(dot(normal0, normal2) < 0) buff2 *= -1; - // Perpendicular vectors to the left of the curve vec3 p0_perp; vec3 p2_perp; if(bool(flat_stroke)){ + // Perpendicular vectors to the left of the curve p0_perp = buff0 * normalize(cross(normal0, v01)); p2_perp = buff2 * normalize(cross(normal2, v12)); }else{ p0_perp = buff0 * normal0; p2_perp = buff2 * normal2; - // vec3 to_cam = transpose(camera_rotation)[2]; - // p0_perp = buff0 * to_cam; - // p2_perp = buff2 * to_cam; } vec3 p1_perp = 0.5 * (p0_perp + p2_perp); @@ -146,8 +140,8 @@ void get_corners( // Account for previous and next control points if(bool(flat_stroke)){ - create_joint(v_joint_product[0].w, v01, buff0, c1, c1, c0, c0); - create_joint(v_joint_product[2].w, -v12, buff2, c5, c5, c4, c4); + create_joint(v_joint_product[0], v01, buff0, c1, c1, c0, c0); + create_joint(v_joint_product[2], -v12, buff2, c5, c5, c4, c4); } corners = vec3[6](c0, c1, c2, c3, c4, c5); From 874906bedfc224338a62a2538b975148aeb2fbde Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 10:04:51 -0800 Subject: [PATCH 23/38] Replace 'light_source_position' with 'light_position' --- manimlib/shaders/textured_surface/frag.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/shaders/textured_surface/frag.glsl b/manimlib/shaders/textured_surface/frag.glsl index 7d8010e681..5c4a54a674 100644 --- a/manimlib/shaders/textured_surface/frag.glsl +++ b/manimlib/shaders/textured_surface/frag.glsl @@ -20,7 +20,7 @@ void main() { if(num_textures == 2.0){ vec4 dark_color = texture(DarkTexture, v_im_coords); float dp = dot( - normalize(light_source_position - v_point), + normalize(light_position - v_point), normalize(v_normal) ); float alpha = smoothstep(-dark_shift, dark_shift, dp); From 1a663943c9fd95a5b2823d5af0383b8852c684b9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 10:57:14 -0800 Subject: [PATCH 24/38] Simpler xyz-to-uv map for linear case --- manimlib/shaders/inserts/get_xy_to_uv.glsl | 67 ++++++++-------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xy_to_uv.glsl index 1880919b09..0ba0e5f3a0 100644 --- a/manimlib/shaders/inserts/get_xy_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xy_to_uv.glsl @@ -39,51 +39,36 @@ mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 d } -mat4 map_point_pairs(vec3 src0, vec3 src1, vec3 dst0, vec3 dst1){ - /* - Returns an orthogonal matrix which will map - src0 onto dst0 and src1 onto dst1. - */ - mat4 shift1 = mat4(1.0); - shift1[3].xyz = -src0; - mat4 shift2 = mat4(1.0); - shift2[3].xzy = dst0; +mat4 map_onto_x_axis(vec3 src0, vec3 src1){ + mat4 shift = mat4(1.0); + shift[3].xyz = -src0; // Find rotation matrix between unit vectors in each direction - vec3 src_v = src1 - src0; - vec3 dst_v = dst1 - dst0; - float src_len = length(src_v); - float dst_len = length(dst_v); - float scale = dst_len / src_len; - src_v /= src_len; - dst_v /= dst_len; - - vec3 cp = cross(src_v, dst_v); - float dp = dot(src_v, dst_v); + vec3 vect = normalize(src1 - src0); + // This is the same as cross(vect, vec3(1, 0, 0)) + vec3 axis = vec3(0.0, vect.z, -vect.y); - float s = length(cp); // Sine of the angle between them - float c = dp; // Cosine of the angle between them + float s = length(axis); // Sine of the angle between them + float c = vect.x; // Cosine of the angle between them - if(s < 1e-8){ - // No rotation needed - return shift2 * shift1; - } + // No rotation needed + if(s < 1e-8) return shift; - vec3 axis = cp / s; // Axis of rotation + axis = axis / s; // Axis of rotation float oc = 1.0 - c; float ax = axis.x; float ay = axis.y; float az = axis.z; // Rotation matrix about axis, with a given angle corresponding to s and c. - mat4 rotate = scale * mat4( + mat4 rotate = mat4( oc * ax * ax + c, oc * ax * ay + az * s, oc * az * ax - ay * s, 0.0, oc * ax * ay - az * s, oc * ay * ay + c, oc * ay * az + ax * s, 0.0, oc * az * ax + ay * s, oc * ay * az - ax * s, oc * az * az + c, 0.0, - 0.0, 0.0, 0.0, 1.0 / scale + 0.0, 0.0, 0.0, 1.0 ); - return shift2 * rotate * shift1; + return rotate * shift; } @@ -93,9 +78,6 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is bezier controls points into a new coordinate system such that the bezier curve coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis. */ - vec3 dst0; - vec3 dst1; - vec3 dst2; is_linear = temp_is_linear; // Portions of the parabola y = x^2 where abs(x) exceeds // this value are treated as straight lines. @@ -107,18 +89,15 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){ is_linear = 1.0; }else{ - dst0 = vec3(x0, x0 * x0, 0.0); - dst1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); - dst2 = vec3(x2, x2 * x2, 0.0); + // This triangle on the xy plane should be isometric + // to (b0, b1, b2), and it should define a quadratic + // bezier segment aligned with y = x^2 + vec3 dst0 = vec3(x0, x0 * x0, 0.0); + vec3 dst1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); + vec3 dst2 = vec3(x2, x2 * x2, 0.0); + return map_triangles(b0, b1, b2, dst0, dst1, dst2); } } - // Check if is_linear status changed above - if (bool(is_linear)){ - dst0 = vec3(0.0, 0.0, 0.0); - dst2 = vec3(1.0, 0.0, 0.0); - return map_point_pairs(b0, b2, dst0, dst2); - } - - // return map_point_pairs(b0, b2, dst0, dst1); - return map_triangles(b0, b1, b2, dst0, dst1, dst2); + // Only lands here if is_linear is 1.0 + return map_onto_x_axis(b0, b2); } From 72e5bde274523251572e02168e7c3f9d1848cc61 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 10:58:25 -0800 Subject: [PATCH 25/38] Rename xy-to-uv -> xyz-to-uv --- .../{get_xy_to_uv.glsl => get_xyz_to_uv.glsl} | 0 .../shaders/quadratic_bezier_fill/geom.glsl | 2 +- .../shaders/quadratic_bezier_stroke/geom.glsl | 19 ++++++++++--------- 3 files changed, 11 insertions(+), 10 deletions(-) rename manimlib/shaders/inserts/{get_xy_to_uv.glsl => get_xyz_to_uv.glsl} (100%) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xyz_to_uv.glsl similarity index 100% rename from manimlib/shaders/inserts/get_xy_to_uv.glsl rename to manimlib/shaders/inserts/get_xyz_to_uv.glsl diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 40985863d7..a9a63a6fe2 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -25,7 +25,7 @@ const float ANGLE_THRESHOLD = 1e-3; // Analog of import for manim only #INSERT get_gl_Position.glsl -#INSERT get_xy_to_uv.glsl +#INSERT get_xyz_to_uv.glsl #INSERT finalize_color.glsl diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 0a6969316f..4fa7c8d1c0 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -34,9 +34,10 @@ const int MITER_JOINT = 3; // consider them aligned const float COS_THRESHOLD = 0.999; +vec3 unit_normal = vec3(0.0, 0.0, 1.0); #INSERT get_gl_Position.glsl -#INSERT get_xy_to_uv.glsl +#INSERT get_xyz_to_uv.glsl #INSERT finalize_color.glsl @@ -109,6 +110,8 @@ void get_corners( // Unit normal and joint angles vec3 normal0 = get_joint_unit_normal(v_joint_product[0]); vec3 normal2 = get_joint_unit_normal(v_joint_product[2]); + // Set global unit normal + unit_normal = normal0; // Make sure normals point in the same direction if(dot(normal0, normal2) < 0) buff2 *= -1; @@ -120,8 +123,10 @@ void get_corners( p0_perp = buff0 * normalize(cross(normal0, v01)); p2_perp = buff2 * normalize(cross(normal2, v12)); }else{ - p0_perp = buff0 * normal0; - p2_perp = buff2 * normal2; + // p0_perp = buff0 * normal0; + // p2_perp = buff2 * normal2; + p0_perp = buff0 * normalize(cross(camera_position - p0, v01)); + p2_perp = buff2 * normalize(cross(camera_position - p2, v12)); } vec3 p1_perp = 0.5 * (p0_perp + p2_perp); @@ -171,7 +176,7 @@ void main() { // We want to change the coordinates to a space where the curve // coincides with y = x^2, between some values x0 and x2. Or, in // the case of a linear curve (bezier degree 1), just put it on - // the segment from (0, 0) to (1, 0) + // the x-axis mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); float uv_scale_factor = length(xyz_to_uv[0].xyz); @@ -186,11 +191,7 @@ void main() { int vert_index = i / 2; uv_coords = (xyz_to_uv * vec4(corners[i], 1)).xy; uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index]; - color = finalize_color( - v_color[vert_index], - corners[i], - vec3(0.0, 0.0, 1.0) // TODO - ); + color = finalize_color(v_color[vert_index], corners[i], unit_normal); gl_Position = get_gl_Position(position_point_into_frame(corners[i])); EmitVertex(); } From 44e5f15ae94fd91e9d8e46ff66b1b33c2d864a54 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 10:58:37 -0800 Subject: [PATCH 26/38] Default to non-flat stroke for meshes --- manimlib/mobject/three_dimensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manimlib/mobject/three_dimensions.py b/manimlib/mobject/three_dimensions.py index 65f3a610ef..2415934f50 100644 --- a/manimlib/mobject/three_dimensions.py +++ b/manimlib/mobject/three_dimensions.py @@ -38,6 +38,7 @@ def __init__( normal_nudge: float = 1e-2, depth_test: bool = True, joint_type: str = 'no_joint', + flat_stroke: bool = False, **kwargs ): self.uv_surface = uv_surface @@ -51,6 +52,7 @@ def __init__( joint_type=joint_type, **kwargs ) + self.set_flat_stroke(flat_stroke) def init_points(self) -> None: uv_surface = self.uv_surface From 40436d63709598cb1d7d8b3af644eb9cf54e50dc Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 11:03:22 -0800 Subject: [PATCH 27/38] Slightly cleaner xs_on_clean_parabola --- manimlib/shaders/inserts/get_xyz_to_uv.glsl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/manimlib/shaders/inserts/get_xyz_to_uv.glsl b/manimlib/shaders/inserts/get_xyz_to_uv.glsl index 0ba0e5f3a0..400eb699d8 100644 --- a/manimlib/shaders/inserts/get_xyz_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xyz_to_uv.glsl @@ -11,10 +11,9 @@ vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ float u0 = dot(b1 - b0, dd); float u2 = dot(b2 - b1, dd); - vec3 cp = cross(b2 - b0, dd); - float denom = length(cp); + float cp = length(cross(b2 - b0, dd)); - return vec2(u0 / denom, u2 / denom); + return vec2(u0 / cp, u2 / cp); } From 96b0ec909421e6d956c37072a8ecec349dacc297 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 12:29:25 -0800 Subject: [PATCH 28/38] Use fontTools.cu2qu.cu2qu import curve_to_quadratic --- manimlib/mobject/types/vectorized_mobject.py | 26 +++++++++----------- requirements.txt | 1 + 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index bca78fa156..3b2904191b 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1,6 +1,7 @@ from __future__ import annotations from functools import wraps +from fontTools.cu2qu.cu2qu import curve_to_quadratic import moderngl import numpy as np @@ -446,22 +447,17 @@ def add_cubic_bezier_curve_to( """ self.throw_error_if_no_points() last = self.get_last_point() - # If the two relevant tangents are close in angle to each other, - # then just approximate with a single quadratic bezier curve. - # Otherwise, approximate with two - v1 = handle1 - last - v2 = anchor - handle2 - angle = angle_between_vectors(v1, v2) - if self.use_simple_quadratic_approx and angle < 45 * DEGREES: - quadratic_approx = [last, find_intersection(last, v1, anchor, -v2), anchor] - else: - quadratic_approx = get_quadratic_approximation_of_cubic( - last, handle1, handle2, anchor - ) - if self.consider_points_equal(quadratic_approx[1], last): + # Not, this assumes all points are on the xy-plane + approx_2d = curve_to_quadratic( + [last[:2], handle1[:2], handle2[:2], anchor[:2]], + 1.0 # High tolerance for error + ) + approx_3d = np.zeros((len(approx_2d), 3)) + approx_3d[:, :2] = approx_2d + if self.consider_points_equal(approx_3d[1], last): # This is to prevent subpaths from accidentally being marked closed - quadratic_approx[1] = midpoint(*quadratic_approx[1:3]) - self.append_points(quadratic_approx[1:]) + approx_3d[1] = midpoint(*approx_3d[1:3]) + self.append_points(approx_3d[1:]) return self def add_quadratic_bezier_curve_to(self, handle: Vect3, anchor: Vect3): diff --git a/requirements.txt b/requirements.txt index ab0b5026ad..0030fd2ae0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ colour ipython isosurfaces +fontTools manimpango>=0.4.0.post0,<0.5.0 mapbox-earcut matplotlib From d39fea0d4dffd793ce48cc79fd77206f361fd0fb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 12:52:05 -0800 Subject: [PATCH 29/38] A few small fixes --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 4fa7c8d1c0..9e2f5461fc 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -63,7 +63,7 @@ void create_joint( out vec3 changing_c1 ){ float cos_angle = joint_product.w; - if(cos_angle > COS_THRESHOLD || int(joint_type) == NO_JOINT){ + if(abs(cos_angle) > COS_THRESHOLD || int(joint_type) == NO_JOINT){ // No joint changing_c0 = static_c0; changing_c1 = static_c1; @@ -105,7 +105,7 @@ void get_corners( // Add correction for sharp angles to prevent weird bevel effects if(v_joint_product[0].w < -0.5) buff0 *= 2 * (v_joint_product[0].w + 1.0); - if(v_joint_product[2].w < -0.5) buff2 *= 2 * (v_joint_product[0].w + 1.0); + if(v_joint_product[2].w < -0.5) buff2 *= 2 * (v_joint_product[2].w + 1.0); // Unit normal and joint angles vec3 normal0 = get_joint_unit_normal(v_joint_product[0]); @@ -113,8 +113,9 @@ void get_corners( // Set global unit normal unit_normal = normal0; - // Make sure normals point in the same direction - if(dot(normal0, normal2) < 0) buff2 *= -1; + // Choose the "outward" normal direction + normal0 *= sign(normal0.z); + normal2 *= sign(normal2.z); vec3 p0_perp; vec3 p2_perp; From 8b3aa8f5c6c1af482278d3519b5df722bb251a2d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 12:52:47 -0800 Subject: [PATCH 30/38] Account for edge cases on curve_to_quadratic --- manimlib/mobject/types/vectorized_mobject.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 3b2904191b..9dae14ad72 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -431,9 +431,8 @@ def add_cubic_bezier_curve( handle2: Vect3, anchor2: Vect3 ): - self.add_subpath(get_quadratic_approximation_of_cubic( - anchor1, handle1, handle2, anchor2 - )) + self.start_new_path(anchor1) + self.add_cubic_bezier_curve_to(handle1, handle2, anchor2) return self def add_cubic_bezier_curve_to( @@ -447,13 +446,19 @@ def add_cubic_bezier_curve_to( """ self.throw_error_if_no_points() last = self.get_last_point() - # Not, this assumes all points are on the xy-plane + # Note, this assumes all points are on the xy-plane approx_2d = curve_to_quadratic( [last[:2], handle1[:2], handle2[:2], anchor[:2]], - 1.0 # High tolerance for error + 0.1 * get_norm(anchor - last) ) - approx_3d = np.zeros((len(approx_2d), 3)) - approx_3d[:, :2] = approx_2d + if approx_2d is not None and len(approx_2d) % 2 == 1: + approx_3d = np.zeros((len(approx_2d), 3)) + approx_3d[:, :2] = approx_2d + else: + approx_3d = get_quadratic_approximation_of_cubic( + last, handle1, handle2, anchor + ) + if self.consider_points_equal(approx_3d[1], last): # This is to prevent subpaths from accidentally being marked closed approx_3d[1] = midpoint(*approx_3d[1:3]) From e20efda3dff67e9ca7c8f92b73b691275b2375f8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 13:03:23 -0800 Subject: [PATCH 31/38] Revert away from using curve_to_quadratic --- manimlib/mobject/types/vectorized_mobject.py | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 9dae14ad72..68888e4f8e 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1,7 +1,6 @@ from __future__ import annotations from functools import wraps -from fontTools.cu2qu.cu2qu import curve_to_quadratic import moderngl import numpy as np @@ -447,22 +446,19 @@ def add_cubic_bezier_curve_to( self.throw_error_if_no_points() last = self.get_last_point() # Note, this assumes all points are on the xy-plane - approx_2d = curve_to_quadratic( - [last[:2], handle1[:2], handle2[:2], anchor[:2]], - 0.1 * get_norm(anchor - last) - ) - if approx_2d is not None and len(approx_2d) % 2 == 1: - approx_3d = np.zeros((len(approx_2d), 3)) - approx_3d[:, :2] = approx_2d + v1 = handle1 - last + v2 = anchor - handle2 + angle = angle_between_vectors(v1, v2) + if self.use_simple_quadratic_approx and angle < 45 * DEGREES: + quad_approx = [last, find_intersection(last, v1, anchor, -v2), anchor] else: - approx_3d = get_quadratic_approximation_of_cubic( + quad_approx = get_quadratic_approximation_of_cubic( last, handle1, handle2, anchor ) - - if self.consider_points_equal(approx_3d[1], last): + if self.consider_points_equal(quad_approx[1], last): # This is to prevent subpaths from accidentally being marked closed - approx_3d[1] = midpoint(*approx_3d[1:3]) - self.append_points(approx_3d[1:]) + quad_approx[1] = midpoint(*quad_approx[1:3]) + self.append_points(quad_approx[1:]) return self def add_quadratic_bezier_curve_to(self, handle: Vect3, anchor: Vect3): From 13c41be17f97539ed029f91a8b89477119de0ffe Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 13:07:18 -0800 Subject: [PATCH 32/38] Small clean up --- .../shaders/quadratic_bezier_stroke/frag.glsl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/frag.glsl b/manimlib/shaders/quadratic_bezier_stroke/frag.glsl index 38a295b9e3..ca95938bea 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/frag.glsl @@ -12,18 +12,21 @@ out vec4 frag_color; const float QUICK_DIST_WIDTH = 0.2; -// Distance from (x0, y0) to the curve y = x^2 -float dist_to_curve(float x0, float y0){ +float dist_to_curve(){ + // Returns distance from uv_coords to the curve v = u^2 + float x0 = uv_coords.x; + float y0 = uv_coords.y; + // In the linear case, the curve will have // been set to equal the x axis - if(bool(is_linear)) return y0; + if(bool(is_linear)) return abs(y0); if(uv_stroke_width < QUICK_DIST_WIDTH){ // This is a quick approximation for computing // the distance to the curve. // Evaluate F(x, y) = y - x^2 // divide by its gradient's magnitude - return (y0 - x0 * x0) / sqrt(1 + 4 * x0 * x0); + return abs((y0 - x0 * x0) / sqrt(1 + 4 * x0 * x0)); } // Otherwise, solve for the minimal distance. // The distance squared between (x0, y0) and a point (x, x^2) looks like @@ -50,9 +53,8 @@ float dist_to_curve(float x0, float y0){ void main() { if (uv_stroke_width == 0) discard; - // Compute sdf for the region around the curve we wish to color. - float dist = dist_to_curve(uv_coords.x, uv_coords.y); - float signed_dist = abs(dist) - 0.5 * uv_stroke_width; + // sdf for the region around the curve we wish to color. + float signed_dist = dist_to_curve() - 0.5 * uv_stroke_width; frag_color = color; frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width); From fa525b494c9d9f0cfc4c6be1959ad5bfffd786e4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 13:07:37 -0800 Subject: [PATCH 33/38] Increase threshold for bevel tweaking --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 9e2f5461fc..065a915e00 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -104,8 +104,8 @@ void get_corners( float buff2 = 0.5 * v_stroke_width[2] + aaw; // Add correction for sharp angles to prevent weird bevel effects - if(v_joint_product[0].w < -0.5) buff0 *= 2 * (v_joint_product[0].w + 1.0); - if(v_joint_product[2].w < -0.5) buff2 *= 2 * (v_joint_product[2].w + 1.0); + if(v_joint_product[0].w < -0.75) buff0 *= 4 * (v_joint_product[0].w + 1.0); + if(v_joint_product[2].w < -0.75) buff2 *= 4 * (v_joint_product[2].w + 1.0); // Unit normal and joint angles vec3 normal0 = get_joint_unit_normal(v_joint_product[0]); From b667d89e9be220668e921513e240619de82b3449 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 13:44:41 -0800 Subject: [PATCH 34/38] Simplify get_gl_Position --- manimlib/camera/camera.py | 14 +++++---- manimlib/shaders/image/vert.glsl | 2 +- manimlib/shaders/inserts/get_gl_Position.glsl | 29 +++++-------------- ...et_rotated_surface_unit_normal_vector.glsl | 16 ---------- manimlib/shaders/mandelbrot_fractal/vert.glsl | 2 +- manimlib/shaders/newton_fractal/vert.glsl | 2 +- .../shaders/quadratic_bezier_fill/geom.glsl | 8 ++--- .../shaders/quadratic_bezier_stroke/geom.glsl | 2 +- manimlib/shaders/simple_vert.glsl | 2 +- manimlib/shaders/surface/vert.glsl | 2 +- manimlib/shaders/textured_surface/vert.glsl | 2 +- manimlib/shaders/true_dot/geom.glsl | 3 ++ manimlib/shaders/true_dot/vert.glsl | 4 +-- 13 files changed, 29 insertions(+), 59 deletions(-) delete mode 100644 manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 027f218a66..bc91df939b 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -39,6 +39,7 @@ def __init__( self.frame_shape = frame_shape self.center_point = center_point self.focal_dist_to_height = focal_dist_to_height + self.perspective_transform = np.identity(4) super().__init__(**kwargs) def init_uniforms(self) -> None: @@ -82,6 +83,11 @@ def get_gamma(self): def get_inverse_camera_rotation_matrix(self): return self.get_orientation().as_matrix().T + def get_perspective_transform(self): + self.perspective_transform[:3, :3] = self.get_inverse_camera_rotation_matrix() + self.perspective_transform[:3, 3] = -self.get_center() + return self.perspective_transform + def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): rot = Rotation.from_rotvec(angle * normalize(axis)) self.set_orientation(rot * self.get_orientation()) @@ -488,16 +494,14 @@ def set_shader_uniforms( def refresh_perspective_uniforms(self) -> None: frame = self.frame # Orient light - rotation = frame.get_inverse_camera_rotation_matrix() - offset = frame.get_center() + perspective_transform = frame.get_perspective_transform() light_pos = self.light_source.get_location() - cam_pos = self.frame.get_implied_camera_location() # TODO + cam_pos = self.frame.get_implied_camera_location() self.perspective_uniforms = { "frame_shape": frame.get_shape(), "pixel_shape": self.get_pixel_shape(), - "camera_offset": tuple(offset), - "camera_rotation": tuple(np.array(rotation).T.flatten()), + "perspective": tuple(perspective_transform.T.flatten()), "camera_position": tuple(cam_pos), "light_position": tuple(light_pos), "focal_distance": frame.get_focal_distance(), diff --git a/manimlib/shaders/image/vert.glsl b/manimlib/shaders/image/vert.glsl index 036dc838c9..d3344931de 100644 --- a/manimlib/shaders/image/vert.glsl +++ b/manimlib/shaders/image/vert.glsl @@ -15,5 +15,5 @@ out float v_opacity; void main(){ v_im_coords = im_coords; v_opacity = opacity; - gl_Position = get_gl_Position(position_point_into_frame(point)); + gl_Position = get_gl_Position(point); } \ No newline at end of file diff --git a/manimlib/shaders/inserts/get_gl_Position.glsl b/manimlib/shaders/inserts/get_gl_Position.glsl index cfcacf480a..0c872515f8 100644 --- a/manimlib/shaders/inserts/get_gl_Position.glsl +++ b/manimlib/shaders/inserts/get_gl_Position.glsl @@ -1,17 +1,18 @@ uniform float is_fixed_in_frame; -uniform vec3 camera_offset; -uniform mat3 camera_rotation; +uniform mat4 perspective; uniform vec2 frame_shape; uniform float focal_distance; const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0); vec4 get_gl_Position(vec3 point){ - vec2 shape; - if(bool(is_fixed_in_frame)) shape = DEFAULT_FRAME_SHAPE; - else shape = frame_shape; - vec4 result = vec4(point, 1.0); + vec2 shape = DEFAULT_FRAME_SHAPE; + if(!bool(is_fixed_in_frame)){ + result = perspective * result; + shape = frame_shape; + } + result.x *= 2.0 / shape.x; result.y *= 2.0 / shape.y; result.z /= focal_distance; @@ -20,19 +21,3 @@ vec4 get_gl_Position(vec3 point){ result.z *= -0.1; return result; } - - -vec3 rotate_point_into_frame(vec3 point){ - if(bool(is_fixed_in_frame)){ - return point; - } - return camera_rotation * point; -} - - -vec3 position_point_into_frame(vec3 point){ - if(bool(is_fixed_in_frame)){ - return point; - } - return rotate_point_into_frame(point - camera_offset); -} diff --git a/manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl b/manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl deleted file mode 100644 index 012ec68263..0000000000 --- a/manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl +++ /dev/null @@ -1,16 +0,0 @@ -// Assumes the following uniforms exist in the surrounding context: -// uniform vec3 camera_offset; -// uniform mat3 camera_rotation; - -vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){ - vec3 cp = cross( - (du_point - point), - (dv_point - point) - ); - if(length(cp) == 0){ - // Instead choose a normal to just dv_point - point in the direction of point - vec3 v2 = dv_point - point; - cp = cross(cross(v2, point), v2); - } - return normalize(rotate_point_into_frame(cp)); -} \ No newline at end of file diff --git a/manimlib/shaders/mandelbrot_fractal/vert.glsl b/manimlib/shaders/mandelbrot_fractal/vert.glsl index b2ddd3f034..879df9233e 100644 --- a/manimlib/shaders/mandelbrot_fractal/vert.glsl +++ b/manimlib/shaders/mandelbrot_fractal/vert.glsl @@ -10,5 +10,5 @@ uniform vec3 offset; void main(){ xyz_coords = (point - offset) / scale_factor; - gl_Position = get_gl_Position(position_point_into_frame(point)); + gl_Position = get_gl_Position(point); } \ No newline at end of file diff --git a/manimlib/shaders/newton_fractal/vert.glsl b/manimlib/shaders/newton_fractal/vert.glsl index b2ddd3f034..879df9233e 100644 --- a/manimlib/shaders/newton_fractal/vert.glsl +++ b/manimlib/shaders/newton_fractal/vert.glsl @@ -10,5 +10,5 @@ uniform vec3 offset; void main(){ xyz_coords = (point - offset) / scale_factor; - gl_Position = get_gl_Position(position_point_into_frame(point)); + gl_Position = get_gl_Position(point); } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index a9a63a6fe2..0d8d12f2ee 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -30,12 +30,8 @@ const float ANGLE_THRESHOLD = 1e-3; void emit_vertex_wrapper(vec3 point, int index, vec3 unit_normal){ - color = finalize_color( - v_color[index], - point, - unit_normal - ); - gl_Position = get_gl_Position(position_point_into_frame(point)); + color = finalize_color(v_color[index], point, unit_normal); + gl_Position = get_gl_Position(point); EmitVertex(); } diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 065a915e00..dae2d0d518 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -193,7 +193,7 @@ void main() { uv_coords = (xyz_to_uv * vec4(corners[i], 1)).xy; uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index]; color = finalize_color(v_color[vert_index], corners[i], unit_normal); - gl_Position = get_gl_Position(position_point_into_frame(corners[i])); + gl_Position = get_gl_Position(corners[i]); EmitVertex(); } EndPrimitive(); diff --git a/manimlib/shaders/simple_vert.glsl b/manimlib/shaders/simple_vert.glsl index 91916fee9b..b5e0c22926 100644 --- a/manimlib/shaders/simple_vert.glsl +++ b/manimlib/shaders/simple_vert.glsl @@ -5,5 +5,5 @@ in vec3 point; #INSERT get_gl_Position.glsl void main(){ - gl_Position = get_gl_Position(position_point_into_frame(point)); + gl_Position = get_gl_Position(point); } \ No newline at end of file diff --git a/manimlib/shaders/surface/vert.glsl b/manimlib/shaders/surface/vert.glsl index 95b8e10828..7ea76b73ad 100644 --- a/manimlib/shaders/surface/vert.glsl +++ b/manimlib/shaders/surface/vert.glsl @@ -14,7 +14,7 @@ out vec4 v_color; #INSERT finalize_color.glsl void main(){ - gl_Position = get_gl_Position(position_point_into_frame(point)); + gl_Position = get_gl_Position(point); vec3 normal = get_unit_normal(point, du_point, dv_point); v_color = finalize_color(rgba, point, normal); diff --git a/manimlib/shaders/textured_surface/vert.glsl b/manimlib/shaders/textured_surface/vert.glsl index 02b9afce3b..92826b360d 100644 --- a/manimlib/shaders/textured_surface/vert.glsl +++ b/manimlib/shaders/textured_surface/vert.glsl @@ -19,5 +19,5 @@ void main(){ v_normal = get_unit_normal(point, du_point, dv_point); v_im_coords = im_coords; v_opacity = opacity; - gl_Position = get_gl_Position(position_point_into_frame(point)); + gl_Position = get_gl_Position(point); } \ No newline at end of file diff --git a/manimlib/shaders/true_dot/geom.glsl b/manimlib/shaders/true_dot/geom.glsl index b1dfc0ba74..0ff8cca5d6 100644 --- a/manimlib/shaders/true_dot/geom.glsl +++ b/manimlib/shaders/true_dot/geom.glsl @@ -5,6 +5,7 @@ layout (triangle_strip, max_vertices = 4) out; uniform float anti_alias_width; uniform vec2 pixel_shape; +uniform vec3 camera_position; in vec3 v_point[1]; in float v_radius[1]; @@ -27,6 +28,8 @@ void main() { radius = v_radius[0] / max(1.0 - v_point[0].z / focal_distance / frame_shape.y, 0.0); float rpa = radius + scaled_aaw; + vec3 to_cam = camera_position - v_point[0]; + for(int i = 0; i < 4; i++){ // To account for perspective diff --git a/manimlib/shaders/true_dot/vert.glsl b/manimlib/shaders/true_dot/vert.glsl index 08c216068b..923d616e43 100644 --- a/manimlib/shaders/true_dot/vert.glsl +++ b/manimlib/shaders/true_dot/vert.glsl @@ -8,10 +8,8 @@ out vec3 v_point; out float v_radius; out vec4 v_color; -#INSERT get_gl_Position.glsl - void main(){ - v_point = position_point_into_frame(point); + v_point = point; v_radius = radius; v_color = rgba; } \ No newline at end of file From 6c2544098bb609ff98f88ee0abf078db42ce6c79 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 13:50:09 -0800 Subject: [PATCH 35/38] Store pixel_size instead of pixel_shape --- manimlib/camera/camera.py | 5 +++-- manimlib/shaders/quadratic_bezier_fill/geom.glsl | 4 ++-- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 4 ++-- manimlib/shaders/true_dot/geom.glsl | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index bc91df939b..5e94bbfbe4 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -497,10 +497,11 @@ def refresh_perspective_uniforms(self) -> None: perspective_transform = frame.get_perspective_transform() light_pos = self.light_source.get_location() cam_pos = self.frame.get_implied_camera_location() + frame_shape = frame.get_shape() self.perspective_uniforms = { - "frame_shape": frame.get_shape(), - "pixel_shape": self.get_pixel_shape(), + "frame_shape": frame_shape, + "pixel_size": frame_shape[0] / self.get_pixel_shape()[0], "perspective": tuple(perspective_transform.T.flatten()), "camera_position": tuple(cam_pos), "light_position": tuple(light_pos), diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 0d8d12f2ee..5f9f8ab60d 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -4,7 +4,7 @@ layout (triangles) in; layout (triangle_strip, max_vertices = 5) out; uniform float anti_alias_width; -uniform vec2 pixel_shape; +uniform float pixel_size; in vec3 verts[3]; in float v_orientation[3]; @@ -63,7 +63,7 @@ void emit_pentagon( is_linear = float(angle < ANGLE_THRESHOLD); bool fill_inside = orientation > 0.0; - float aaw = anti_alias_width * frame_shape.y / pixel_shape.y; + float aaw = anti_alias_width * pixel_size; vec3 corners[5] = vec3[5](p0, p0, p1, p2, p2); if(fill_inside || bool(is_linear)){ diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index dae2d0d518..f19484cb0b 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -5,7 +5,7 @@ layout (triangle_strip, max_vertices = 6) out; uniform float anti_alias_width; uniform float flat_stroke; -uniform vec2 pixel_shape; +uniform float pixel_size; uniform float joint_type; in vec3 verts[3]; @@ -181,7 +181,7 @@ void main() { mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); float uv_scale_factor = length(xyz_to_uv[0].xyz); - float scaled_aaw = anti_alias_width * (frame_shape.y / pixel_shape.y); + float scaled_aaw = anti_alias_width * pixel_size; uv_anti_alias_width = uv_scale_factor * scaled_aaw; vec3 corners[6]; diff --git a/manimlib/shaders/true_dot/geom.glsl b/manimlib/shaders/true_dot/geom.glsl index 0ff8cca5d6..e0148dc3ab 100644 --- a/manimlib/shaders/true_dot/geom.glsl +++ b/manimlib/shaders/true_dot/geom.glsl @@ -4,7 +4,7 @@ layout (points) in; layout (triangle_strip, max_vertices = 4) out; uniform float anti_alias_width; -uniform vec2 pixel_shape; +uniform float pixel_size; uniform vec3 camera_position; in vec3 v_point[1]; @@ -24,7 +24,7 @@ void main() { radius = v_radius[0]; center = v_point[0].xy; - scaled_aaw = (frame_shape.y / pixel_shape.y); + scaled_aaw = anti_alias_width * pixel_size; radius = v_radius[0] / max(1.0 - v_point[0].z / focal_distance / frame_shape.y, 0.0); float rpa = radius + scaled_aaw; From cd3c5031fa7978e72868ea2555375dba1339c6b5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 13:59:50 -0800 Subject: [PATCH 36/38] Fix get_perspective_transform to shift before rotation --- manimlib/camera/camera.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 5e94bbfbe4..818dd790d9 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -84,9 +84,17 @@ def get_inverse_camera_rotation_matrix(self): return self.get_orientation().as_matrix().T def get_perspective_transform(self): - self.perspective_transform[:3, :3] = self.get_inverse_camera_rotation_matrix() - self.perspective_transform[:3, 3] = -self.get_center() - return self.perspective_transform + """ + Returns a 4x4 for the affine transformation mapping a point + into the camera's internal coordinate system + """ + result = self.perspective_transform + result[:] = np.identity(4) + result[:3, 3] = -self.get_center() + rotation = np.identity(4) + rotation[:3, :3] = self.get_inverse_camera_rotation_matrix() + result[:] = np.dot(rotation, result) + return result def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): rot = Rotation.from_rotvec(angle * normalize(axis)) @@ -215,6 +223,7 @@ def __init__( self.background_rgba: list[float] = list(color_to_rgba( background_color, background_opacity )) + self.perspective_uniforms = dict() self.init_frame(**frame_config) self.init_context(ctx) self.init_shaders() @@ -499,14 +508,14 @@ def refresh_perspective_uniforms(self) -> None: cam_pos = self.frame.get_implied_camera_location() frame_shape = frame.get_shape() - self.perspective_uniforms = { - "frame_shape": frame_shape, - "pixel_size": frame_shape[0] / self.get_pixel_shape()[0], - "perspective": tuple(perspective_transform.T.flatten()), - "camera_position": tuple(cam_pos), - "light_position": tuple(light_pos), - "focal_distance": frame.get_focal_distance(), - } + self.perspective_uniforms.update( + frame_shape=frame_shape, + pixel_size=frame_shape[0] / self.get_pixel_shape()[0], + perspective=tuple(perspective_transform.T.flatten()), + camera_position=tuple(cam_pos), + light_position=tuple(light_pos), + focal_distance=frame.get_focal_distance(), + ) def init_textures(self) -> None: self.n_textures: int = 0 From 8e2cf04b715512915617746102d4a64126f92b73 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 15:36:00 -0800 Subject: [PATCH 37/38] Simplify true_dot shaders --- manimlib/camera/camera.py | 2 ++ manimlib/mobject/types/dot_cloud.py | 2 +- manimlib/shaders/true_dot/frag.glsl | 40 ++++++++++--------------- manimlib/shaders/true_dot/geom.glsl | 45 ----------------------------- manimlib/shaders/true_dot/vert.glsl | 19 +++++++++--- 5 files changed, 34 insertions(+), 74 deletions(-) delete mode 100644 manimlib/shaders/true_dot/geom.glsl diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 818dd790d9..4b4743bc85 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -248,6 +248,8 @@ def init_context(self, ctx: moderngl.Context | None = None) -> None: self.fbo = fbo self.set_ctx_blending() + self.ctx.enable(moderngl.PROGRAM_POINT_SIZE) + # For multisample antialiasing fbo_msaa = self.get_fbo(ctx, self.samples) fbo_msaa.use() diff --git a/manimlib/mobject/types/dot_cloud.py b/manimlib/mobject/types/dot_cloud.py index 8e8e0a0e67..613c6e0b19 100644 --- a/manimlib/mobject/types/dot_cloud.py +++ b/manimlib/mobject/types/dot_cloud.py @@ -37,7 +37,7 @@ def __init__( opacity: float = 1.0, radius: float = DEFAULT_DOT_RADIUS, glow_factor: float = 0.0, - anti_alias_width: float = 1.0, + anti_alias_width: float = 2.0, **kwargs ): self.radius = radius diff --git a/manimlib/shaders/true_dot/frag.glsl b/manimlib/shaders/true_dot/frag.glsl index a34d9ed05a..8f82ee4c78 100644 --- a/manimlib/shaders/true_dot/frag.glsl +++ b/manimlib/shaders/true_dot/frag.glsl @@ -1,41 +1,33 @@ #version 330 -uniform float anti_alias_width; uniform float glow_factor; +uniform mat4 perspective; in vec4 color; -in float radius; -in vec2 center; -in vec2 point; in float scaled_aaw; +in vec3 v_point; out vec4 frag_color; -// This include a delaration of -// uniform vec3 shading -// uniform vec3 camera_position -// uniform vec3 light_position +// This include a delaration of uniform vec3 shading #INSERT finalize_color.glsl void main() { - vec2 diff = point - center; - float dist = length(diff); - float signed_dist = dist - radius; - if (signed_dist > 0.5 * scaled_aaw){ - discard; - } + vec2 vect = 2.0 * gl_PointCoord.xy - vec2(1.0); + float r = length(vect); + if(r > 1.0 + scaled_aaw) discard; + frag_color = color; - if(shading != vec3(0.0)){ - vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius))); - frag_color = finalize_color( - frag_color, - vec3(point.xy, 0.0), - normal - ); - } + if(glow_factor > 0){ - frag_color.a *= pow(1 - dist / radius, glow_factor); + frag_color.a *= pow(1 - r, glow_factor); + } + + 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); } - frag_color.a *= smoothstep(0.5, -0.5, signed_dist / scaled_aaw); + frag_color.a *= smoothstep(1.0, 1.0 - scaled_aaw, r); } \ No newline at end of file diff --git a/manimlib/shaders/true_dot/geom.glsl b/manimlib/shaders/true_dot/geom.glsl deleted file mode 100644 index e0148dc3ab..0000000000 --- a/manimlib/shaders/true_dot/geom.glsl +++ /dev/null @@ -1,45 +0,0 @@ -#version 330 - -layout (points) in; -layout (triangle_strip, max_vertices = 4) out; - -uniform float anti_alias_width; -uniform float pixel_size; -uniform vec3 camera_position; - -in vec3 v_point[1]; -in float v_radius[1]; -in vec4 v_color[1]; - -out vec4 color; -out float radius; -out vec2 center; -out vec2 point; -out float scaled_aaw; - -#INSERT get_gl_Position.glsl - -void main() { - color = v_color[0]; - radius = v_radius[0]; - center = v_point[0].xy; - - scaled_aaw = anti_alias_width * pixel_size; - radius = v_radius[0] / max(1.0 - v_point[0].z / focal_distance / frame_shape.y, 0.0); - float rpa = radius + scaled_aaw; - - vec3 to_cam = camera_position - v_point[0]; - - for(int i = 0; i < 4; i++){ - // To account for perspective - - int x_index = 2 * (i % 2) - 1; - int y_index = 2 * (i / 2) - 1; - vec3 corner = v_point[0] + vec3(x_index * rpa, y_index * rpa, 0.0); - - gl_Position = get_gl_Position(corner); - point = corner.xy; - 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 923d616e43..77c328c4b0 100644 --- a/manimlib/shaders/true_dot/vert.glsl +++ b/manimlib/shaders/true_dot/vert.glsl @@ -1,15 +1,26 @@ #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 float v_radius; -out vec4 v_color; +out vec3 light_pos; + +#INSERT get_gl_Position.glsl void main(){ v_point = point; - v_radius = radius; - v_color = rgba; + color = rgba; + scaled_aaw = (anti_alias_width * pixel_size) / radius; + + gl_Position = get_gl_Position(point); + float z = -10 * gl_Position.z; + float scaled_radius = radius * 1.0 / (1.0 - z); + gl_PointSize = (scaled_radius / pixel_size) + anti_alias_width; } \ No newline at end of file From 3820e098c05bbc02f9eb6a745b32d3329a10b6d1 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Jan 2023 16:25:32 -0800 Subject: [PATCH 38/38] Tweak to type hints --- manimlib/utils/bezier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manimlib/utils/bezier.py b/manimlib/utils/bezier.py index df7a900e4a..3685cf15fc 100644 --- a/manimlib/utils/bezier.py +++ b/manimlib/utils/bezier.py @@ -22,14 +22,14 @@ def bezier( - points: Sequence[Scalable] | VectNArray -) -> Callable[[float], Scalable]: + points: Sequence[float | FloatArray] | VectNArray +) -> Callable[[float], float | FloatArray]: if len(points) == 0: raise Exception("bezier cannot be calld on an empty list") n = len(points) - 1 - def result(t: float) -> Scalable: + def result(t: float) -> float | FloatArray: return sum( ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point for k, point in enumerate(points)