Partial progress

This commit is contained in:
Grant Sanderson 2021-01-10 18:51:47 -08:00
parent fb0de62ef4
commit b3335c65fb
8 changed files with 250 additions and 180 deletions

View file

@ -30,7 +30,7 @@ class CameraFrame(Mobject):
} }
def init_points(self): def init_points(self):
self.points = np.array([ORIGIN, LEFT, RIGHT, DOWN, UP]) self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP])
self.set_width(self.frame_shape[0], stretch=True) self.set_width(self.frame_shape[0], stretch=True)
self.set_height(self.frame_shape[1], stretch=True) self.set_height(self.frame_shape[1], stretch=True)
self.move_to(self.center_point) self.move_to(self.center_point)
@ -108,14 +108,19 @@ class CameraFrame(Mobject):
return self.set_rotation(theta=self.euler_angles[2] + dgamma) return self.set_rotation(theta=self.euler_angles[2] + dgamma)
def get_shape(self): def get_shape(self):
return ( return (self.get_width(), self.get_height())
self.points[2, 0] - self.points[1, 0],
self.points[4, 1] - self.points[3, 1],
)
def get_center(self): def get_center(self):
# Assumes first point is at the center # Assumes first point is at the center
return self.points[0] return self.get_points()[0]
def get_width(self):
points = self.get_points()
return points[2, 0] - points[1, 0]
def get_height(self):
points = self.get_points()
return points[4, 1] - points[3, 1]
def get_focal_distance(self): def get_focal_distance(self):
return self.focal_distance * self.get_height() return self.focal_distance * self.get_height()
@ -123,7 +128,8 @@ class CameraFrame(Mobject):
def interpolate(self, frame1, frame2, alpha, path_func): def interpolate(self, frame1, frame2, alpha, path_func):
self.euler_angles[:] = interpolate(frame1.euler_angles, frame2.euler_angles, alpha) self.euler_angles[:] = interpolate(frame1.euler_angles, frame2.euler_angles, alpha)
self.refresh_camera_rotation_matrix() self.refresh_camera_rotation_matrix()
self.points = interpolate(frame1.points, frame2.points, alpha) # TODO, can probably safely call super
self.set_points(interpolate(frame1.get_points(), frame2.get_points(), alpha))
class Camera(object): class Camera(object):

View file

@ -165,10 +165,10 @@ class TipableVMobject(VMobject):
return self.tip_length return self.tip_length
def get_first_handle(self): def get_first_handle(self):
return self.points[1] return self.get_points()[1]
def get_last_handle(self): def get_last_handle(self):
return self.points[-2] return self.get_points()[-2]
def get_end(self): def get_end(self):
if self.has_tip(): if self.has_tip():
@ -234,7 +234,7 @@ class Arc(TipableVMobject):
anchors, and finds their intersection points anchors, and finds their intersection points
""" """
# First two anchors and handles # First two anchors and handles
a1, h, a2 = self.points[:3] a1, h, a2 = self.get_points()[:3]
# Tangent vectors # Tangent vectors
t1 = h - a1 t1 = h - a1
t2 = h - a2 t2 = h - a2
@ -354,10 +354,10 @@ class AnnularSector(Arc):
for radius in (self.inner_radius, self.outer_radius) for radius in (self.inner_radius, self.outer_radius)
] ]
outer_arc.reverse_points() outer_arc.reverse_points()
self.append_points(inner_arc.points) self.append_points(inner_arc.get_points())
self.add_line_to(outer_arc.points[0]) self.add_line_to(outer_arc.get_points()[0])
self.append_points(outer_arc.points) self.append_points(outer_arc.get_points())
self.add_line_to(inner_arc.points[0]) self.add_line_to(inner_arc.get_points()[0])
class Sector(AnnularSector): class Sector(AnnularSector):
@ -382,8 +382,8 @@ class Annulus(Circle):
outer_circle = Circle(radius=self.outer_radius) outer_circle = Circle(radius=self.outer_radius)
inner_circle = Circle(radius=self.inner_radius) inner_circle = Circle(radius=self.inner_radius)
inner_circle.reverse_points() inner_circle.reverse_points()
self.append_points(outer_circle.points) self.append_points(outer_circle.get_points())
self.append_points(inner_circle.points) self.append_points(inner_circle.get_points())
self.shift(self.arc_center) self.shift(self.arc_center)
@ -534,10 +534,10 @@ class DashedLine(Line):
return Line.get_end(self) return Line.get_end(self)
def get_first_handle(self): def get_first_handle(self):
return self.submobjects[0].points[1] return self.submobjects[0].get_points()[1]
def get_last_handle(self): def get_last_handle(self):
return self.submobjects[-1].points[-2] return self.submobjects[-1].get_points()[-2]
class TangentLine(Line): class TangentLine(Line):
@ -625,7 +625,7 @@ class Arrow(Line):
# Tip # Tip
self.add_line_to(tip_width * UP / 2) self.add_line_to(tip_width * UP / 2)
self.add_line_to(tip_length * LEFT) self.add_line_to(tip_length * LEFT)
self.tip_index = len(self.points) - 1 self.tip_index = len(self.get_points()) - 1
self.add_line_to(tip_width * DOWN / 2) self.add_line_to(tip_width * DOWN / 2)
self.add_line_to(points2[0]) self.add_line_to(points2[0])
# Close it out # Close it out
@ -633,7 +633,7 @@ class Arrow(Line):
self.add_line_to(points1[0]) self.add_line_to(points1[0])
if length > 0: if length > 0:
self.points *= length / self.get_length() # Final correction self.scale(length / self.get_length()) # Final correction
self.rotate(angle_of_vector(vect) - self.get_angle()) self.rotate(angle_of_vector(vect) - self.get_angle())
self.shift(start - self.get_start()) self.shift(start - self.get_start())
@ -645,10 +645,11 @@ class Arrow(Line):
def get_start(self): def get_start(self):
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
return (self.points[0] + self.points[-nppc]) / 2 points = self.get_points()
return (points[0] + points[-nppc]) / 2
def get_end(self): def get_end(self):
return self.points[self.tip_index] return self.get_points()[self.tip_index]
def put_start_and_end_on(self, start, end): def put_start_and_end_on(self, start, end):
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc) self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
@ -732,7 +733,7 @@ class Polygon(VMobject):
# To ensure that we loop through starting with last # To ensure that we loop through starting with last
arcs = [arcs[-1], *arcs[:-1]] arcs = [arcs[-1], *arcs[:-1]]
for arc1, arc2 in adjacent_pairs(arcs): for arc1, arc2 in adjacent_pairs(arcs):
self.append_points(arc1.points) self.append_points(arc1.get_points())
line = Line(arc1.get_end(), arc2.get_start()) line = Line(arc1.get_end(), arc2.get_start())
# Make sure anchors are evenly distributed # Make sure anchors are evenly distributed
len_ratio = line.get_length() / arc1.get_arc_length() len_ratio = line.get_length() / arc1.get_arc_length()
@ -783,7 +784,7 @@ class ArrowTip(Triangle):
return self.point_from_proportion(0.5) return self.point_from_proportion(0.5)
def get_tip_point(self): def get_tip_point(self):
return self.points[0] return self.get_points()[0]
def get_vector(self): def get_vector(self):
return self.get_tip_point() - self.get_base() return self.get_tip_point() - self.get_base()

