mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 04:07:46 +00:00
commit
83a9217063
10 changed files with 273 additions and 231 deletions
|
|
@ -27,6 +27,7 @@ tex:
|
|||
# intermediate_filetype: "xdv"
|
||||
universal_import_line: "from manimlib.imports import *"
|
||||
style:
|
||||
font: "Consolas"
|
||||
background_color: "#333333"
|
||||
camera_qualities:
|
||||
low:
|
||||
|
|
|
|||
|
|
@ -310,22 +310,33 @@ class SurfaceExample(Scene):
|
|||
}
|
||||
|
||||
def construct(self):
|
||||
surface_text = Text("For 3d scenes, try using surfaces")
|
||||
surface_text.fix_in_frame()
|
||||
surface_text.to_edge(UP)
|
||||
self.add(surface_text)
|
||||
self.wait(0.1)
|
||||
|
||||
torus1 = Torus(r1=1, r2=1)
|
||||
torus2 = Torus(r1=3, r2=1)
|
||||
sphere = Sphere(radius=3, resolution=torus1.resolution)
|
||||
# You can texture a surface with up to two images, which will
|
||||
# be interpreted as the side towards the light, and away from
|
||||
# the light. These can be either urls, or paths to a local file
|
||||
# in whatever you've set as the iamge directory in
|
||||
# in whatever you've set as the image directory in
|
||||
# the custom_defaults.yml file
|
||||
|
||||
# day_texture = "EarthTextureMap"
|
||||
# night_texture = "NightEarthTextureMap"
|
||||
day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
|
||||
night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg"
|
||||
|
||||
surfaces = [
|
||||
TexturedSurface(surface, day_texture, night_texture)
|
||||
for surface in [sphere, torus1, torus2]
|
||||
]
|
||||
|
||||
for mob in surfaces:
|
||||
mob.shift(IN)
|
||||
mob.mesh = SurfaceMesh(mob)
|
||||
mob.mesh.set_stroke(BLUE, 1, opacity=0.5)
|
||||
|
||||
|
|
@ -365,12 +376,23 @@ class SurfaceExample(Scene):
|
|||
frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
|
||||
|
||||
# Play around with where the light is
|
||||
light_text = Text("You can move around the light source")
|
||||
light_text.move_to(surface_text)
|
||||
light_text.fix_in_frame()
|
||||
|
||||
self.play(FadeTransform(surface_text, light_text))
|
||||
light = self.camera.light_source
|
||||
self.add(light)
|
||||
light.save_state()
|
||||
self.play(light.move_to, 3 * IN, run_time=5)
|
||||
self.play(light.shift, 10 * OUT, run_time=5)
|
||||
self.wait(4)
|
||||
|
||||
drag_text = Text("Try clicking and dragging while pressing d")
|
||||
drag_text.move_to(light_text)
|
||||
drag_text.fix_in_frame()
|
||||
|
||||
self.play(FadeTransform(light_text, drag_text))
|
||||
self.wait()
|
||||
|
||||
|
||||
# See https://github.com/3b1b/videos for many, many more
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ class CameraFrame(Mobject):
|
|||
return self.set_rotation(phi=phi)
|
||||
|
||||
def set_gamma(self, gamma):
|
||||
return self.set_rotation(phi=phi)
|
||||
return self.set_rotation(gamma=gamma)
|
||||
|
||||
def increment_theta(self, dtheta):
|
||||
return self.set_rotation(theta=self.euler_angles[0] + dtheta)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ from manimlib.scene.vector_space_scene import *
|
|||
from manimlib.utils.bezier import *
|
||||
from manimlib.utils.color import *
|
||||
from manimlib.utils.config_ops import *
|
||||
from manimlib.utils.customization import *
|
||||
from manimlib.utils.debug import *
|
||||
from manimlib.utils.directories import *
|
||||
from manimlib.utils.images import *
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
from functools import reduce
|
||||
import copy
|
||||
import itertools as it
|
||||
import operator as op
|
||||
import random
|
||||
import sys
|
||||
import moderngl
|
||||
|
|
@ -31,9 +29,6 @@ from manimlib.shader_wrapper import ShaderWrapper
|
|||
from manimlib.shader_wrapper import get_colormap_code
|
||||
|
||||
|
||||
# TODO: Explain array_attrs
|
||||
# TODO: Incorporate shader defaults
|
||||
|
||||
class Mobject(object):
|
||||
"""
|
||||
Mathematical Object
|
||||
|
|
@ -146,9 +141,12 @@ class Mobject(object):
|
|||
about_point = self.get_bounding_box_point(about_edge)
|
||||
|
||||
for mob in self.get_family():
|
||||
arrs = [mob.get_points()]
|
||||
arrs = []
|
||||
if mob.has_points():
|
||||
arrs.append(mob.get_points())
|
||||
if works_on_bounding_box:
|
||||
arrs.append(mob.get_bounding_box())
|
||||
|
||||
for arr in arrs:
|
||||
if about_point is None:
|
||||
arr[:] = func(arr)
|
||||
|
|
@ -298,6 +296,84 @@ class Mobject(object):
|
|||
self.set_submobjects(list_update(self.submobjects, mobject_attrs))
|
||||
return self
|
||||
|
||||
# Submobject organization
|
||||
|
||||
def arrange(self, direction=RIGHT, center=True, **kwargs):
|
||||
for m1, m2 in zip(self.submobjects, self.submobjects[1:]):
|
||||
m2.next_to(m1, direction, **kwargs)
|
||||
if center:
|
||||
self.center()
|
||||
return self
|
||||
|
||||
def arrange_in_grid(self, n_rows=None, n_cols=None,
|
||||
buff=None,
|
||||
h_buff=None,
|
||||
v_buff=None,
|
||||
buff_ratio=None,
|
||||
h_buff_ratio=0.5,
|
||||
v_buff_ratio=0.5,
|
||||
aligned_edge=ORIGIN,
|
||||
fill_rows_first=True):
|
||||
submobs = self.submobjects
|
||||
if n_rows is None and n_cols is None:
|
||||
n_rows = int(np.sqrt(len(submobs)))
|
||||
if n_rows is None:
|
||||
n_rows = len(submobs) // n_cols
|
||||
if n_cols is None:
|
||||
n_cols = len(submobs) // n_rows
|
||||
|
||||
if buff is not None:
|
||||
h_buff = buff
|
||||
v_buff = buff
|
||||
else:
|
||||
if buff_ratio is not None:
|
||||
v_buff_ratio = buff_ratio
|
||||
h_buff_ratio = buff_ratio
|
||||
if h_buff is None:
|
||||
h_buff = h_buff_ratio * self[0].get_width()
|
||||
if v_buff is None:
|
||||
v_buff = v_buff_ratio * self[0].get_height()
|
||||
|
||||
x_unit = h_buff + max([sm.get_width() for sm in submobs])
|
||||
y_unit = v_buff + max([sm.get_height() for sm in submobs])
|
||||
|
||||
for index, sm in enumerate(submobs):
|
||||
if fill_rows_first:
|
||||
x, y = index % n_cols, index // n_cols
|
||||
else:
|
||||
x, y = index // n_rows, index % n_rows
|
||||
sm.move_to(ORIGIN, aligned_edge)
|
||||
sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN)
|
||||
self.center()
|
||||
return self
|
||||
|
||||
def get_grid(self, n_rows, n_cols, height=None, **kwargs):
|
||||
"""
|
||||
Returns a new mobject containing multiple copies of this one
|
||||
arranged in a grid
|
||||
"""
|
||||
grid = self.get_group_class()(
|
||||
*(self.copy() for n in range(n_rows * n_cols))
|
||||
)
|
||||
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
|
||||
if height is not None:
|
||||
grid.set_height(height)
|
||||
return grid
|
||||
|
||||
def sort(self, point_to_num_func=lambda p: p[0], submob_func=None):
|
||||
if submob_func is not None:
|
||||
self.submobjects.sort(key=submob_func)
|
||||
else:
|
||||
self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
|
||||
return self
|
||||
|
||||
def shuffle(self, recurse=False):
|
||||
if recurse:
|
||||
for submob in self.submobjects:
|
||||
submob.shuffle(recurse=True)
|
||||
random.shuffle(self.submobjects)
|
||||
return self
|
||||
|
||||
# Copying
|
||||
|
||||
def copy(self):
|
||||
|
|
@ -373,16 +449,16 @@ class Mobject(object):
|
|||
self.has_updaters = False
|
||||
self.updating_suspended = False
|
||||
|
||||
def update(self, dt=0, recursive=True):
|
||||
def update(self, dt=0, recurse=True):
|
||||
if not self.has_updaters or self.updating_suspended:
|
||||
return self
|
||||
for updater in self.time_based_updaters:
|
||||
updater(self, dt)
|
||||
for updater in self.non_time_updaters:
|
||||
updater(self)
|
||||
if recursive:
|
||||
if recurse:
|
||||
for submob in self.submobjects:
|
||||
submob.update(dt, recursive)
|
||||
submob.update(dt, recurse)
|
||||
return self
|
||||
|
||||
def get_time_based_updaters(self):
|
||||
|
|
@ -419,13 +495,13 @@ class Mobject(object):
|
|||
updater_list.remove(update_function)
|
||||
return self
|
||||
|
||||
def clear_updaters(self, recursive=True):
|
||||
def clear_updaters(self, recurse=True):
|
||||
self.time_based_updaters = []
|
||||
self.non_time_updaters = []
|
||||
if recursive:
|
||||
if recurse:
|
||||
for submob in self.submobjects:
|
||||
submob.clear_updaters()
|
||||
self.suspend_updating(recursive)
|
||||
self.suspend_updating(recurse)
|
||||
return self
|
||||
|
||||
def match_updaters(self, mobject):
|
||||
|
|
@ -434,22 +510,22 @@ class Mobject(object):
|
|||
self.add_updater(updater)
|
||||
return self
|
||||
|
||||
def suspend_updating(self, recursive=True):
|
||||
def suspend_updating(self, recurse=True):
|
||||
self.updating_suspended = True
|
||||
if recursive:
|
||||
if recurse:
|
||||
for submob in self.submobjects:
|
||||
submob.suspend_updating(recursive)
|
||||
submob.suspend_updating(recurse)
|
||||
return self
|
||||
|
||||
def resume_updating(self, recursive=True, call_updater=True):
|
||||
def resume_updating(self, recurse=True, call_updater=True):
|
||||
self.updating_suspended = False
|
||||
if recursive:
|
||||
if recurse:
|
||||
for submob in self.submobjects:
|
||||
submob.resume_updating(recursive)
|
||||
submob.resume_updating(recurse)
|
||||
for parent in self.parents:
|
||||
parent.resume_updating(recursive=False, call_updater=False)
|
||||
parent.resume_updating(recurse=False, call_updater=False)
|
||||
if call_updater:
|
||||
self.update(dt=0, recursive=recursive)
|
||||
self.update(dt=0, recurse=recurse)
|
||||
return self
|
||||
|
||||
def refresh_has_updater_status(self):
|
||||
|
|
@ -744,30 +820,6 @@ class Mobject(object):
|
|||
self.shift(start - curr_start)
|
||||
return self
|
||||
|
||||
# Background rectangle
|
||||
|
||||
def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs):
|
||||
# TODO, this does not behave well when the mobject has points,
|
||||
# since it gets displayed on top
|
||||
from manimlib.mobject.shape_matchers import BackgroundRectangle
|
||||
self.background_rectangle = BackgroundRectangle(
|
||||
self, color=color,
|
||||
fill_opacity=opacity,
|
||||
**kwargs
|
||||
)
|
||||
self.add_to_back(self.background_rectangle)
|
||||
return self
|
||||
|
||||
def add_background_rectangle_to_submobjects(self, **kwargs):
|
||||
for submobject in self.submobjects:
|
||||
submobject.add_background_rectangle(**kwargs)
|
||||
return self
|
||||
|
||||
def add_background_rectangle_to_family_members_with_points(self, **kwargs):
|
||||
for mob in self.family_members_with_points():
|
||||
mob.add_background_rectangle(**kwargs)
|
||||
return self
|
||||
|
||||
# Color functions
|
||||
|
||||
def set_rgba_array(self, color=None, opacity=None, name="rgbas", recurse=True):
|
||||
|
|
@ -857,6 +909,30 @@ class Mobject(object):
|
|||
mob.uniforms["shadow"] = shadow
|
||||
return self
|
||||
|
||||
# Background rectangle
|
||||
|
||||
def add_background_rectangle(self, color=None, opacity=0.75, **kwargs):
|
||||
# TODO, this does not behave well when the mobject has points,
|
||||
# since it gets displayed on top
|
||||
from manimlib.mobject.shape_matchers import BackgroundRectangle
|
||||
self.background_rectangle = BackgroundRectangle(
|
||||
self, color=color,
|
||||
fill_opacity=opacity,
|
||||
**kwargs
|
||||
)
|
||||
self.add_to_back(self.background_rectangle)
|
||||
return self
|
||||
|
||||
def add_background_rectangle_to_submobjects(self, **kwargs):
|
||||
for submobject in self.submobjects:
|
||||
submobject.add_background_rectangle(**kwargs)
|
||||
return self
|
||||
|
||||
def add_background_rectangle_to_family_members_with_points(self, **kwargs):
|
||||
for mob in self.family_members_with_points():
|
||||
mob.add_background_rectangle(**kwargs)
|
||||
return self
|
||||
|
||||
# Getters
|
||||
|
||||
def get_bounding_box_point(self, direction):
|
||||
|
|
@ -1037,84 +1113,6 @@ class Mobject(object):
|
|||
def get_group_class(self):
|
||||
return Group
|
||||
|
||||
# Submobject organization
|
||||
|
||||
def arrange(self, direction=RIGHT, center=True, **kwargs):
|
||||
for m1, m2 in zip(self.submobjects, self.submobjects[1:]):
|
||||
m2.next_to(m1, direction, **kwargs)
|
||||
if center:
|
||||
self.center()
|
||||
return self
|
||||
|
||||
def arrange_in_grid(self, n_rows=None, n_cols=None,
|
||||
buff=None,
|
||||
h_buff=None,
|
||||
v_buff=None,
|
||||
buff_ratio=None,
|
||||
h_buff_ratio=0.5,
|
||||
v_buff_ratio=0.5,
|
||||
aligned_edge=ORIGIN,
|
||||
fill_rows_first=True):
|
||||
submobs = self.submobjects
|
||||
if n_rows is None and n_cols is None:
|
||||
n_rows = int(np.sqrt(len(submobs)))
|
||||
if n_rows is None:
|
||||
n_rows = len(submobs) // n_cols
|
||||
if n_cols is None:
|
||||
n_cols = len(submobs) // n_rows
|
||||
|
||||
if buff is not None:
|
||||
h_buff = buff
|
||||
v_buff = buff
|
||||
else:
|
||||
if buff_ratio is not None:
|
||||
v_buff_ratio = buff_ratio
|
||||
h_buff_ratio = buff_ratio
|
||||
if h_buff is None:
|
||||
h_buff = h_buff_ratio * self[0].get_width()
|
||||
if v_buff is None:
|
||||
v_buff = v_buff_ratio * self[0].get_height()
|
||||
|
||||
x_unit = h_buff + max([sm.get_width() for sm in submobs])
|
||||
y_unit = v_buff + max([sm.get_height() for sm in submobs])
|
||||
|
||||
for index, sm in enumerate(submobs):
|
||||
if fill_rows_first:
|
||||
x, y = index % n_cols, index // n_cols
|
||||
else:
|
||||
x, y = index // n_rows, index % n_rows
|
||||
sm.move_to(ORIGIN, aligned_edge)
|
||||
sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN)
|
||||
self.center()
|
||||
return self
|
||||
|
||||
def get_grid(self, n_rows, n_cols, height=None, **kwargs):
|
||||
"""
|
||||
Returns a new mobject containing multiple copies of this one
|
||||
arranged in a grid
|
||||
"""
|
||||
grid = self.get_group_class()(
|
||||
*(self.copy() for n in range(n_rows * n_cols))
|
||||
)
|
||||
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
|
||||
if height is not None:
|
||||
grid.set_height(height)
|
||||
return grid
|
||||
|
||||
def sort(self, point_to_num_func=lambda p: p[0], submob_func=None):
|
||||
if submob_func is not None:
|
||||
self.submobjects.sort(key=submob_func)
|
||||
else:
|
||||
self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
|
||||
return self
|
||||
|
||||
def shuffle(self, recurse=False):
|
||||
if recurse:
|
||||
for submob in self.submobjects:
|
||||
submob.shuffle(recurse=True)
|
||||
random.shuffle(self.submobjects)
|
||||
return self
|
||||
|
||||
# Alignment
|
||||
|
||||
def align_data_and_family(self, mobject):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from manimlib.mobject.geometry import Rectangle
|
|||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.color import Color
|
||||
from manimlib.utils.customization import get_customization
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
|
||||
|
||||
|
|
@ -23,15 +24,16 @@ class SurroundingRectangle(Rectangle):
|
|||
|
||||
class BackgroundRectangle(SurroundingRectangle):
|
||||
CONFIG = {
|
||||
"color": BLACK,
|
||||
"stroke_width": 0,
|
||||
"stroke_opacity": 0,
|
||||
"fill_opacity": 0.75,
|
||||
"buff": 0
|
||||
}
|
||||
|
||||
def __init__(self, mobject, **kwargs):
|
||||
SurroundingRectangle.__init__(self, mobject, **kwargs)
|
||||
def __init__(self, mobject, color=None, **kwargs):
|
||||
if color is None:
|
||||
color = get_customization()['style']['background_color']
|
||||
SurroundingRectangle.__init__(self, mobject, color=color, **kwargs)
|
||||
self.original_fill_opacity = self.fill_opacity
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import cairo
|
|||
from manimlib.constants import *
|
||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.customization import get_customization
|
||||
from manimlib.utils.directories import get_text_dir
|
||||
|
||||
|
||||
|
|
@ -135,7 +136,7 @@ class Text(SVGMobject):
|
|||
settings = self.font + self.slant + self.weight
|
||||
settings += str(self.t2f) + str(self.t2s) + str(self.t2w)
|
||||
settings += str(self.lsh) + str(self.size)
|
||||
id_str = self.text+settings
|
||||
id_str = self.text + settings
|
||||
hasher = hashlib.sha256()
|
||||
hasher.update(id_str.encode())
|
||||
return hasher.hexdigest()[:16]
|
||||
|
|
@ -192,11 +193,11 @@ class Text(SVGMobject):
|
|||
lsh = self.lsh * 10
|
||||
|
||||
if self.font == '':
|
||||
print(NOT_SETTING_FONT_MSG)
|
||||
self.font = get_customization()['style']['font']
|
||||
|
||||
dir_name = get_text_dir()
|
||||
hash_name = self.text2hash()
|
||||
file_name = os.path.join(dir_name, hash_name) +'.svg'
|
||||
file_name = os.path.join(dir_name, hash_name) + '.svg'
|
||||
if os.path.exists(file_name):
|
||||
return file_name
|
||||
|
||||
|
|
|
|||
|
|
@ -90,13 +90,6 @@ class VMobject(Mobject):
|
|||
"unit_normal": np.zeros((1, 3))
|
||||
})
|
||||
|
||||
def set_points(self, points):
|
||||
old_points = self.get_points()
|
||||
super().set_points(points)
|
||||
if not np.all(points == old_points):
|
||||
self.refresh_triangulation()
|
||||
return self
|
||||
|
||||
# Colors
|
||||
def init_colors(self):
|
||||
self.set_fill(
|
||||
|
|
@ -458,19 +451,6 @@ class VMobject(Mobject):
|
|||
self.resize_data(len(self.get_points() - 1))
|
||||
self.append_points(new_points)
|
||||
|
||||
# TODO, how to be smart about tangents here?
|
||||
def apply_function(self, function):
|
||||
Mobject.apply_function(self, function)
|
||||
if self.make_smooth_after_applying_functions:
|
||||
self.make_smooth()
|
||||
self.refresh_triangulation()
|
||||
return self
|
||||
|
||||
def flip(self, *args, **kwargs):
|
||||
super().flip(*args, **kwargs)
|
||||
self.refresh_unit_normal()
|
||||
self.refresh_triangulation()
|
||||
|
||||
#
|
||||
def consider_points_equals(self, p0, p1):
|
||||
return get_norm(p1 - p0) < self.tolerance_for_point_equality
|
||||
|
|
@ -761,6 +741,98 @@ class VMobject(Mobject):
|
|||
vmob.pointwise_become_partial(self, a, b)
|
||||
return vmob
|
||||
|
||||
# Related to triangulation
|
||||
|
||||
def refresh_triangulation(self):
|
||||
for mob in self.get_family():
|
||||
mob.needs_new_triangulation = True
|
||||
return self
|
||||
|
||||
def get_triangulation(self, normal_vector=None):
|
||||
# Figure out how to triangulate the interior to know
|
||||
# how to send the points as to the vertex shader.
|
||||
# First triangles come directly from the points
|
||||
if normal_vector is None:
|
||||
normal_vector = self.get_unit_normal()
|
||||
|
||||
if not self.needs_new_triangulation:
|
||||
return self.triangulation
|
||||
|
||||
points = self.get_points()
|
||||
|
||||
if len(points) <= 1:
|
||||
self.triangulation = np.zeros(0, dtype='i4')
|
||||
self.needs_new_triangulation = False
|
||||
return self.triangulation
|
||||
|
||||
# Rotate points such that unit normal vector is OUT
|
||||
# TODO, 99% of the time this does nothing. Do a check for that?
|
||||
points = np.dot(points, z_to_vector(normal_vector))
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
|
||||
b0s = points[0::3]
|
||||
b1s = points[1::3]
|
||||
b2s = points[2::3]
|
||||
v01s = b1s - b0s
|
||||
v12s = b2s - b1s
|
||||
|
||||
crosses = cross2d(v01s, v12s)
|
||||
convexities = np.sign(crosses)
|
||||
|
||||
atol = self.tolerance_for_point_equality
|
||||
end_of_loop = np.zeros(len(b0s), dtype=bool)
|
||||
end_of_loop[:-1] = (np.abs(b2s[:-1] - b0s[1:]) > atol).any(1)
|
||||
end_of_loop[-1] = True
|
||||
|
||||
concave_parts = convexities < 0
|
||||
|
||||
# These are the vertices to which we'll apply a polygon triangulation
|
||||
inner_vert_indices = np.hstack([
|
||||
indices[0::3],
|
||||
indices[1::3][concave_parts],
|
||||
indices[2::3][end_of_loop],
|
||||
])
|
||||
inner_vert_indices.sort()
|
||||
rings = np.arange(1, len(inner_vert_indices) + 1)[inner_vert_indices % 3 == 2]
|
||||
|
||||
# Triangulate
|
||||
inner_verts = points[inner_vert_indices]
|
||||
inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)]
|
||||
|
||||
tri_indices = np.hstack([indices, inner_tri_indices])
|
||||
self.triangulation = tri_indices
|
||||
self.needs_new_triangulation = False
|
||||
return tri_indices
|
||||
|
||||
def triggers_refreshed_triangulation(func):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
old_points = self.get_points()
|
||||
func(self, *args, **kwargs)
|
||||
if not np.all(self.get_points() == old_points):
|
||||
self.refresh_triangulation()
|
||||
self.refresh_unit_normal()
|
||||
return wrapper
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def set_points(self, points):
|
||||
super().set_points(points)
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def set_data(self, data):
|
||||
super().set_data(data)
|
||||
|
||||
# TODO, how to be smart about tangents here?
|
||||
@triggers_refreshed_triangulation
|
||||
def apply_function(self, function):
|
||||
super().apply_function(function)
|
||||
if self.make_smooth_after_applying_functions:
|
||||
self.make_smooth()
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def flip(self, *args, **kwargs):
|
||||
super().flip(*args, **kwargs)
|
||||
|
||||
# For shaders
|
||||
def init_shader_data(self):
|
||||
self.fill_data = np.zeros(0, dtype=self.fill_dtype)
|
||||
|
|
@ -849,67 +921,6 @@ class VMobject(Mobject):
|
|||
|
||||
return self.stroke_data
|
||||
|
||||
def refresh_triangulation(self):
|
||||
for mob in self.get_family():
|
||||
mob.needs_new_triangulation = True
|
||||
return self
|
||||
|
||||
def get_triangulation(self, normal_vector=None):
|
||||
# Figure out how to triangulate the interior to know
|
||||
# how to send the points as to the vertex shader.
|
||||
# First triangles come directly from the points
|
||||
if normal_vector is None:
|
||||
normal_vector = self.get_unit_normal()
|
||||
|
||||
if not self.needs_new_triangulation:
|
||||
return self.triangulation
|
||||
|
||||
points = self.get_points()
|
||||
|
||||
if len(points) <= 1:
|
||||
self.triangulation = np.zeros(0, dtype='i4')
|
||||
self.needs_new_triangulation = False
|
||||
return self.triangulation
|
||||
|
||||
# Rotate points such that unit normal vector is OUT
|
||||
# TODO, 99% of the time this does nothing. Do a check for that?
|
||||
points = np.dot(points, z_to_vector(normal_vector))
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
|
||||
b0s = points[0::3]
|
||||
b1s = points[1::3]
|
||||
b2s = points[2::3]
|
||||
v01s = b1s - b0s
|
||||
v12s = b2s - b1s
|
||||
|
||||
crosses = cross2d(v01s, v12s)
|
||||
convexities = np.sign(crosses)
|
||||
|
||||
atol = self.tolerance_for_point_equality
|
||||
end_of_loop = np.zeros(len(b0s), dtype=bool)
|
||||
end_of_loop[:-1] = (np.abs(b2s[:-1] - b0s[1:]) > atol).any(1)
|
||||
end_of_loop[-1] = True
|
||||
|
||||
concave_parts = convexities < 0
|
||||
|
||||
# These are the vertices to which we'll apply a polygon triangulation
|
||||
inner_vert_indices = np.hstack([
|
||||
indices[0::3],
|
||||
indices[1::3][concave_parts],
|
||||
indices[2::3][end_of_loop],
|
||||
])
|
||||
inner_vert_indices.sort()
|
||||
rings = np.arange(1, len(inner_vert_indices) + 1)[inner_vert_indices % 3 == 2]
|
||||
|
||||
# Triangulate
|
||||
inner_verts = points[inner_vert_indices]
|
||||
inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)]
|
||||
|
||||
tri_indices = np.hstack([indices, inner_tri_indices])
|
||||
self.triangulation = tri_indices
|
||||
self.needs_new_triangulation = False
|
||||
return tri_indices
|
||||
|
||||
def get_fill_shader_data(self):
|
||||
points = self.get_points()
|
||||
if len(self.fill_data) != len(points):
|
||||
|
|
|
|||
23
manimlib/utils/customization.py
Normal file
23
manimlib/utils/customization.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import os
|
||||
import tempfile
|
||||
|
||||
from manimlib.config import get_custom_defaults
|
||||
from manimlib.config import get_manim_dir
|
||||
|
||||
CUSTOMIZATION = {}
|
||||
|
||||
|
||||
def get_customization():
|
||||
if not CUSTOMIZATION:
|
||||
CUSTOMIZATION.update(get_custom_defaults())
|
||||
directories = CUSTOMIZATION["directories"]
|
||||
# Unless user has specified otherwise, use the system default temp
|
||||
# directory for storing tex files, mobject_data, etc.
|
||||
if not directories["temporary_storage"]:
|
||||
directories["temporary_storage"] = tempfile.gettempdir()
|
||||
|
||||
# Assumes all shaders are written into manimlib/shaders
|
||||
directories["shaders"] = os.path.join(
|
||||
get_manim_dir(), "manimlib", "shaders"
|
||||
)
|
||||
return CUSTOMIZATION
|
||||
|
|
@ -1,28 +1,11 @@
|
|||
import os
|
||||
import tempfile
|
||||
|
||||
from manimlib.utils.file_ops import guarantee_existence
|
||||
from manimlib.config import get_custom_defaults
|
||||
from manimlib.config import get_manim_dir
|
||||
|
||||
PRE_COMPUTED_DIRS = {}
|
||||
from manimlib.utils.customization import get_customization
|
||||
|
||||
|
||||
def get_directories():
|
||||
if not PRE_COMPUTED_DIRS:
|
||||
custom_defaults = get_custom_defaults()
|
||||
PRE_COMPUTED_DIRS.update(custom_defaults["directories"])
|
||||
|
||||
# Unless user has specified otherwise, use the system default temp
|
||||
# directory for storing tex files, mobject_data, etc.
|
||||
if not PRE_COMPUTED_DIRS["temporary_storage"]:
|
||||
PRE_COMPUTED_DIRS["temporary_storage"] = tempfile.gettempdir()
|
||||
|
||||
# Assumes all shaders are written into manimlib/shaders
|
||||
PRE_COMPUTED_DIRS["shaders"] = os.path.join(
|
||||
get_manim_dir(), "manimlib", "shaders"
|
||||
)
|
||||
return PRE_COMPUTED_DIRS
|
||||
return get_customization()["directories"]
|
||||
|
||||
|
||||
def get_temp_dir():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue