Merge branch '3b1b:master' into master

This commit is contained in:
Michael W
2021-12-01 08:42:17 +08:00
committed by GitHub
32 changed files with 423 additions and 163 deletions

View File

@ -10,7 +10,7 @@ def main():
print(f"ManimGL \033[32mv{__version__}\033[0m") print(f"ManimGL \033[32mv{__version__}\033[0m")
args = manimlib.config.parse_cli() args = manimlib.config.parse_cli()
if args.version and args.file == None: if args.version and args.file is None:
return return
if args.log_level: if args.log_level:
manimlib.logger.log.setLevel(args.log_level) manimlib.logger.log.setLevel(args.log_level)
@ -24,5 +24,6 @@ def main():
for scene in scenes: for scene in scenes:
scene.run() scene.run()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -174,16 +174,12 @@ class ShowSubmobjectsOneByOne(ShowIncreasingSubsets):
"int_func": np.ceil, "int_func": np.ceil,
} }
def __init__(self, group, **kwargs):
new_group = Group(*group)
super().__init__(new_group, **kwargs)
def update_submobject_list(self, index): def update_submobject_list(self, index):
# N = len(self.all_submobs) # N = len(self.all_submobs)
if index == 0: if index == 0:
self.mobject.set_submobjects([]) self.mobject.set_submobjects([])
else: else:
self.mobject.set_submobjects(self.all_submobs[index - 1]) self.mobject.set_submobjects([self.all_submobs[index - 1]])
# TODO, this is broken... # TODO, this is broken...

View File

@ -224,6 +224,8 @@ class FlashAround(VShowPassingFlash):
def __init__(self, mobject, **kwargs): def __init__(self, mobject, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
path = self.get_path(mobject) path = self.get_path(mobject)
if mobject.is_fixed_in_frame:
path.fix_in_frame()
path.insert_n_curves(self.n_inserted_curves) path.insert_n_curves(self.n_inserted_curves)
path.set_points(path.get_points_without_null_curves()) path.set_points(path.get_points_without_null_curves())
path.set_stroke(self.color, self.stroke_width) path.set_stroke(self.color, self.stroke_width)

View File

@ -1,4 +1,5 @@
import moderngl import moderngl
import math
from colour import Color from colour import Color
import OpenGL.GL as gl import OpenGL.GL as gl
@ -121,6 +122,15 @@ class CameraFrame(Mobject):
self.refresh_rotation_matrix() self.refresh_rotation_matrix()
return self return self
def get_theta(self):
return self.data["euler_angles"][0]
def get_phi(self):
return self.data["euler_angles"][1]
def get_gamma(self):
return self.data["euler_angles"][2]
def get_shape(self): def get_shape(self):
return (self.get_width(), self.get_height()) return (self.get_width(), self.get_height())
@ -139,6 +149,16 @@ class CameraFrame(Mobject):
def get_focal_distance(self): def get_focal_distance(self):
return self.focal_distance * self.get_height() return self.focal_distance * self.get_height()
def get_implied_camera_location(self):
theta, phi, gamma = self.get_euler_angles()
dist = self.get_focal_distance()
x, y, z = self.get_center()
return (
x + dist * math.sin(theta) * math.sin(phi),
y - dist * math.cos(theta) * math.sin(phi),
z + dist * math.cos(phi)
)
def interpolate(self, *args, **kwargs): def interpolate(self, *args, **kwargs):
super().interpolate(*args, **kwargs) super().interpolate(*args, **kwargs)
self.refresh_rotation_matrix() self.refresh_rotation_matrix()
@ -194,20 +214,30 @@ class Camera(object):
fbo = self.get_fbo(ctx, 0) fbo = self.get_fbo(ctx, 0)
else: else:
fbo = ctx.detect_framebuffer() fbo = ctx.detect_framebuffer()
self.ctx = ctx
self.fbo = fbo
self.set_ctx_blending()
# For multisample antialiasing # For multisample antialiasing
fbo_msaa = self.get_fbo(ctx, self.samples) fbo_msaa = self.get_fbo(ctx, self.samples)
fbo_msaa.use() fbo_msaa.use()
self.fbo_msaa = fbo_msaa
ctx.enable(moderngl.BLEND) def set_ctx_blending(self, enable=True):
ctx.blend_func = ( if enable:
self.ctx.enable(moderngl.BLEND)
else:
self.ctx.disable(moderngl.BLEND)
self.ctx.blend_func = (
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA, moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
moderngl.ONE, moderngl.ONE # moderngl.ONE, moderngl.ONE
) )
self.ctx = ctx def set_ctx_depth_test(self, enable=True):
self.fbo = fbo if enable:
self.fbo_msaa = fbo_msaa self.ctx.enable(moderngl.DEPTH_TEST)
else:
self.ctx.disable(moderngl.DEPTH_TEST)
def init_light_source(self): def init_light_source(self):
self.light_source = Point(self.light_source_position) self.light_source = Point(self.light_source_position)
@ -297,6 +327,9 @@ class Camera(object):
def get_frame_center(self): def get_frame_center(self):
return self.frame.get_center() return self.frame.get_center()
def get_location(self):
return self.frame.get_implied_camera_location()
def resize_frame_shape(self, fixed_dimension=0): def resize_frame_shape(self, fixed_dimension=0):
""" """
Changes frame_shape to match the aspect ratio Changes frame_shape to match the aspect ratio
@ -327,17 +360,11 @@ class Camera(object):
shader_wrapper = render_group["shader_wrapper"] shader_wrapper = render_group["shader_wrapper"]
shader_program = render_group["prog"] shader_program = render_group["prog"]
self.set_shader_uniforms(shader_program, shader_wrapper) self.set_shader_uniforms(shader_program, shader_wrapper)
self.update_depth_test(shader_wrapper) self.set_ctx_depth_test(shader_wrapper.depth_test)
render_group["vao"].render(int(shader_wrapper.render_primitive)) render_group["vao"].render(int(shader_wrapper.render_primitive))
if render_group["single_use"]: if render_group["single_use"]:
self.release_render_group(render_group) self.release_render_group(render_group)
def update_depth_test(self, shader_wrapper):
if shader_wrapper.depth_test:
self.ctx.enable(moderngl.DEPTH_TEST)
else:
self.ctx.disable(moderngl.DEPTH_TEST)
def get_render_group_list(self, mobject): def get_render_group_list(self, mobject):
try: try:
return self.static_mobject_to_render_group_list[id(mobject)] return self.static_mobject_to_render_group_list[id(mobject)]
@ -410,7 +437,7 @@ class Camera(object):
for name, path in shader_wrapper.texture_paths.items(): for name, path in shader_wrapper.texture_paths.items():
tid = self.get_texture_id(path) tid = self.get_texture_id(path)
shader[name].value = tid shader[name].value = tid
for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()): for name, value in it.chain(self.perspective_uniforms.items(), shader_wrapper.uniforms.items()):
try: try:
if isinstance(value, np.ndarray): if isinstance(value, np.ndarray):
value = tuple(value) value = tuple(value)
@ -427,14 +454,18 @@ class Camera(object):
anti_alias_width = self.anti_alias_width / (ph / fh) anti_alias_width = self.anti_alias_width / (ph / fh)
# Orient light # Orient light
rotation = frame.get_inverse_camera_rotation_matrix() rotation = frame.get_inverse_camera_rotation_matrix()
light_pos = self.light_source.get_location() offset = frame.get_center()
light_pos = np.dot(rotation, light_pos) light_pos = np.dot(
rotation, self.light_source.get_location() + offset
)
cam_pos = self.frame.get_implied_camera_location() # TODO
self.perspective_uniforms = { self.perspective_uniforms = {
"frame_shape": frame.get_shape(), "frame_shape": frame.get_shape(),
"anti_alias_width": anti_alias_width, "anti_alias_width": anti_alias_width,
"camera_center": tuple(frame.get_center()), "camera_offset": tuple(offset),
"camera_rotation": tuple(np.array(rotation).T.flatten()), "camera_rotation": tuple(np.array(rotation).T.flatten()),
"camera_position": tuple(cam_pos),
"light_source_position": tuple(light_pos), "light_source_position": tuple(light_pos),
"focal_distance": frame.get_focal_distance(), "focal_distance": frame.get_focal_distance(),
} }
@ -445,6 +476,8 @@ class Camera(object):
def get_texture_id(self, path): def get_texture_id(self, path):
if path not in self.path_to_texture: if path not in self.path_to_texture:
if self.n_textures == 15: # I have no clue why this is needed
self.n_textures += 1
tid = self.n_textures tid = self.n_textures
self.n_textures += 1 self.n_textures += 1
im = Image.open(path).convert("RGBA") im = Image.open(path).convert("RGBA")
@ -468,4 +501,5 @@ class Camera(object):
class ThreeDCamera(Camera): class ThreeDCamera(Camera):
CONFIG = { CONFIG = {
"samples": 4, "samples": 4,
"anti_alias_width": 0,
} }

View File

@ -5,6 +5,7 @@ import importlib
import os import os
import sys import sys
import yaml import yaml
from contextlib import contextmanager
from screeninfo import get_monitors from screeninfo import get_monitors
from manimlib.utils.config_ops import merge_dicts_recursively from manimlib.utils.config_ops import merge_dicts_recursively
@ -12,6 +13,9 @@ from manimlib.utils.init_config import init_customization
from manimlib.logger import log from manimlib.logger import log
__config_file__ = "custom_config.yml"
def parse_cli(): def parse_cli():
try: try:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -112,6 +116,12 @@ def parse_cli():
"in two comma separated values, e.g. \"3,6\", it will end" "in two comma separated values, e.g. \"3,6\", it will end"
"the rendering at the second value", "the rendering at the second value",
) )
parser.add_argument(
"-e", "--embed",
help="Takes a line number as an argument, and results"
"in the scene being called as if the line `self.embed()`"
"was inserted into the scene code at that line number."
)
parser.add_argument( parser.add_argument(
"-r", "--resolution", "-r", "--resolution",
help="Resolution, passed as \"WxH\", e.g. \"1920x1080\"", help="Resolution, passed as \"WxH\", e.g. \"1920x1080\"",
@ -162,14 +172,30 @@ def get_manim_dir():
def get_module(file_name): def get_module(file_name):
if file_name is None: if file_name is None:
return None return None
else: module_name = file_name.replace(os.sep, ".").replace(".py", "")
module_name = file_name.replace(os.sep, ".").replace(".py", "") spec = importlib.util.spec_from_file_location(module_name, file_name)
spec = importlib.util.spec_from_file_location(module_name, file_name) module = importlib.util.module_from_spec(spec)
module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module)
spec.loader.exec_module(module) return module
return module
@contextmanager
def insert_embed_line(file_name, lineno):
with open(file_name, 'r') as fp:
lines = fp.readlines()
line = lines[lineno - 1]
n_spaces = len(line) - len(line.lstrip())
lines.insert(lineno - 1, " " * n_spaces + "self.embed()\n")
alt_file = file_name.replace(".py", "_inserted_embed.py")
with open(alt_file, 'w') as fp:
fp.writelines(lines)
try:
yield alt_file
finally:
os.remove(alt_file)
__config_file__ = "custom_config.yml"
def get_custom_config(): def get_custom_config():
global __config_file__ global __config_file__
@ -197,9 +223,11 @@ def get_custom_config():
def check_temporary_storage(config): def check_temporary_storage(config):
if config["directories"]["temporary_storage"] == "" and sys.platform == "win32": if config["directories"]["temporary_storage"] == "" and sys.platform == "win32":
log.warning("You may be using Windows platform and have not specified the path of" log.warning(
"You may be using Windows platform and have not specified the path of"
" `temporary_storage`, which may cause OSError. So it is recommended" " `temporary_storage`, which may cause OSError. So it is recommended"
" to specify the `temporary_storage` in the config file (.yml)") " to specify the `temporary_storage` in the config file (.yml)"
)
def get_configuration(args): def get_configuration(args):
@ -229,8 +257,10 @@ def get_configuration(args):
elif not os.path.exists(__config_file__): elif not os.path.exists(__config_file__):
log.info(f"Using the default configuration file, which you can modify in `{global_defaults_file}`") log.info(f"Using the default configuration file, which you can modify in `{global_defaults_file}`")
log.info("If you want to create a local configuration file, you can create a file named" log.info(
f" `{__config_file__}`, or run `manimgl --config`") "If you want to create a local configuration file, you can create a file named"
f" `{__config_file__}`, or run `manimgl --config`"
)
custom_config = get_custom_config() custom_config = get_custom_config()
check_temporary_storage(custom_config) check_temporary_storage(custom_config)
@ -260,7 +290,12 @@ def get_configuration(args):
"quiet": args.quiet, "quiet": args.quiet,
} }
module = get_module(args.file) if args.embed is None:
module = get_module(args.file)
else:
with insert_embed_line(args.file, int(args.embed)) as alt_file:
module = get_module(alt_file)
config = { config = {
"module": module, "module": module,
"scene_names": args.scene_names, "scene_names": args.scene_names,
@ -282,7 +317,7 @@ def get_configuration(args):
mon_index = custom_config["window_monitor"] mon_index = custom_config["window_monitor"]
monitor = monitors[min(mon_index, len(monitors) - 1)] monitor = monitors[min(mon_index, len(monitors) - 1)]
window_width = monitor.width window_width = monitor.width
if not args.full_screen: if not (args.full_screen or custom_config["full_screen"]):
window_width //= 2 window_width //= 2
window_height = window_width * 9 // 16 window_height = window_width * 9 // 16
config["window_config"] = { config["window_config"] = {

View File

@ -34,6 +34,7 @@ style:
# the window on the monitor, e.g. "960,540" # the window on the monitor, e.g. "960,540"
window_position: UR window_position: UR
window_monitor: 0 window_monitor: 0
full_screen: False
# If break_into_partial_movies is set to True, then many small # If break_into_partial_movies is set to True, then many small
# files will be written corresponding to each Scene.play and # files will be written corresponding to each Scene.play and
# Scene.wait call, and these files will then be combined # Scene.wait call, and these files will then be combined

View File

@ -1,5 +1,6 @@
import inspect import inspect
import sys import sys
import copy
from manimlib.scene.scene import Scene from manimlib.scene.scene import Scene
from manimlib.config import get_custom_config from manimlib.config import get_custom_config
@ -38,7 +39,7 @@ def prompt_user_for_choice(scene_classes):
"\nScene Name or Number: " "\nScene Name or Number: "
) )
return [ return [
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str)-1] name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]
for split_str in user_input.replace(" ", "").split(",") for split_str in user_input.replace(" ", "").split(",")
] ]
except IndexError: except IndexError:
@ -67,6 +68,24 @@ def get_scene_config(config):
]) ])
def compute_total_frames(scene_class, scene_config):
"""
When a scene is being written to file, a copy of the scene is run with
skip_animations set to true so as to count how many frames it will require.
This allows for a total progress bar on rendering, and also allows runtime
errors to be exposed preemptively for long running scenes. The final frame
is saved by default, so that one can more quickly check that the last frame
looks as expected.
"""
pre_config = copy.deepcopy(scene_config)
pre_config["file_writer_config"]["write_to_movie"] = False
pre_config["file_writer_config"]["save_last_frame"] = True
pre_config["skip_animations"] = True
pre_scene = scene_class(**pre_config)
pre_scene.run()
return int(pre_scene.time * scene_config["camera_config"]["frame_rate"])
def get_scenes_to_render(scene_classes, scene_config, config): def get_scenes_to_render(scene_classes, scene_config, config):
if config["write_all"]: if config["write_all"]:
return [sc(**scene_config) for sc in scene_classes] return [sc(**scene_config) for sc in scene_classes]
@ -76,6 +95,9 @@ def get_scenes_to_render(scene_classes, scene_config, config):
found = False found = False
for scene_class in scene_classes: for scene_class in scene_classes:
if scene_class.__name__ == scene_name: if scene_class.__name__ == scene_name:
fw_config = scene_config["file_writer_config"]
if fw_config["write_to_movie"]:
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
scene = scene_class(**scene_config) scene = scene_class(**scene_config)
result.append(scene) result.append(scene)
found = True found = True

View File

@ -151,14 +151,14 @@ class CoordinateSystem():
else: else:
alpha = binary_search( alpha = binary_search(
function=lambda a: self.point_to_coords( function=lambda a: self.point_to_coords(
graph.point_from_proportion(a) graph.quick_point_from_proportion(a)
)[0], )[0],
target=x, target=x,
lower_bound=self.x_range[0], lower_bound=self.x_range[0],
upper_bound=self.x_range[1], upper_bound=self.x_range[1],
) )
if alpha is not None: if alpha is not None:
return graph.point_from_proportion(alpha) return graph.quick_point_from_proportion(alpha)
else: else:
return None return None

View File

@ -507,6 +507,7 @@ class Line(TipableVMobject):
def set_length(self, length, **kwargs): def set_length(self, length, **kwargs):
self.scale(length / self.get_length(), **kwargs) self.scale(length / self.get_length(), **kwargs)
return self
class DashedLine(Line): class DashedLine(Line):

View File

@ -43,10 +43,13 @@ class Mobject(object):
"opacity": 1, "opacity": 1,
"dim": 3, # TODO, get rid of this "dim": 3, # TODO, get rid of this
# Lighting parameters # Lighting parameters
# Positive gloss up to 1 makes it reflect the light. # ...
"gloss": 0.0, # Larger reflectiveness makes things brighter when facing the light
# Positive shadow up to 1 makes a side opposite the light darker "reflectiveness": 0.0,
# Larger shadow makes faces opposite the light darker
"shadow": 0.0, "shadow": 0.0,
# Makes parts bright where light gets reflected toward the camera
"gloss": 0.0,
# For shaders # For shaders
"shader_folder": "", "shader_folder": "",
"render_primitive": moderngl.TRIANGLE_STRIP, "render_primitive": moderngl.TRIANGLE_STRIP,
@ -82,11 +85,11 @@ class Mobject(object):
def __str__(self): def __str__(self):
return self.__class__.__name__ return self.__class__.__name__
def __add__(self, other : 'Mobject') -> 'Mobject': def __add__(self, other: 'Mobject') -> 'Mobject':
assert(isinstance(other, Mobject)) assert(isinstance(other, Mobject))
return self.get_group_class()(self, other) return self.get_group_class()(self, other)
def __mul__(self, other : 'int') -> 'Mobject': def __mul__(self, other: 'int') -> 'Mobject':
assert(isinstance(other, int)) assert(isinstance(other, int))
return self.replicate(other) return self.replicate(other)
@ -102,6 +105,7 @@ class Mobject(object):
"is_fixed_in_frame": float(self.is_fixed_in_frame), "is_fixed_in_frame": float(self.is_fixed_in_frame),
"gloss": self.gloss, "gloss": self.gloss,
"shadow": self.shadow, "shadow": self.shadow,
"reflectiveness": self.reflectiveness,
} }
def init_colors(self): def init_colors(self):
@ -153,6 +157,7 @@ class Mobject(object):
for mob in self.get_family(): for mob in self.get_family():
for key in mob.data: for key in mob.data:
mob.data[key] = mob.data[key][::-1] mob.data[key] = mob.data[key][::-1]
self.refresh_unit_normal()
return self return self
def apply_points_function(self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False): def apply_points_function(self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False):
@ -308,6 +313,11 @@ class Mobject(object):
self.assemble_family() self.assemble_family()
return self return self
def insert_submobject(self, index, new_submob):
self.submobjects.insert(index, new_submob)
self.assemble_family()
return self
def set_submobjects(self, submobject_list): def set_submobjects(self, submobject_list):
self.remove(*self.submobjects) self.remove(*self.submobjects)
self.add(*submobject_list) self.add(*submobject_list)
@ -394,6 +404,7 @@ class Mobject(object):
self.submobjects.sort(key=submob_func) self.submobjects.sort(key=submob_func)
else: else:
self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center())) self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
self.assemble_family()
return self return self
def shuffle(self, recurse=False): def shuffle(self, recurse=False):
@ -401,6 +412,7 @@ class Mobject(object):
for submob in self.submobjects: for submob in self.submobjects:
submob.shuffle(recurse=True) submob.shuffle(recurse=True)
random.shuffle(self.submobjects) random.shuffle(self.submobjects)
self.assemble_family()
return self return self
# Copying # Copying
@ -974,12 +986,12 @@ class Mobject(object):
def fade(self, darkness=0.5, recurse=True): def fade(self, darkness=0.5, recurse=True):
self.set_opacity(1.0 - darkness, recurse=recurse) self.set_opacity(1.0 - darkness, recurse=recurse)
def get_gloss(self): def get_reflectiveness(self):
return self.uniforms["gloss"] return self.uniforms["reflectiveness"]
def set_gloss(self, gloss, recurse=True): def set_reflectiveness(self, reflectiveness, recurse=True):
for mob in self.get_family(recurse): for mob in self.get_family(recurse):
mob.uniforms["gloss"] = gloss mob.uniforms["reflectiveness"] = reflectiveness
return self return self
def get_shadow(self): def get_shadow(self):
@ -990,6 +1002,14 @@ class Mobject(object):
mob.uniforms["shadow"] = shadow mob.uniforms["shadow"] = shadow
return self return self
def get_gloss(self):
return self.uniforms["gloss"]
def set_gloss(self, gloss, recurse=True):
for mob in self.get_family(recurse):
mob.uniforms["gloss"] = gloss
return self
# Background rectangle # Background rectangle
def add_background_rectangle(self, color=None, opacity=0.75, **kwargs): def add_background_rectangle(self, color=None, opacity=0.75, **kwargs):
@ -1371,11 +1391,13 @@ class Mobject(object):
@affects_shader_info_id @affects_shader_info_id
def fix_in_frame(self): def fix_in_frame(self):
self.uniforms["is_fixed_in_frame"] = 1.0 self.uniforms["is_fixed_in_frame"] = 1.0
self.is_fixed_in_frame = True
return self return self
@affects_shader_info_id @affects_shader_info_id
def unfix_from_frame(self): def unfix_from_frame(self):
self.uniforms["is_fixed_in_frame"] = 0.0 self.uniforms["is_fixed_in_frame"] = 0.0
self.is_fixed_in_frame = False
return self return self
@affects_shader_info_id @affects_shader_info_id
@ -1618,7 +1640,7 @@ class Group(Mobject):
Mobject.__init__(self, **kwargs) Mobject.__init__(self, **kwargs)
self.add(*mobjects) self.add(*mobjects)
def __add__(self, other : 'Mobject' or 'Group'): def __add__(self, other: 'Mobject' or 'Group'):
assert(isinstance(other, Mobject)) assert(isinstance(other, Mobject))
return self.add(other) return self.add(other)

View File

@ -200,12 +200,11 @@ class Laptop(VGroup):
class VideoIcon(SVGMobject): class VideoIcon(SVGMobject):
CONFIG = { CONFIG = {
"file_name": "video_icon",
"width": FRAME_WIDTH / 12., "width": FRAME_WIDTH / 12.,
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs) super().__init__(file_name="video_icon", **kwargs)
self.center() self.center()
self.set_width(self.width) self.set_width(self.width)
self.set_stroke(color=WHITE, width=0) self.set_stroke(color=WHITE, width=0)

View File

@ -1,4 +1,3 @@
import copy
import hashlib import hashlib
import os import os
import re import re

View File

@ -6,6 +6,7 @@ from manimlib.mobject.types.surface import SGroup
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Square from manimlib.mobject.geometry import Square
from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import z_to_vector from manimlib.utils.space_ops import z_to_vector
@ -14,9 +15,9 @@ from manimlib.utils.space_ops import compass_directions
class SurfaceMesh(VGroup): class SurfaceMesh(VGroup):
CONFIG = { CONFIG = {
"resolution": (21, 21), "resolution": (21, 11),
"stroke_width": 1, "stroke_width": 1,
"normal_nudge": 1e-3, "normal_nudge": 1e-2,
"depth_test": True, "depth_test": True,
"flat_stroke": False, "flat_stroke": False,
} }
@ -32,8 +33,11 @@ class SurfaceMesh(VGroup):
full_nu, full_nv = uv_surface.resolution full_nu, full_nv = uv_surface.resolution
part_nu, part_nv = self.resolution part_nu, part_nv = self.resolution
u_indices = np.linspace(0, full_nu, part_nu).astype(int) # 'indices' are treated as floats. Later, there will be
v_indices = np.linspace(0, full_nv, part_nv).astype(int) # an interpolation between the floor and ceiling of these
# indices
u_indices = np.linspace(0, full_nu - 1, part_nu)
v_indices = np.linspace(0, full_nv - 1, part_nv)
points, du_points, dv_points = uv_surface.get_surface_points_and_nudged_points() points, du_points, dv_points = uv_surface.get_surface_points_and_nudged_points()
normals = uv_surface.get_unit_normals() normals = uv_surface.get_unit_normals()
@ -42,12 +46,28 @@ class SurfaceMesh(VGroup):
for ui in u_indices: for ui in u_indices:
path = VMobject() path = VMobject()
full_ui = full_nv * ui # full_ui = full_nv * ui
path.set_points_smoothly(nudged_points[full_ui:full_ui + full_nv]) # path.set_points_smoothly(
# nudged_points[full_ui:full_ui + full_nv]
# )
low_ui = full_nv * int(math.floor(ui))
high_ui = full_nv * int(math.ceil(ui))
path.set_points_smoothly(interpolate(
nudged_points[low_ui:low_ui + full_nv],
nudged_points[high_ui:high_ui + full_nv],
ui % 1
))
self.add(path) self.add(path)
for vi in v_indices: for vi in v_indices:
path = VMobject() path = VMobject()
path.set_points_smoothly(nudged_points[vi::full_nv]) # path.set_points_smoothly(
# nudged_points[vi::full_nv]
# )
path.set_points_smoothly(interpolate(
nudged_points[int(math.floor(vi))::full_nv],
nudged_points[int(math.ceil(vi))::full_nv],
vi % 1
))
self.add(path) self.add(path)

View File

@ -17,6 +17,7 @@ class DotCloud(PMobject):
"color": GREY_C, "color": GREY_C,
"opacity": 1, "opacity": 1,
"radius": DEFAULT_DOT_RADIUS, "radius": DEFAULT_DOT_RADIUS,
"glow_factor": 0,
"shader_folder": "true_dot", "shader_folder": "true_dot",
"render_primitive": moderngl.POINTS, "render_primitive": moderngl.POINTS,
"shader_dtype": [ "shader_dtype": [
@ -36,6 +37,10 @@ class DotCloud(PMobject):
self.data["radii"] = np.zeros((1, 1)) self.data["radii"] = np.zeros((1, 1))
self.set_radius(self.radius) self.set_radius(self.radius)
def init_uniforms(self):
super().init_uniforms()
self.uniforms["glow_factor"] = self.glow_factor
def to_grid(self, n_rows, n_cols, n_layers=1, def to_grid(self, n_rows, n_cols, n_layers=1,
buff_ratio=None, buff_ratio=None,
h_buff_ratio=1.0, h_buff_ratio=1.0,
@ -85,6 +90,12 @@ class DotCloud(PMobject):
def get_radius(self): def get_radius(self):
return self.get_radii().max() return self.get_radii().max()
def set_glow_factor(self, glow_factor):
self.uniforms["glow_factor"] = glow_factor
def get_glow_factor(self):
return self.uniforms["glow_factor"]
def compute_bounding_box(self): def compute_bounding_box(self):
bb = super().compute_bounding_box() bb = super().compute_bounding_box()
radius = self.get_radius() radius = self.get_radius()
@ -98,8 +109,8 @@ class DotCloud(PMobject):
self.set_radii(scale_factor * self.get_radii()) self.set_radii(scale_factor * self.get_radii())
return self return self
def make_3d(self, gloss=0.5, shadow=0.2): def make_3d(self, reflectiveness=0.5, shadow=0.2):
self.set_gloss(gloss) self.set_reflectiveness(reflectiveness)
self.set_shadow(shadow) self.set_shadow(shadow)
self.apply_depth_test() self.apply_depth_test()
return self return self

View File

@ -20,7 +20,8 @@ class Surface(Mobject):
"resolution": (101, 101), "resolution": (101, 101),
"color": GREY, "color": GREY,
"opacity": 1.0, "opacity": 1.0,
"gloss": 0.3, "reflectiveness": 0.3,
"gloss": 0.1,
"shadow": 0.4, "shadow": 0.4,
"prefered_creation_axis": 1, "prefered_creation_axis": 1,
# For du and dv steps. Much smaller and numerical error # For du and dv steps. Much smaller and numerical error
@ -161,6 +162,11 @@ class Surface(Mobject):
tri_is[k::3] = tri_is[k::3][indices] tri_is[k::3] = tri_is[k::3][indices]
return self return self
def always_sort_to_camera(self, camera):
self.add_updater(lambda m: m.sort_faces_back_to_front(
camera.get_location() - self.get_center()
))
# For shaders # For shaders
def get_shader_data(self): def get_shader_data(self):
s_points, du_points, dv_points = self.get_surface_points_and_nudged_points() s_points, du_points, dv_points = self.get_surface_points_and_nudged_points()

View File

@ -12,6 +12,7 @@ from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points
from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import partial_quadratic_bezier_points from manimlib.utils.bezier import partial_quadratic_bezier_points
from manimlib.utils.color import rgb_to_hex from manimlib.utils.color import rgb_to_hex
@ -74,7 +75,6 @@ class VMobject(Mobject):
self.needs_new_triangulation = True self.needs_new_triangulation = True
self.triangulation = np.zeros(0, dtype='i4') self.triangulation = np.zeros(0, dtype='i4')
super().__init__(**kwargs) super().__init__(**kwargs)
self.refresh_unit_normal()
def get_group_class(self): def get_group_class(self):
return VGroup return VGroup
@ -135,6 +135,10 @@ class VMobject(Mobject):
mob.draw_stroke_behind_fill = background mob.draw_stroke_behind_fill = background
return self return self
def set_backstroke(self, color=BLACK, width=3, background=True):
self.set_stroke(color, width, background=background)
return self
def align_stroke_width_data_to_points(self, recurse=True): def align_stroke_width_data_to_points(self, recurse=True):
for mob in self.get_family(recurse): for mob in self.get_family(recurse):
mob.data["stroke_width"] = resize_with_interpolation( mob.data["stroke_width"] = resize_with_interpolation(
@ -150,6 +154,7 @@ class VMobject(Mobject):
stroke_rgba=None, stroke_rgba=None,
stroke_width=None, stroke_width=None,
stroke_background=True, stroke_background=True,
reflectiveness=None,
gloss=None, gloss=None,
shadow=None, shadow=None,
recurse=True): recurse=True):
@ -177,6 +182,8 @@ class VMobject(Mobject):
background=stroke_background, background=stroke_background,
) )
if reflectiveness is not None:
self.set_reflectiveness(reflectiveness, recurse=recurse)
if gloss is not None: if gloss is not None:
self.set_gloss(gloss, recurse=recurse) self.set_gloss(gloss, recurse=recurse)
if shadow is not None: if shadow is not None:
@ -185,10 +192,11 @@ class VMobject(Mobject):
def get_style(self): def get_style(self):
return { return {
"fill_rgba": self.data['fill_rgba'], "fill_rgba": self.data['fill_rgba'].copy(),
"stroke_rgba": self.data['stroke_rgba'], "stroke_rgba": self.data['stroke_rgba'].copy(),
"stroke_width": self.data['stroke_width'], "stroke_width": self.data['stroke_width'].copy(),
"stroke_background": self.draw_stroke_behind_fill, "stroke_background": self.draw_stroke_behind_fill,
"reflectiveness": self.get_reflectiveness(),
"gloss": self.get_gloss(), "gloss": self.get_gloss(),
"shadow": self.get_shadow(), "shadow": self.get_shadow(),
} }
@ -218,16 +226,17 @@ class VMobject(Mobject):
return self return self
def fade(self, darkness=0.5, recurse=True): def fade(self, darkness=0.5, recurse=True):
factor = 1.0 - darkness mobs = self.get_family() if recurse else [self]
self.set_fill( for mob in mobs:
opacity=factor * self.get_fill_opacity(), factor = 1.0 - darkness
recurse=False, mob.set_fill(
) opacity=factor * mob.get_fill_opacity(),
self.set_stroke( recurse=False,
opacity=factor * self.get_stroke_opacity(), )
recurse=False, mob.set_stroke(
) opacity=factor * mob.get_stroke_opacity(),
super().fade(darkness, recurse) recurse=False,
)
return self return self
def get_fill_colors(self): def get_fill_colors(self):
@ -277,9 +286,9 @@ class VMobject(Mobject):
return self.get_stroke_opacities()[0] return self.get_stroke_opacities()[0]
def get_color(self): def get_color(self):
if self.has_stroke(): if self.has_fill():
return self.get_stroke_color() return self.get_fill_color()
return self.get_fill_color() return self.get_stroke_color()
def has_stroke(self): def has_stroke(self):
return self.get_stroke_widths().any() and self.get_stroke_opacities().any() return self.get_stroke_widths().any() and self.get_stroke_opacities().any()
@ -504,10 +513,10 @@ class VMobject(Mobject):
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
remainder = len(points) % nppc remainder = len(points) % nppc
points = points[:len(points) - remainder] points = points[:len(points) - remainder]
return [ return (
points[i:i + nppc] points[i:i + nppc]
for i in range(0, len(points), nppc) for i in range(0, len(points), nppc)
] )
def get_bezier_tuples(self): def get_bezier_tuples(self):
return self.get_bezier_tuples_from_points(self.get_points()) return self.get_bezier_tuples_from_points(self.get_points())
@ -543,12 +552,35 @@ class VMobject(Mobject):
def get_num_curves(self): def get_num_curves(self):
return self.get_num_points() // self.n_points_per_curve return self.get_num_points() // self.n_points_per_curve
def point_from_proportion(self, alpha): def quick_point_from_proportion(self, alpha):
# Assumes all curves have the same length, so is inaccurate
num_curves = self.get_num_curves() num_curves = self.get_num_curves()
n, residue = integer_interpolate(0, num_curves, alpha) n, residue = integer_interpolate(0, num_curves, alpha)
curve_func = self.get_nth_curve_function(n) curve_func = self.get_nth_curve_function(n)
return curve_func(residue) return curve_func(residue)
def point_from_proportion(self, alpha):
if alpha <= 0:
return self.get_start()
elif alpha >= 1:
return self.get_end()
partials = [0]
for tup in self.get_bezier_tuples():
# Approximate length with straight line from start to end
arclen = get_norm(tup[0] - tup[-1])
partials.append(partials[-1] + arclen)
full = partials[-1]
if full == 0:
return self.get_start()
# First index where the partial lenth is more alpha times the full length
i = next(
(i for i, x in enumerate(partials) if x >= full * alpha),
len(partials) # Default
)
residue = inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha)
return self.get_nth_curve_function(i - 1)(residue)
def get_anchors_and_handles(self): def get_anchors_and_handles(self):
""" """
returns anchors1, handles, anchors2, returns anchors1, handles, anchors2,
@ -629,17 +661,19 @@ class VMobject(Mobject):
area_vect = self.get_area_vector() area_vect = self.get_area_vector()
area = get_norm(area_vect) area = get_norm(area_vect)
if area > 0: if area > 0:
return area_vect / area normal = area_vect / area
else: else:
points = self.get_points() points = self.get_points()
return get_unit_normal( normal = get_unit_normal(
points[1] - points[0], points[1] - points[0],
points[2] - points[1], points[2] - points[1],
) )
self.data["unit_normal"][:] = normal
return normal
def refresh_unit_normal(self): def refresh_unit_normal(self):
for mob in self.get_family(): for mob in self.get_family():
mob.data["unit_normal"][:] = mob.get_unit_normal(recompute=True) mob.get_unit_normal(recompute=True)
return self return self
# Alignment # Alignment
@ -701,7 +735,7 @@ class VMobject(Mobject):
if len(points) == 1: if len(points) == 1:
return np.repeat(points, nppc * n, 0) return np.repeat(points, nppc * n, 0)
bezier_groups = self.get_bezier_tuples_from_points(points) bezier_groups = list(self.get_bezier_tuples_from_points(points))
norms = np.array([ norms = np.array([
get_norm(bg[nppc - 1] - bg[0]) get_norm(bg[nppc - 1] - bg[0])
for bg in bezier_groups for bg in bezier_groups
@ -797,7 +831,7 @@ class VMobject(Mobject):
# how to send the points as to the vertex shader. # how to send the points as to the vertex shader.
# First triangles come directly from the points # First triangles come directly from the points
if normal_vector is None: if normal_vector is None:
normal_vector = self.get_unit_normal() normal_vector = self.get_unit_normal(recompute=True)
if not self.needs_new_triangulation: if not self.needs_new_triangulation:
return self.triangulation return self.triangulation

View File

@ -135,7 +135,7 @@ class Scene(object):
for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"): for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"):
local_ns[term] = getattr(self, term) local_ns[term] = getattr(self, term)
log.info("Tips: Now the embed iPython terminal is open. But you can't interact with" log.info("Tips: Now the embed iPython terminal is open. But you can't interact with"
" the window directly. To do so, you need to type `touch()` or `self.interact()`") " the window directly. To do so, you need to type `touch()` or `self.interact()`")
shell(local_ns=local_ns, stack_depth=2) shell(local_ns=local_ns, stack_depth=2)
# End scene when exiting an embed. # End scene when exiting an embed.
raise EndSceneEarlyException() raise EndSceneEarlyException()
@ -282,48 +282,42 @@ class Scene(object):
self.skip_time += self.time self.skip_time += self.time
# Methods associated with running animations # Methods associated with running animations
def get_time_progression(self, run_time, n_iterations=None, override_skip_animations=False): def get_time_progression(self, run_time, n_iterations=None, desc="", override_skip_animations=False):
if self.skip_animations and not override_skip_animations: if self.skip_animations and not override_skip_animations:
times = [run_time] return [run_time]
else: else:
step = 1 / self.camera.frame_rate step = 1 / self.camera.frame_rate
times = np.arange(0, run_time, step) times = np.arange(0, run_time, step)
time_progression = ProgressDisplay(
if self.file_writer.has_progress_display:
self.file_writer.set_progress_display_subdescription(desc)
return times
return ProgressDisplay(
times, times,
total=n_iterations, total=n_iterations,
leave=self.leave_progress_bars, leave=self.leave_progress_bars,
ascii=True if platform.system() == 'Windows' else None ascii=True if platform.system() == 'Windows' else None,
desc=desc,
) )
return time_progression
def get_run_time(self, animations): def get_run_time(self, animations):
return np.max([animation.run_time for animation in animations]) return np.max([animation.run_time for animation in animations])
def get_animation_time_progression(self, animations): def get_animation_time_progression(self, animations):
run_time = self.get_run_time(animations) run_time = self.get_run_time(animations)
time_progression = self.get_time_progression(run_time) description = f"{self.num_plays} {animations[0]}"
time_progression.set_description("".join([ if len(animations) > 1:
f"Animation {self.num_plays}: {animations[0]}", description += ", etc."
", etc." if len(animations) > 1 else "", time_progression = self.get_time_progression(run_time, desc=description)
]))
return time_progression return time_progression
def get_wait_time_progression(self, duration, stop_condition): def get_wait_time_progression(self, duration, stop_condition=None):
kw = {"desc": f"{self.num_plays} Waiting"}
if stop_condition is not None: if stop_condition is not None:
time_progression = self.get_time_progression( kw["n_iterations"] = -1 # So it doesn't show % progress
duration, kw["override_skip_animations"] = True
n_iterations=-1, # So it doesn't show % progress return self.get_time_progression(duration, **kw)
override_skip_animations=True
)
time_progression.set_description(
"Waiting for {}".format(stop_condition.__name__)
)
else:
time_progression = self.get_time_progression(duration)
time_progression.set_description(
"Waiting {}".format(self.num_plays)
)
return time_progression
def anims_from_play_args(self, *args, **kwargs): def anims_from_play_args(self, *args, **kwargs):
""" """
@ -488,13 +482,9 @@ class Scene(object):
time_progression.close() time_progression.close()
break break
self.unlock_mobject_data() self.unlock_mobject_data()
elif self.skip_animations:
# Do nothing
return self
else: else:
self.update_frame(duration) self.update_frame(duration)
n_frames = int(duration * self.camera.frame_rate) for n in self.get_wait_time_progression(duration):
for n in range(n_frames):
self.emit_frame() self.emit_frame()
return self return self

View File

@ -5,6 +5,7 @@ import subprocess as sp
import os import os
import sys import sys
import platform import platform
from tqdm import tqdm as ProgressDisplay
from manimlib.constants import FFMPEG_BIN from manimlib.constants import FFMPEG_BIN
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
@ -35,12 +36,15 @@ class SceneFileWriter(object):
"open_file_upon_completion": False, "open_file_upon_completion": False,
"show_file_location_upon_completion": False, "show_file_location_upon_completion": False,
"quiet": False, "quiet": False,
"total_frames": 0,
"progress_description_len": 35,
} }
def __init__(self, scene, **kwargs): def __init__(self, scene, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
self.scene = scene self.scene = scene
self.writing_process = None self.writing_process = None
self.has_progress_display = False
self.init_output_directories() self.init_output_directories()
self.init_audio() self.init_audio()
@ -205,15 +209,37 @@ class SceneFileWriter(object):
command += [self.temp_file_path] command += [self.temp_file_path]
self.writing_process = sp.Popen(command, stdin=sp.PIPE) self.writing_process = sp.Popen(command, stdin=sp.PIPE)
if self.total_frames > 0:
self.progress_display = ProgressDisplay(
range(self.total_frames),
leave=False,
ascii=True if platform.system() == 'Windows' else None,
desc="Full render: "
)
self.has_progress_display = True
def set_progress_display_subdescription(self, desc):
desc_len = self.progress_description_len
full_desc = f"Full render ({desc})"
if len(full_desc) > desc_len:
full_desc = full_desc[:desc_len - 4] + "...)"
else:
full_desc += " " * (desc_len - len(full_desc))
self.progress_display.set_description(full_desc)
def write_frame(self, camera): def write_frame(self, camera):
if self.write_to_movie: if self.write_to_movie:
raw_bytes = camera.get_raw_fbo_data() raw_bytes = camera.get_raw_fbo_data()
self.writing_process.stdin.write(raw_bytes) self.writing_process.stdin.write(raw_bytes)
if self.has_progress_display:
self.progress_display.update()
def close_movie_pipe(self): def close_movie_pipe(self):
self.writing_process.stdin.close() self.writing_process.stdin.close()
self.writing_process.wait() self.writing_process.wait()
self.writing_process.terminate() self.writing_process.terminate()
if self.has_progress_display:
self.progress_display.close()
shutil.move(self.temp_file_path, self.final_file_path) shutil.move(self.temp_file_path, self.final_file_path)
def combine_movie_files(self): def combine_movie_files(self):

View File

@ -5,7 +5,6 @@ class ThreeDScene(Scene):
CONFIG = { CONFIG = {
"camera_config": { "camera_config": {
"samples": 4, "samples": 4,
"anti_alias_width": 0,
} }
} }

View File

@ -1,6 +1,6 @@
uniform vec2 frame_shape; uniform vec2 frame_shape;
uniform float anti_alias_width; uniform float anti_alias_width;
uniform vec3 camera_center; uniform vec3 camera_offset;
uniform mat3 camera_rotation; uniform mat3 camera_rotation;
uniform float is_fixed_in_frame; uniform float is_fixed_in_frame;
uniform float focal_distance; uniform float focal_distance;

View File

@ -13,39 +13,56 @@ vec4 add_light(vec4 color,
vec3 point, vec3 point,
vec3 unit_normal, vec3 unit_normal,
vec3 light_coords, vec3 light_coords,
vec3 cam_coords,
float reflectiveness,
float gloss, float gloss,
float shadow){ float shadow){
if(gloss == 0.0 && shadow == 0.0) return color; if(reflectiveness == 0.0 && gloss == 0.0 && shadow == 0.0) return color;
float camera_distance = focal_distance; vec4 result = color;
// Assume everything has already been rotated such that camera is in the z-direction // Assume everything has already been rotated such that camera is in the z-direction
vec3 to_camera = vec3(0, 0, camera_distance) - point; // cam_coords = vec3(0, 0, focal_distance);
vec3 to_light = light_coords - point; vec3 to_camera = normalize(cam_coords - point);
vec3 to_light = normalize(light_coords - point);
// TODO, do we actually want this? It effectively treats surfaces as two-sided // Note, this effectively treats surfaces as two-sided
if(dot(to_camera,unit_normal) < 0){ // if(dot(to_camera, unit_normal) < 0) unit_normal *= -1;
unit_normal *= -1;
}
float light_to_normal = dot(to_light, unit_normal);
// When unit normal points towards light, brighten
float bright_factor = max(light_to_normal, 0) * reflectiveness;
// For glossy surface, add extra shine if light beam go towards camera
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal); vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
float dot_prod = dot(normalize(light_reflection), normalize(to_camera)); float light_to_cam = dot(light_reflection, to_camera);
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2));
float dp2 = dot(normalize(to_light), unit_normal); bright_factor += shine;
float darkening = mix(1, max(dp2, 0), shadow);
return vec4( result.rgb = mix(result.rgb, vec3(1.0), bright_factor);
darkening * mix(color.rgb, vec3(1.0), shine), if (light_to_normal < 0){
color.a // Darken
); result.rgb = mix(result.rgb, vec3(0.0), -light_to_normal * shadow);
}
// float darkening = mix(1, max(light_to_normal, 0), shadow);
// return vec4(
// darkening * mix(color.rgb, vec3(1.0), shine),
// color.a
// );
return result;
} }
vec4 finalize_color(vec4 color, vec4 finalize_color(vec4 color,
vec3 point, vec3 point,
vec3 unit_normal, vec3 unit_normal,
vec3 light_coords, vec3 light_coords,
vec3 cam_coords,
float reflectiveness,
float gloss, float gloss,
float shadow){ float shadow){
///// INSERT COLOR FUNCTION HERE ///// ///// INSERT COLOR FUNCTION HERE /////
// The line above may be replaced by arbitrary code snippets, as per // The line above may be replaced by arbitrary code snippets, as per
// the method Mobject.set_color_by_code // the method Mobject.set_color_by_code
return add_light(color, point, unit_normal, light_coords, gloss, shadow); return add_light(
color, point, unit_normal, light_coords, cam_coords,
reflectiveness, gloss, shadow
);
} }

View File

@ -1,5 +1,5 @@
// Assumes the following uniforms exist in the surrounding context: // Assumes the following uniforms exist in the surrounding context:
// uniform vec3 camera_center; // uniform vec3 camera_offset;
// uniform mat3 camera_rotation; // uniform mat3 camera_rotation;
vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){ vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){

View File

@ -1,6 +1,6 @@
// Assumes the following uniforms exist in the surrounding context: // Assumes the following uniforms exist in the surrounding context:
// uniform float is_fixed_in_frame; // uniform float is_fixed_in_frame;
// uniform vec3 camera_center; // uniform vec3 camera_offset;
// uniform mat3 camera_rotation; // uniform mat3 camera_rotation;
vec3 rotate_point_into_frame(vec3 point){ vec3 rotate_point_into_frame(vec3 point){
@ -15,5 +15,5 @@ vec3 position_point_into_frame(vec3 point){
if(bool(is_fixed_in_frame)){ if(bool(is_fixed_in_frame)){
return point; return point;
} }
return rotate_point_into_frame(point - camera_center); return rotate_point_into_frame(point - camera_offset);
} }

View File

@ -1,6 +1,8 @@
#version 330 #version 330
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
uniform float focal_distance; uniform float focal_distance;
@ -71,6 +73,8 @@ void main() {
xyz_coords, xyz_coords,
vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 1.0),
light_source_position, light_source_position,
camera_position,
reflectiveness,
gloss, gloss,
shadow shadow
); );