View file

@ -63,7 +63,7 @@ class Mobject(object):
self.family = [self] self.family = [self]
self.init_updaters() self.init_updaters()
self.points = np.zeros((0, self.dim)) self.init_data()
self.init_points() self.init_points()
self.init_colors() self.init_colors()
self.init_shader_data() self.init_shader_data()
@ -76,9 +76,6 @@ class Mobject(object):
def __str__(self): def __str__(self):
return self.__class__.__name__ return self.__class__.__name__
def reset_points(self):
self.points = np.zeros((0, self.dim))
def init_colors(self): def init_colors(self):
# For subclasses # For subclasses
pass pass
@ -87,6 +84,37 @@ class Mobject(object):
# Typically implemented in subclass, unless purposefully left blank # Typically implemented in subclass, unless purposefully left blank
pass pass
# To sort out later
def init_data(self):
self.data = np.zeros(0, dtype=self.shader_dtype)
def resize_data(self, new_length):
if new_length != len(self.data):
self.data = np.resize(self.data, new_length)
def set_points(self, points):
self.resize_data(len(points))
self.data["point"] = points
def get_points(self):
return self.data["point"]
def get_all_point_arrays(self):
return [self.data["point"]]
def get_all_data_arrays(self):
return [self.data]
def get_data_array_attrs(self):
return ["data"]
def clear_points(self):
self.resize_data(0)
def get_num_points(self):
return len(self.data)
#
# Family matters # Family matters
def __getitem__(self, value): def __getitem__(self, value):
self_list = self.split() self_list = self.split()
@ -102,7 +130,7 @@ class Mobject(object):
return len(self.split()) return len(self.split())
def split(self): def split(self):
result = [self] if len(self.points) > 0 else [] result = [self] if self.get_num_points() > 0 else []
return result + self.submobjects return result + self.submobjects
def assemble_family(self): def assemble_family(self):
@ -117,7 +145,7 @@ class Mobject(object):
return self.family return self.family
def family_members_with_points(self): def family_members_with_points(self):
return [m for m in self.get_family() if m.points.size > 0] return [m for m in self.get_family() if m.has_points()]
def add(self, *mobjects): def add(self, *mobjects):
if self in mobjects: if self in mobjects:
@ -184,7 +212,8 @@ class Mobject(object):
copy_mobject = copy.copy(self) copy_mobject = copy.copy(self)
self.parents = parents self.parents = parents
copy_mobject.points = np.array(self.points) for attr in self.get_data_array_attrs():
setattr(copy_mobject, attr, getattr(self, attr).copy())
copy_mobject.submobjects = [] copy_mobject.submobjects = []
copy_mobject.add(*[sm.copy() for sm in self.submobjects]) copy_mobject.add(*[sm.copy() for sm in self.submobjects])
copy_mobject.match_updaters(self) copy_mobject.match_updaters(self)
@ -307,10 +336,6 @@ class Mobject(object):
return self return self
# Transforming operations # Transforming operations
def set_points(self, points):
self.points = np.array(points)
return self
def apply_to_family(self, func): def apply_to_family(self, func):
for mob in self.family_members_with_points(): for mob in self.family_members_with_points():
func(mob) func(mob)
@ -318,7 +343,8 @@ class Mobject(object):
def shift(self, *vectors): def shift(self, *vectors):
total_vector = reduce(op.add, vectors) total_vector = reduce(op.add, vectors)
for mob in self.get_family(): for mob in self.get_family():
mob.points += total_vector for arr in mob.get_all_point_arrays():
arr += total_vector
return self return self
def scale(self, scale_factor, **kwargs): def scale(self, scale_factor, **kwargs):
@ -403,14 +429,14 @@ class Mobject(object):
def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0): def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0):
for mob in self.family_members_with_points(): for mob in self.family_members_with_points():
alphas = np.dot(mob.points, np.transpose(axis)) alphas = np.dot(mob.get_points(), np.transpose(axis))
alphas -= min(alphas) alphas -= min(alphas)
alphas /= max(alphas) alphas /= max(alphas)
alphas = alphas**wag_factor alphas = alphas**wag_factor
mob.points += np.dot( mob.set_points(mob.get_points() + np.dot(
alphas.reshape((len(alphas), 1)), alphas.reshape((len(alphas), 1)),
np.array(direction).reshape((1, mob.dim)) np.array(direction).reshape((1, mob.dim))
) ))
return self return self
def reverse_points(self): def reverse_points(self):
@ -432,9 +458,8 @@ class Mobject(object):
about_edge = ORIGIN about_edge = ORIGIN
about_point = self.get_bounding_box_point(about_edge) about_point = self.get_bounding_box_point(about_edge)
for mob in self.family_members_with_points(): for mob in self.family_members_with_points():
mob.points -= about_point for arr in mob.get_all_point_arrays():
mob.points[:] = func(mob.points) arr[:] = func(arr - about_point) + about_point
mob.points += about_point
return self return self
# Positioning methods # Positioning methods
@ -782,22 +807,19 @@ class Mobject(object):
else: else:
return getattr(self, array_attr) return getattr(self, array_attr)
def get_all_points(self): def get_all_points(self): # TODO, use get_all_point_arrays?
if self.submobjects: if self.submobjects:
return np.vstack([ return np.vstack([
sm.points for sm in self.get_family() sm.get_points() for sm in self.get_family()
]) ])
else: else:
return self.points return self.get_points()
# Getters # Getters
def get_points_defining_boundary(self): def get_points_defining_boundary(self):
return self.get_all_points() return self.get_all_points()
def get_num_points(self):
return len(self.points)
def get_bounding_box_point(self, direction): def get_bounding_box_point(self, direction):
result = np.zeros(self.dim) result = np.zeros(self.dim)
bb = self.get_bounding_box() bb = self.get_bounding_box()
@ -896,11 +918,11 @@ class Mobject(object):
def get_start(self): def get_start(self):
self.throw_error_if_no_points() self.throw_error_if_no_points()
return np.array(self.points[0]) return np.array(self.get_points()[0])
def get_end(self): def get_end(self):
self.throw_error_if_no_points() self.throw_error_if_no_points()
return np.array(self.points[-1]) return np.array(self.get_points()[-1])
def get_start_and_end(self): def get_start_and_end(self):
return self.get_start(), self.get_end() return self.get_start(), self.get_end()
@ -929,7 +951,7 @@ class Mobject(object):
return z_index_group.get_center() return z_index_group.get_center()
def has_points(self): def has_points(self):
return len(self.points) > 0 return self.get_num_points() > 0
def has_no_points(self): def has_no_points(self):
return not self.has_points() return not self.has_points()
@ -1166,9 +1188,14 @@ class Mobject(object):
Turns self into an interpolation between mobject1 Turns self into an interpolation between mobject1
and mobject2. and mobject2.
""" """
self.points[:] = path_func(mobject1.points, mobject2.points, alpha) mobs = [self, mobject1, mobject2]
self.interpolate_color(mobject1, mobject2, alpha) # for arr, arr1, arr2 in zip(*(m.get_all_data_arrays() for m in mobs)):
self.interpolate_light_style(mobject1, mobject2, alpha) # arr[:] = interpolate(arr1, arr2, alpha)
# if path_func is not straight_path:
for arr, arr1, arr2 in zip(*(m.get_all_point_arrays() for m in mobs)):
arr[:] = path_func(arr1, arr2, alpha)
# self.interpolate_color(mobject1, mobject2, alpha)
self.interpolate_light_style(mobject1, mobject2, alpha) # TODO, interpolate uniforms instaed
return self return self
def interpolate_color(self, mobject1, mobject2, alpha): def interpolate_color(self, mobject1, mobject2, alpha):
@ -1209,8 +1236,8 @@ class Mobject(object):
""" """
self.align_submobjects(mobject) self.align_submobjects(mobject)
for sm1, sm2 in zip(self.get_family(), mobject.get_family()): for sm1, sm2 in zip(self.get_family(), mobject.get_family()):
sm1.set_points(sm2.points) for arr1, arr2 in zip(sm1.get_all_data_arrays(), sm2.get_all_data_arrays()):
sm1.interpolate_color(sm1, sm2, 1) arr1[:] = arr2
return self return self
def cleanup_from_animation(self): def cleanup_from_animation(self):
@ -1293,10 +1320,9 @@ class Mobject(object):
# For shader data # For shader data
def init_shader_data(self): def init_shader_data(self):
self.shader_data = np.zeros(len(self.points), dtype=self.shader_dtype)
self.shader_indices = None self.shader_indices = None
self.shader_wrapper = ShaderWrapper( self.shader_wrapper = ShaderWrapper(
vert_data=self.shader_data, vert_data=self.data,
shader_folder=self.shader_folder, shader_folder=self.shader_folder,
texture_paths=self.texture_paths, texture_paths=self.texture_paths,
depth_test=self.depth_test, depth_test=self.depth_test,
@ -1307,18 +1333,18 @@ class Mobject(object):
self.shader_wrapper.refresh_id() self.shader_wrapper.refresh_id()
return self return self
def get_blank_shader_data_array(self, size, name="shader_data"): # def get_blank_shader_data_array(self, size, name="data"):
# If possible, try to populate an existing array, rather # # If possible, try to populate an existing array, rather
# than recreating it each frame # # than recreating it each frame
arr = getattr(self, name) # arr = getattr(self, name)
if arr.size != size: # if arr.size != size:
new_arr = np.resize(arr, size) # new_arr = np.resize(arr, size)
setattr(self, name, new_arr) # setattr(self, name, new_arr)
return new_arr # return new_arr
return arr # return arr
def get_shader_wrapper(self): def get_shader_wrapper(self):
self.shader_wrapper.vert_data = self.get_shader_data() self.shader_wrapper.vert_data = self.data # TODO
self.shader_wrapper.vert_indices = self.get_shader_vert_indices() self.shader_wrapper.vert_indices = self.get_shader_vert_indices()
self.shader_wrapper.uniforms = self.get_shader_uniforms() self.shader_wrapper.uniforms = self.get_shader_uniforms()
self.shader_wrapper.depth_test = self.depth_test self.shader_wrapper.depth_test = self.depth_test
@ -1348,11 +1374,6 @@ class Mobject(object):
"shadow": self.shadow, "shadow": self.shadow,
} }
def get_shader_data(self):
# Typically to be implemented by subclasses
# Must return a structured numpy array
return self.shader_data
def get_shader_vert_indices(self): def get_shader_vert_indices(self):
return self.shader_indices return self.shader_indices
@ -1390,10 +1411,10 @@ class Point(Mobject):
return self.artificial_height return self.artificial_height
def get_location(self): def get_location(self):
return np.array(self.points[0]) return np.array(self.get_points()[0])
def get_bounding_box_point(self, *args, **kwargs): def get_bounding_box_point(self, *args, **kwargs):
return self.get_location() return self.get_location()
def set_location(self, new_loc): def set_location(self, new_loc):
self.points = np.array(new_loc, ndmin=2, dtype=float) self.set_points(np.array(new_loc, ndmin=2, dtype=float))

