diff --git a/manimlib/mobject/three_dimensions.py b/manimlib/mobject/three_dimensions.py index 8e44d7c9..2bd88339 100644 --- a/manimlib/mobject/three_dimensions.py +++ b/manimlib/mobject/three_dimensions.py @@ -1,93 +1,24 @@ from manimlib.constants import * from manimlib.mobject.geometry import Square -from manimlib.mobject.types.surface_mobject import SurfaceMobject +from manimlib.mobject.types.surface_mobject import ParametricSurface from manimlib.mobject.types.vectorized_mobject import VGroup -class ParametricSurface(SurfaceMobject): - CONFIG = { - "u_range": (0, 1), - "v_range": (0, 1), - "resolution": (32, 32), - "surface_piece_config": {}, - "fill_color": BLUE_D, - "fill_opacity": 1.0, - "checkerboard_colors": [BLUE_D, BLUE_E], - "stroke_color": LIGHT_GREY, - "stroke_width": 0.5, - "should_make_jagged": False, - "pre_function_handle_to_anchor_scale_factor": 0.00001, - } - - def __init__(self, function=None, **kwargs): - if function is None: - self.uv_func = self.func - else: - self.uv_func = function - super().__init__(**kwargs) - - def init_points(self): - epsilon = 1e-6 # For differentials - nu, nv = self.resolution - u_range = np.linspace(*self.u_range, nu + 1) - v_range = np.linspace(*self.v_range, nv + 1) - # List of three grids, [Pure uv values, those nudged by du, those nudged by dv] - uv_grids = [ - np.array([[[u, v] for v in v_range] for u in u_range]) - for (du, dv) in [(0, 0), (epsilon, 0), (0, epsilon)] - ] - point_grid, points_nudged_du, points_nudged_dv = [ - np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid) - for uv_grid in uv_grids - ] - normal_grid = np.cross( - (points_nudged_du - point_grid) / epsilon, - (points_nudged_dv - point_grid) / epsilon, - ) - - self.set_points( - self.get_triangle_ready_array_from_grid(point_grid), - self.get_triangle_ready_array_from_grid(normal_grid), - ) - - # self.points = point_grid[indices] - - def get_triangle_ready_array_from_grid(self, grid): - # Given a grid, say of points or normals, this returns an Nx3 array - # whose rows are elements from this grid in such such a way that successive - # triplets of points form triangles covering the grid. - nu = grid.shape[0] - 1 - nv = grid.shape[1] - 1 - dim = grid.shape[2] - arr = np.zeros((nu * nv * 6, dim)) - # To match the triangles covering this surface - arr[0::6] = grid[:-1, :-1].reshape((nu * nv, 3)) # Top left - arr[1::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left - arr[2::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right - arr[3::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right - arr[4::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left - arr[5::6] = grid[+1:, +1:].reshape((nu * nv, 3)) # Bottom right - return arr - - def func(self, u, v): - pass - - # Sphere, cylinder, cube, prism class Sphere(ParametricSurface): CONFIG = { - "resolution": (12, 24), + "resolution": (96, 48), "radius": 1, - "u_range": (0, PI), - "v_range": (0, TAU), + "u_range": (0, TAU), + "v_range": (0, PI), } def func(self, u, v): return self.radius * np.array([ - np.cos(v) * np.sin(u), - np.sin(v) * np.sin(u), - np.cos(u) + np.cos(u) * np.sin(v), + np.sin(u) * np.sin(v), + np.cos(v) ]) diff --git a/manimlib/mobject/types/surface_mobject.py b/manimlib/mobject/types/surface_mobject.py index b1774fef..1b08bd8a 100644 --- a/manimlib/mobject/types/surface_mobject.py +++ b/manimlib/mobject/types/surface_mobject.py @@ -1,20 +1,20 @@ import numpy as np import moderngl -# from PIL import Image +from PIL import Image from manimlib.constants import * from manimlib.mobject.mobject import Mobject from manimlib.utils.color import color_to_rgba +from manimlib.utils.images import get_full_raster_image_path class SurfaceMobject(Mobject): CONFIG = { "color": GREY, "opacity": 1, - "gloss": 1.0, + "gloss": 0.3, "render_primative": moderngl.TRIANGLES, - # "render_primative": moderngl.TRIANGLE_STRIP, "vert_shader_file": "surface_vert.glsl", "frag_shader_file": "surface_frag.glsl", "shader_dtype": [ @@ -22,7 +22,6 @@ class SurfaceMobject(Mobject): ('normal', np.float32, (3,)), ('color', np.float32, (4,)), ('gloss', np.float32, (1,)), - # ('im_coords', np.float32, (2,)), ] } @@ -55,6 +54,13 @@ class SurfaceMobject(Mobject): rgba = color_to_rgba(color, opacity) self.rgbas = np.array([rgba]) + def set_gloss(self, gloss, family=True): + self.gloss = gloss + if family: + for sm in self.submobjects: + sm.set_gloss(gloss, family) + return self + def apply_function(self, function, **kwargs): # Apply it to infinitesimal neighbors to preserve normals pass @@ -74,3 +80,123 @@ class SurfaceMobject(Mobject): data["color"] = self.rgbas data["gloss"] = self.gloss return data + + +class ParametricSurface(SurfaceMobject): + CONFIG = { + "u_range": (0, 1), + "v_range": (0, 1), + "resolution": (100, 100), + "surface_piece_config": {}, + "fill_color": BLUE_D, + "fill_opacity": 1.0, + "checkerboard_colors": [BLUE_D, BLUE_E], + "stroke_color": LIGHT_GREY, + "stroke_width": 0.5, + "should_make_jagged": False, + "pre_function_handle_to_anchor_scale_factor": 0.00001, + } + + def __init__(self, function=None, **kwargs): + if function is None: + self.uv_func = self.func + else: + self.uv_func = function + super().__init__(**kwargs) + + def init_points(self): + epsilon = 1e-6 # For differentials + nu, nv = self.resolution + u_range = np.linspace(*self.u_range, nu + 1) + v_range = np.linspace(*self.v_range, nv + 1) + + # List of three grids, [Pure uv values, those nudged by du, those nudged by dv] + uv_grids = [ + np.array([[[u + du, v + dv] for v in v_range] for u in u_range]) + for (du, dv) in [(0, 0), (epsilon, 0), (0, epsilon)] + ] + point_grid, points_nudged_du, points_nudged_dv = [ + np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid) + for uv_grid in uv_grids + ] + normal_grid = np.cross( + (points_nudged_du - point_grid) / epsilon, + (points_nudged_dv - point_grid) / epsilon, + ) + + self.set_points( + self.get_triangle_ready_array_from_grid(point_grid), + self.get_triangle_ready_array_from_grid(normal_grid), + ) + + def get_triangle_ready_array_from_grid(self, grid): + # Given a grid, say of points or normals, this returns an Nx3 array + # whose rows are elements from this grid in such such a way that successive + # triplets of points form triangles covering the grid. + nu, nv, dim = grid.shape + nu -= 1 + nv -= 1 + arr = np.zeros((nu * nv * 6, dim)) + # To match the triangles covering this surface + arr[0::6] = grid[:-1, :-1].reshape((nu * nv, dim)) # Top left + arr[1::6] = grid[+1:, :-1].reshape((nu * nv, dim)) # Bottom left + arr[2::6] = grid[:-1, +1:].reshape((nu * nv, dim)) # Top right + arr[3::6] = grid[:-1, +1:].reshape((nu * nv, dim)) # Top right + arr[4::6] = grid[+1:, :-1].reshape((nu * nv, dim)) # Bottom left + arr[5::6] = grid[+1:, +1:].reshape((nu * nv, dim)) # Bottom right + return arr + + def func(self, u, v): + raise Exception("Not implemented") + + +class TexturedSurfaceMobject(SurfaceMobject): + CONFIG = { + "vert_shader_file": "textured_surface_vert.glsl", + "frag_shader_file": "textured_surface_frag.glsl", + "shader_dtype": [ + ('point', np.float32, (3,)), + ('normal', np.float32, (3,)), + ('im_coords', np.float32, (2,)), + ('opacity', np.float32, (1,)), + ('gloss', np.float32, (1,)), + ] + } + + def __init__(self, uv_surface, filename, **kwargs): + if not isinstance(uv_surface, ParametricSurface): + raise Exception("uv_surface must be of type Paramet") + path = get_full_raster_image_path(filename) + self.image = Image.open(path) + self.texture_path = path + super().__init__(**kwargs) + + self.set_points( + uv_surface.points, + uv_surface.normals, + ) + self.opacity = uv_surface.rgbas[:, 3] + self.gloss = uv_surface.gloss + + # Init im_coords + nu, nv = uv_surface.resolution + u_range = np.linspace(0, 1, nu + 1) + v_range = np.linspace(0, 1, nv + 1) # Upsidedown? + uv_grid = np.array([[[u, v] for v in v_range] for u in u_range]) + self.im_coords = uv_surface.get_triangle_ready_array_from_grid(uv_grid) + + def set_opacity(self, opacity, family=True): + self.opacity = opacity + if family: + for sm in self.submobjects: + sm.set_opacity(opacity, family) + return self + + def get_shader_data(self): + data = self.get_blank_shader_data_array(len(self.points)) + data["point"] = self.points + data["normal"] = self.normals + data["im_coords"] = self.im_coords + data["opacity"] = self.opacity + data["gloss"] = self.gloss + return data diff --git a/manimlib/shaders/add_light.glsl b/manimlib/shaders/add_light.glsl index 1c1b61cb..44aab967 100644 --- a/manimlib/shaders/add_light.glsl +++ b/manimlib/shaders/add_light.glsl @@ -13,11 +13,10 @@ vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords, vec3 to_light = light_coords - point; vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal); float dot_prod = dot(normalize(light_reflection), normalize(to_camera)); - // float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); - float shine = 2 * gloss * exp(-1 * pow(1 - dot_prod, 2)); + float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); float dp2 = dot(normalize(to_light), unit_normal); return vec4( - mix(0.5, 1.0, max(dp2, 0)) * mix(raw_color.rgb, vec3(1.0), shine), + ((dp2 + 3.0) / 4.0) * mix(raw_color.rgb, vec3(1.0), shine), raw_color.a ); } \ No newline at end of file diff --git a/manimlib/shaders/surface_frag.glsl b/manimlib/shaders/surface_frag.glsl index 3f0eb69c..6e71b36a 100644 --- a/manimlib/shaders/surface_frag.glsl +++ b/manimlib/shaders/surface_frag.glsl @@ -1,10 +1,6 @@ #version 330 -// uniform sampler2D Texture; - -// in vec2 v_im_coords; in vec4 v_color; - out vec4 frag_color; void main() { diff --git a/manimlib/shaders/surface_vert.glsl b/manimlib/shaders/surface_vert.glsl index 2a8284de..d8ed38b8 100644 --- a/manimlib/shaders/surface_vert.glsl +++ b/manimlib/shaders/surface_vert.glsl @@ -17,7 +17,7 @@ in float gloss; // out vec2 v_im_coords; out vec4 v_color; -// Analog of import for manim only +// These lines will get replaced #INSERT position_point_into_frame.glsl #INSERT get_gl_Position.glsl #INSERT add_light.glsl diff --git a/manimlib/shaders/textured_surface_frag.glsl b/manimlib/shaders/textured_surface_frag.glsl new file mode 100644 index 00000000..9ad76932 --- /dev/null +++ b/manimlib/shaders/textured_surface_frag.glsl @@ -0,0 +1,20 @@ +#version 330 + +uniform sampler2D Texture; +uniform vec3 light_source_position; + +in vec3 xyz_coords; +in vec3 v_normal; +in vec2 v_im_coords; +in float v_opacity; +in float v_gloss; + +out vec4 frag_color; + +#INSERT add_light.glsl + +void main() { + vec4 im_color = texture(Texture, v_im_coords); + frag_color = add_light(im_color, xyz_coords, normalize(v_normal), light_source_position, v_gloss); + 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 new file mode 100644 index 00000000..1b44b1f1 --- /dev/null +++ b/manimlib/shaders/textured_surface_vert.glsl @@ -0,0 +1,35 @@ +#version 330 + +uniform float aspect_ratio; +uniform float anti_alias_width; +uniform mat4 to_screen_space; +uniform float focal_distance; +uniform vec3 light_source_position; + +// uniform sampler2D Texture; + +in vec3 point; +in vec3 normal; +in vec2 im_coords; +in float opacity; +in float gloss; + +out vec3 xyz_coords; +out vec3 v_normal; +out vec2 v_im_coords; +out float v_opacity; +out float v_gloss; + + +// These lines will get replaced +#INSERT position_point_into_frame.glsl +#INSERT get_gl_Position.glsl + +void main(){ + xyz_coords = position_point_into_frame(point); + v_normal = position_point_into_frame(normal); + v_im_coords = im_coords; + v_opacity = opacity; + v_gloss = gloss; + gl_Position = get_gl_Position(xyz_coords); +} \ No newline at end of file