View File

@ -1,6 +1,8 @@
#version 330 #version 330
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
uniform float focal_distance; uniform float focal_distance;
@ -75,7 +77,7 @@ vec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, int max_steps, out float n_it
} }
z = z - step; z = z - step;
} }
n_iters -= clamp((threshold - curr_len) / (last_len - curr_len), 0.0, 1.0); n_iters -= log(curr_len) / log(threshold);
return z; return z;
} }
@ -118,7 +120,7 @@ void main() {
color = colors[i]; color = colors[i];
} }
} }
color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 5 * saturation_factor); color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 2 * saturation_factor);
if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){ if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){
color = vec4(0.0, 0.0, 0.0, 1.0); color = vec4(0.0, 0.0, 0.0, 1.0);
@ -151,6 +153,8 @@ void main() {
xyz_coords, xyz_coords,
vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 1.0),
light_source_position, light_source_position,
camera_position,
reflectiveness,
gloss, gloss,
shadow shadow
); );

View File

@ -3,7 +3,7 @@
#INSERT camera_uniform_declarations.glsl #INSERT camera_uniform_declarations.glsl
in vec4 color; in vec4 color;
in float fill_all; // Either 0 or 1e in float fill_all; // Either 0 or 1
in float uv_anti_alias_width; in float uv_anti_alias_width;
in vec3 xyz_coords; in vec3 xyz_coords;