View file

@ -29,24 +29,25 @@ def check_and_fix_percent_bug(sym):
# The svg path for percent symbols have a known bug, so this # The svg path for percent symbols have a known bug, so this
# checks if the symbol is (probably) a percentage sign, and # checks if the symbol is (probably) a percentage sign, and
# splits it so that it's displayed properly. # splits it so that it's displayed properly.
if len(sym.points) not in [315, 324, 468, 483] or len(sym.get_subpaths()) != 4: if len(sym.get_points()) not in [315, 324, 468, 483] or len(sym.get_subpaths()) != 4:
return return
sym = sym.family_members_with_points()[0] sym = sym.family_members_with_points()[0]
new_sym = VMobject() new_sym = VMobject()
path_lengths = [len(path) for path in sym.get_subpaths()] path_lengths = [len(path) for path in sym.get_subpaths()]
if len(sym.points) in [315, 324]: sym_points = sym.get_points()
if len(sym_points) in [315, 324]:
n = sum(path_lengths[:2]) n = sum(path_lengths[:2])
p1 = sym.points[:n] p1 = sym_points[:n]
p2 = sym.points[n:] p2 = sym_points[n:]
elif len(sym.points) in [468, 483]: elif len(sym_points) in [468, 483]:
p1 = np.vstack([ p1 = np.vstack([
sym.points[:path_lengths[0]], sym_points[:path_lengths[0]],
sym.points[-path_lengths[3]:] sym_points[-path_lengths[3]:]
]) ])
p2 = sym.points[path_lengths[0]:sum(path_lengths[:3])] p2 = sym_points[path_lengths[0]:sum(path_lengths[:3])]
sym.points = p1 sym.set_points(p1)
new_sym.points = p2 new_sym.set_points(p2)
sym.add(new_sym) sym.add(new_sym)
sym.refresh_triangulation() sym.refresh_triangulation()
@ -280,7 +281,7 @@ class SVGMobject(VMobject):
matrix[:, 1] *= -1 matrix[:, 1] *= -1
for mob in mobject.family_members_with_points(): for mob in mobject.family_members_with_points():
mob.points = np.dot(mob.points, matrix) mob.set_points(np.dot(mob.get_points(), matrix))
mobject.shift(x * RIGHT + y * UP) mobject.shift(x * RIGHT + y * UP)
except: except:
pass pass
@ -356,7 +357,7 @@ class VMobjectFromSVGPathstring(VMobject):
filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}.npy") filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}.npy")
if os.path.exists(filepath): if os.path.exists(filepath):
self.points = np.load(filepath) self.set_points(np.load(filepath))
else: else:
self.relative_point = np.array(ORIGIN) self.relative_point = np.array(ORIGIN)
for command, coord_string in self.get_commands_and_coord_strings(): for command, coord_string in self.get_commands_and_coord_strings():
@ -367,11 +368,11 @@ class VMobjectFromSVGPathstring(VMobject):
self.subdivide_sharp_curves() self.subdivide_sharp_curves()
if self.should_remove_null_curves: if self.should_remove_null_curves:
# Get rid of any null curves # Get rid of any null curves
self.points = self.get_points_without_null_curves() self.set_points(self.get_points_without_null_curves())
# SVG treats y-coordinate differently # SVG treats y-coordinate differently
self.stretch(-1, 1, about_point=ORIGIN) self.stretch(-1, 1, about_point=ORIGIN)
# Save to a file for future use # Save to a file for future use
np.save(filepath, self.points) np.save(filepath, self.get_points())
check_and_fix_percent_bug(self) check_and_fix_percent_bug(self)
def get_commands_and_coord_strings(self): def get_commands_and_coord_strings(self):
@ -399,11 +400,11 @@ class VMobjectFromSVGPathstring(VMobject):
command = "l" command = "l"
if command.islower(): if command.islower():
leftover_points -= self.relative_point leftover_points -= self.relative_point
self.relative_point = self.points[-1] self.relative_point = self.get_last_point()
self.handle_command(command, leftover_points) self.handle_command(command, leftover_points)
else: else:
# Command is over, reset for future relative commands # Command is over, reset for future relative commands
self.relative_point = self.points[-1] self.relative_point = self.get_last_point()
def string_to_points(self, command, coord_string): def string_to_points(self, command, coord_string):
numbers = string_to_numbers(coord_string) numbers = string_to_numbers(coord_string)