View File

@ -11,6 +11,8 @@ uniform float focal_distance;
uniform float is_fixed_in_frame; uniform float is_fixed_in_frame;
// Needed for finalize_color // Needed for finalize_color
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
@ -44,6 +46,8 @@ void emit_vertex_wrapper(vec3 point, int index){
point, point,
v_global_unit_normal[index], v_global_unit_normal[index],
light_source_position, light_source_position,
camera_position,
reflectiveness,
gloss, gloss,
shadow shadow
); );

View File

@ -13,7 +13,9 @@ uniform float flat_stroke;
//Needed for lighting //Needed for lighting
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float joint_type; uniform float joint_type;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
@ -259,6 +261,8 @@ void main() {
xyz_coords, xyz_coords,
v_global_unit_normal[index_map[i]], v_global_unit_normal[index_map[i]],
light_source_position, light_source_position,
camera_position,
reflectiveness,
gloss, gloss,
shadow shadow
); );

View File

@ -1,6 +1,8 @@
#version 330 #version 330
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
uniform float focal_distance; uniform float focal_distance;
@ -19,6 +21,8 @@ void main() {
xyz_coords, xyz_coords,
normalize(v_normal), normalize(v_normal),
light_source_position, light_source_position,
camera_position,
reflectiveness,
gloss, gloss,
shadow shadow
); );