View file

@ -53,14 +53,8 @@ class VMobject(Mobject):
"long_lines": False, "long_lines": False,
# For shaders # For shaders
"stroke_shader_folder": "quadratic_bezier_stroke", "stroke_shader_folder": "quadratic_bezier_stroke",
# "stroke_vert_shader_file": "quadratic_bezier_stroke_vert.glsl",
# "stroke_geom_shader_file": "quadratic_bezier_stroke_geom.glsl",
# "stroke_frag_shader_file": "quadratic_bezier_stroke_frag.glsl",
"fill_shader_folder": "quadratic_bezier_fill", "fill_shader_folder": "quadratic_bezier_fill",
# "fill_vert_shader_file": "quadratic_bezier_fill_vert.glsl", # Could also be "bevel", "miter", "round"
# "fill_geom_shader_file": "quadratic_bezier_fill_geom.glsl",
# "fill_frag_shader_file": "quadratic_bezier_fill_frag.glsl",
# Could also be Bevel, Miter, Round
"joint_type": "auto", "joint_type": "auto",
"flat_stroke": True, "flat_stroke": True,
"render_primitive": moderngl.TRIANGLES, "render_primitive": moderngl.TRIANGLES,
@ -69,7 +63,6 @@ class VMobject(Mobject):
('point', np.float32, (3,)), ('point', np.float32, (3,)),
('unit_normal', np.float32, (3,)), ('unit_normal', np.float32, (3,)),
('color', np.float32, (4,)), ('color', np.float32, (4,)),
# ('fill_all', np.float32, (1,)),
('vert_index', np.float32, (1,)), ('vert_index', np.float32, (1,)),
], ],
"stroke_dtype": [ "stroke_dtype": [
@ -92,6 +85,56 @@ class VMobject(Mobject):
def get_group_class(self): def get_group_class(self):
return VGroup return VGroup
# To sort out later
def init_data(self):
self.fill_data = np.zeros(0, dtype=self.fill_dtype)
self.stroke_data = np.zeros(0, dtype=self.stroke_dtype)
def resize_data(self, new_length):
self.stroke_data = np.resize(self.stroke_data, new_length)
self.fill_data = np.resize(self.fill_data, new_length)
self.fill_data["vert_index"][:, 0] = range(new_length)
def set_points(self, points):
if len(points) != len(self.stroke_data):
self.resize_data(len(points))
nppc = self.n_points_per_curve
self.stroke_data["point"] = points
self.stroke_data["prev_point"][:nppc] = points[-nppc:]
self.stroke_data["prev_point"][nppc:] = points[:-nppc]
self.stroke_data["next_point"][:-nppc] = points[nppc:]
self.stroke_data["next_point"][-nppc:] = points[:nppc]
self.fill_data["point"] = points
# # TODO, only do conditionally
# unit_normal = self.get_unit_normal()
# self.stroke_data["unit_normal"] = unit_normal
# self.fill_data["unit_normal"] = unit_normal
# self.refresh_triangulation()
def get_points(self):
return self.stroke_data["point"]
def get_all_point_arrays(self):
return [
self.fill_data["point"],
self.stroke_data["point"],
self.stroke_data["prev_point"],
self.stroke_data["next_point"],
]
def get_all_data_arrays(self):
return [self.fill_data, self.stroke_data]
def get_data_array_attrs(self):
return ["fill_data", "stroke_data"]
def get_num_points(self):
return len(self.stroke_data)
# Colors # Colors
def init_colors(self): def init_colors(self):
self.fill_rgbas = np.zeros((1, 4)) self.fill_rgbas = np.zeros((1, 4))
@ -379,36 +422,25 @@ class VMobject(Mobject):
return result return result
# Points # Points
def set_points(self, points):
super().set_points(points)
self.refresh_triangulation()
return self
def get_points(self):
# TODO, shouldn't points always be a numpy array anyway?
return np.array(self.points)
def set_anchors_and_handles(self, anchors1, handles, anchors2): def set_anchors_and_handles(self, anchors1, handles, anchors2):
assert(len(anchors1) == len(handles) == len(anchors2)) assert(len(anchors1) == len(handles) == len(anchors2))
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
self.points = np.zeros((nppc * len(anchors1), self.dim)) new_points = np.zeros((nppc * len(anchors1), self.dim))
arrays = [anchors1, handles, anchors2] arrays = [anchors1, handles, anchors2]
for index, array in enumerate(arrays): for index, array in enumerate(arrays):
self.points[index::nppc] = array new_points[index::nppc] = array
self.set_points(new_points)
return self return self
def clear_points(self):
self.points = np.zeros((0, self.dim))
def append_points(self, new_points): def append_points(self, new_points):
# TODO, check that number new points is a multiple of 4? # TODO, check that number new points is a multiple of 4?
# or else that if len(self.points) % 4 == 1, then # or else that if self.get_num_points() % 4 == 1, then
# len(new_points) % 4 == 3? # len(new_points) % 4 == 3?
self.points = np.vstack([self.points, new_points]) self.set_points(np.vstack([self.get_points(), new_points]))
return self return self
def start_new_path(self, point): def start_new_path(self, point):
assert(len(self.points) % self.n_points_per_curve == 0) assert(self.get_num_points() % self.n_points_per_curve == 0)
self.append_points([point]) self.append_points([point])
return self return self
@ -422,7 +454,7 @@ class VMobject(Mobject):
""" """
self.throw_error_if_no_points() self.throw_error_if_no_points()
quadratic_approx = get_quadratic_approximation_of_cubic( quadratic_approx = get_quadratic_approximation_of_cubic(
self.points[-1], handle1, handle2, anchor self.get_last_point(), handle1, handle2, anchor
) )
if self.has_new_path_started(): if self.has_new_path_started():
self.append_points(quadratic_approx[1:]) self.append_points(quadratic_approx[1:])
@ -434,10 +466,10 @@ class VMobject(Mobject):
if self.has_new_path_started(): if self.has_new_path_started():
self.append_points([handle, anchor]) self.append_points([handle, anchor])
else: else:
self.append_points([self.points[-1], handle, anchor]) self.append_points([self.get_last_point(), handle, anchor])
def add_line_to(self, point): def add_line_to(self, point):
end = self.points[-1] end = self.get_points()[-1]
alphas = np.linspace(0, 1, self.n_points_per_curve) alphas = np.linspace(0, 1, self.n_points_per_curve)
if self.long_lines: if self.long_lines:
halfway = interpolate(end, point, 0.5) halfway = interpolate(end, point, 0.5)
@ -473,13 +505,14 @@ class VMobject(Mobject):
self.add_cubic_bezier_curve_to(new_handle, handle, point) self.add_cubic_bezier_curve_to(new_handle, handle, point)
def has_new_path_started(self): def has_new_path_started(self):
return len(self.points) % self.n_points_per_curve == 1 return self.get_num_points() % self.n_points_per_curve == 1
def get_last_point(self): def get_last_point(self):
return self.points[-1] return self.get_points()[-1]
def get_reflection_of_last_handle(self): def get_reflection_of_last_handle(self):
return 2 * self.points[-1] - self.points[-2] points = self.get_points()
return 2 * points[-1] - points[-2]
def close_path(self): def close_path(self):
if not self.is_closed(): if not self.is_closed():
@ -487,7 +520,7 @@ class VMobject(Mobject):
def is_closed(self): def is_closed(self):
return self.consider_points_equals( return self.consider_points_equals(
self.points[0], self.points[-1] self.get_points()[0], self.get_points()[-1]
) )
def subdivide_sharp_curves(self, angle_threshold=30 * DEGREES, family=True): def subdivide_sharp_curves(self, angle_threshold=30 * DEGREES, family=True):
@ -509,7 +542,7 @@ class VMobject(Mobject):
]) ])
else: else:
new_points.append(tup) new_points.append(tup)
vmob.points = np.vstack(new_points) vmob.set_points(np.vstack(new_points))
return self return self
def add_points_as_corners(self, points): def add_points_as_corners(self, points):
@ -566,12 +599,12 @@ class VMobject(Mobject):
self.append_points(points) self.append_points(points)
def append_vectorized_mobject(self, vectorized_mobject): def append_vectorized_mobject(self, vectorized_mobject):
new_points = list(vectorized_mobject.points) new_points = list(vectorized_mobject.get_points())
if self.has_new_path_started(): if self.has_new_path_started():
# Remove last point, which is starting # Remove last point, which is starting
# a new path # a new path
self.points = self.points[:-1] self.resize_data(len(self.get_points() - 1))
self.append_points(new_points) self.append_points(new_points)
# TODO, how to be smart about tangents here? # TODO, how to be smart about tangents here?
@ -627,13 +660,13 @@ class VMobject(Mobject):
def get_nth_curve_points(self, n): def get_nth_curve_points(self, n):
assert(n < self.get_num_curves()) assert(n < self.get_num_curves())
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
return self.points[nppc * n:nppc * (n + 1)] return self.get_points()[nppc * n:nppc * (n + 1)]
def get_nth_curve_function(self, n): def get_nth_curve_function(self, n):
return bezier(self.get_nth_curve_points(n)) return bezier(self.get_nth_curve_points(n))
def get_num_curves(self): def get_num_curves(self):
return len(self.points) // self.n_points_per_curve return self.get_num_points() // self.n_points_per_curve
def point_from_proportion(self, alpha): def point_from_proportion(self, alpha):
num_curves = self.get_num_curves() num_curves = self.get_num_curves()
@ -649,21 +682,23 @@ class VMobject(Mobject):
for any i in range(0, len(anchors1)) for any i in range(0, len(anchors1))
""" """
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
points = self.get_points()
return [ return [
self.points[i::nppc] points[i::nppc]
for i in range(nppc) for i in range(nppc)
] ]
def get_start_anchors(self): def get_start_anchors(self):
return self.points[0::self.n_points_per_curve] return self.get_points()[0::self.n_points_per_curve]
def get_end_anchors(self): def get_end_anchors(self):
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
return self.points[nppc - 1::nppc] return self.get_points()[nppc - 1::nppc]
def get_anchors(self): def get_anchors(self):
if len(self.points) == 1: points = self.get_points()
return self.points if len(points) == 1:
return points
return np.array(list(it.chain(*zip( return np.array(list(it.chain(*zip(
self.get_start_anchors(), self.get_start_anchors(),
self.get_end_anchors(), self.get_end_anchors(),
@ -671,11 +706,12 @@ class VMobject(Mobject):
def get_points_without_null_curves(self, atol=1e-9): def get_points_without_null_curves(self, atol=1e-9):
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
points = self.get_points()
distinct_curves = reduce(op.or_, [ distinct_curves = reduce(op.or_, [
(abs(self.points[i::nppc] - self.points[0::nppc]) > atol).any(1) (abs(points[i::nppc] - points[0::nppc]) > atol).any(1)
for i in range(1, nppc) for i in range(1, nppc)
]) ])
return self.points[distinct_curves.repeat(nppc)] return points[distinct_curves.repeat(nppc)]
def get_arc_length(self, n_sample_points=None): def get_arc_length(self, n_sample_points=None):
if n_sample_points is None: if n_sample_points is None:
@ -697,8 +733,9 @@ class VMobject(Mobject):
return np.zeros(3) return np.zeros(3)
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
p0 = self.points[0::nppc] points = self.get_points()
p1 = self.points[nppc - 1::nppc] p0 = points[0::nppc]
p1 = points[nppc - 1::nppc]
# Each term goes through all edges [(x1, y1, z1), (x2, y2, z2)] # Each term goes through all edges [(x1, y1, z1), (x2, y2, z2)]
return 0.5 * np.array([ return 0.5 * np.array([
@ -711,7 +748,7 @@ class VMobject(Mobject):
if self.unit_normal_locked: if self.unit_normal_locked:
return self.saved_unit_normal return self.saved_unit_normal
if len(self.points) < 3: if self.get_num_points() < 3:
return OUT return OUT
area_vect = self.get_area_vector() area_vect = self.get_area_vector()
@ -719,9 +756,10 @@ class VMobject(Mobject):
if area > 0: if area > 0:
return area_vect / area return area_vect / area
else: else:
points = self.get_points()
return get_unit_normal( return get_unit_normal(
self.points[1] - self.points[0], points[1] - points[0],
self.points[2] - self.points[1], points[2] - points[1],
) )
def lock_unit_normal(self, family=True): def lock_unit_normal(self, family=True):
@ -747,7 +785,7 @@ class VMobject(Mobject):
# Alignment # Alignment
def align_points(self, vmobject): def align_points(self, vmobject):
self.align_rgbas(vmobject) self.align_rgbas(vmobject)
if len(self.points) == len(vmobject.points): if self.get_num_points() == len(vmobject.get_points()):
return return
for mob in self, vmobject: for mob in self, vmobject:
@ -758,7 +796,7 @@ class VMobject(Mobject):
# If there's only one point, turn it into # If there's only one point, turn it into
# a null curve # a null curve
if mob.has_new_path_started(): if mob.has_new_path_started():
mob.add_line_to(mob.points[0]) mob.add_line_to(mob.get_points()[0])
# Figure out what the subpaths are, and align # Figure out what the subpaths are, and align
subpaths1 = self.get_subpaths() subpaths1 = self.get_subpaths()
@ -862,7 +900,7 @@ class VMobject(Mobject):
def pointwise_become_partial(self, vmobject, a, b): def pointwise_become_partial(self, vmobject, a, b):
assert(isinstance(vmobject, VMobject)) assert(isinstance(vmobject, VMobject))
self.points[:] = vmobject.points[:] self.set_points(vmobject.get_points())
if a <= 0 and b >= 1: if a <= 0 and b >= 1:
return self return self
num_curves = self.get_num_curves() num_curves = self.get_num_curves()
@ -880,23 +918,26 @@ class VMobject(Mobject):
i3 = nppc * upper_index i3 = nppc * upper_index
i4 = nppc * (upper_index + 1) i4 = nppc * (upper_index + 1)
points = self.get_points()
vm_points = vmobject.get_points()
if num_curves == 0: if num_curves == 0:
self.points[:] = 0 points[:] = 0
return self return self
if lower_index == upper_index: if lower_index == upper_index:
tup = partial_quadratic_bezier_points(vmobject.points[i1:i2], lower_residue, upper_residue) tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, upper_residue)
self.points[:i1] = tup[0] points[:i1] = tup[0]
self.points[i1:i4] = tup points[i1:i4] = tup
self.points[i4:] = tup[2] points[i4:] = tup[2]
self.points[nppc:] = self.points[nppc - 1] points[nppc:] = points[nppc - 1]
else: else:
low_tup = partial_quadratic_bezier_points(vmobject.points[i1:i2], lower_residue, 1) low_tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, 1)
high_tup = partial_quadratic_bezier_points(vmobject.points[i3:i4], 0, upper_residue) high_tup = partial_quadratic_bezier_points(vm_points[i3:i4], 0, upper_residue)
self.points[0:i1] = low_tup[0] points[0:i1] = low_tup[0]
self.points[i1:i2] = low_tup points[i1:i2] = low_tup
# Keep points i2:i3 as they are # Keep points i2:i3 as they are
self.points[i3:i4] = high_tup points[i3:i4] = high_tup
self.points[i4:] = high_tup[2] points[i4:] = high_tup[2]
self.set_points(points)
return self return self
def get_subcurve(self, a, b): def get_subcurve(self, a, b):
@ -912,8 +953,6 @@ class VMobject(Mobject):
# For shaders # For shaders
def init_shader_data(self): def init_shader_data(self):
self.fill_data = np.zeros(len(self.points), dtype=self.fill_dtype)
self.stroke_data = np.zeros(len(self.points), dtype=self.stroke_dtype)
self.fill_shader_wrapper = ShaderWrapper( self.fill_shader_wrapper = ShaderWrapper(
vert_data=self.fill_data, vert_data=self.fill_data,
vert_indices=np.zeros(0, dtype='i4'), vert_indices=np.zeros(0, dtype='i4'),
@ -980,6 +1019,7 @@ class VMobject(Mobject):
return result return result
def get_stroke_shader_data(self): def get_stroke_shader_data(self):
# TODO, make even simpler after fixing colors
rgbas = self.get_stroke_rgbas() rgbas = self.get_stroke_rgbas()
if len(rgbas) > 1: if len(rgbas) > 1:
rgbas = self.stretched_style_array_matching_points(rgbas) rgbas = self.stretched_style_array_matching_points(rgbas)
@ -988,16 +1028,7 @@ class VMobject(Mobject):
if len(stroke_width) > 1: if len(stroke_width) > 1:
stroke_width = self.stretched_style_array_matching_points(stroke_width) stroke_width = self.stretched_style_array_matching_points(stroke_width)
points = self.points data = self.stroke_data
nppc = self.n_points_per_curve
data = self.get_blank_shader_data_array(len(points), "stroke_data")
data["point"] = points
data["prev_point"][:nppc] = points[-nppc:]
data["prev_point"][nppc:] = points[:-nppc]
data["next_point"][:-nppc] = points[nppc:]
data["next_point"][-nppc:] = points[:nppc]
data["unit_normal"] = self.get_unit_normal()
data["stroke_width"][:, 0] = stroke_width data["stroke_width"][:, 0] = stroke_width
data["color"] = rgbas data["color"] = rgbas
return data return data
@ -1033,12 +1064,14 @@ class VMobject(Mobject):
if self.triangulation_locked: if self.triangulation_locked:
return self.saved_triangulation return self.saved_triangulation
if len(self.points) <= 1: points = self.get_points()
if len(points) <= 1:
return np.zeros(0, dtype='i4') return np.zeros(0, dtype='i4')
# Rotate points such that unit normal vector is OUT # Rotate points such that unit normal vector is OUT
# TODO, 99% of the time this does nothing. Do a check for that? # TODO, 99% of the time this does nothing. Do a check for that?
points = np.dot(self.points, z_to_vector(normal_vector)) points = np.dot(points, z_to_vector(normal_vector))
indices = np.arange(len(points), dtype=int) indices = np.arange(len(points), dtype=int)
b0s = points[0::3] b0s = points[0::3]
@ -1074,18 +1107,10 @@ class VMobject(Mobject):
return tri_indices return tri_indices
def get_fill_shader_data(self): def get_fill_shader_data(self):
points = self.points # TODO, make simpler
n_points = len(points)
unit_normal = self.get_unit_normal()
# TODO, best way to enable multiple colors?
rgbas = self.get_fill_rgbas()[:1] rgbas = self.get_fill_rgbas()[:1]
data = self.fill_data
data = self.get_blank_shader_data_array(n_points, "fill_data")
data["point"] = points
data["unit_normal"] = unit_normal
data["color"] = rgbas data["color"] = rgbas
data["vert_index"][:, 0] = range(n_points)
return data return data
def get_fill_shader_vert_indices(self): def get_fill_shader_vert_indices(self):

View file

@ -64,7 +64,14 @@ def partial_quadratic_bezier_points(points, a, b):
# Linear interpolation variants # Linear interpolation variants
def interpolate(start, end, alpha): def interpolate(start, end, alpha):
return (1 - alpha) * start + alpha * end try:
return (1 - alpha) * start + alpha * end
except TypeError:
print(type(start), start.dtype)
print(type(end), start.dtype)
print(alpha)
import sys
sys.exit(2)
def set_array_by_interpolation(arr, arr1, arr2, alpha): def set_array_by_interpolation(arr, arr1, arr2, alpha):

View file

@ -81,6 +81,7 @@ def listify(obj):
def stretch_array_to_length(nparray, length): def stretch_array_to_length(nparray, length):
# TODO, rename to "resize"?
indices = np.arange(length) * len(nparray) // length indices = np.arange(length) * len(nparray) // length
return nparray[indices] return nparray[indices]

8
temp_todo.md Normal file
View file

@ -0,0 +1,8 @@
- Write init_data
- Write set_points
- Write get_all_points_arrays
- Write get_primary_points_array
- Write stretch data array method
- Replace all .points with .data["point"]
- Change setting/getting colors based on rgba arrays
-