View File

@ -4,6 +4,8 @@ uniform sampler2D LightTexture;
uniform sampler2D DarkTexture; uniform sampler2D DarkTexture;
uniform float num_textures; uniform float num_textures;
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
uniform float focal_distance; uniform float focal_distance;
@ -36,6 +38,8 @@ void main() {
xyz_coords, xyz_coords,
normalize(v_normal), normalize(v_normal),
light_source_position, light_source_position,
camera_position,
reflectiveness,
gloss, gloss,
shadow shadow
); );

View File

@ -1,10 +1,13 @@
#version 330 #version 330
uniform vec3 light_source_position; uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss; uniform float gloss;
uniform float shadow; uniform float shadow;
uniform float anti_alias_width; uniform float anti_alias_width;
uniform float focal_distance; uniform float focal_distance;
uniform float glow_factor;
in vec4 color; in vec4 color;
in float radius; in float radius;
@ -22,14 +25,23 @@ void main() {
if (signed_dist > 0.5 * anti_alias_width){ if (signed_dist > 0.5 * anti_alias_width){
discard; discard;
} }
vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius))); frag_color = color;
frag_color = finalize_color( if(gloss > 0 || shadow > 0){
color, vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius)));
vec3(point.xy, 0.0), frag_color = finalize_color(
normal, frag_color,
light_source_position, vec3(point.xy, 0.0),
gloss, normal,
shadow light_source_position,
); camera_position,
reflectiveness,
gloss,
shadow
);
}
if(glow_factor > 0){
frag_color.a *= pow(1 - dist / radius, glow_factor);
}
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width); frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width);
} }

View File

@ -10,6 +10,15 @@ from manimlib.constants import OUT
from manimlib.constants import PI from manimlib.constants import PI
from manimlib.constants import TAU from manimlib.constants import TAU
from manimlib.utils.iterables import adjacent_pairs from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.simple_functions import clip
def cross(v1, v2):
return [
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0]
]
def get_norm(vect): def get_norm(vect):
@ -147,6 +156,15 @@ def z_to_vector(vector):
return rotation_matrix(angle, axis=axis) return rotation_matrix(angle, axis=axis)
def rotation_between_vectors(v1, v2):
if np.all(np.isclose(v1, v2)):
return np.identity(3)
return rotation_matrix(
angle=angle_between_vectors(v1, v2),
axis=normalize(np.cross(v1, v2))
)
def angle_of_vector(vector): def angle_of_vector(vector):
""" """
Returns polar coordinate theta when vector is project on xy plane Returns polar coordinate theta when vector is project on xy plane
@ -159,8 +177,7 @@ def angle_between_vectors(v1, v2):
Returns the angle between two 3D vectors. Returns the angle between two 3D vectors.
This angle will always be btw 0 and pi This angle will always be btw 0 and pi
""" """
diff = (angle_of_vector(v2) - angle_of_vector(v1)) % TAU return math.acos(clip(np.dot(normalize(v1), normalize(v2)), -1, 1))
return min(diff, TAU - diff)
def project_along_vector(point, vector): def project_along_vector(point, vector):
@ -186,14 +203,6 @@ def normalize_along_axis(array, axis, fall_back=None):
return array return array
def cross(v1, v2):
return np.array([
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0]
])
def get_unit_normal(v1, v2, tol=1e-6): def get_unit_normal(v1, v2, tol=1e-6):
v1 = normalize(v1) v1 = normalize(v1)
v2 = normalize(v2) v2 = normalize(v2)