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):
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_height(self.frame_shape[1], stretch=True)
self.move_to(self.center_point)
@ -108,14 +108,19 @@ class CameraFrame(Mobject):
return self.set_rotation(theta=self.euler_angles[2] + dgamma)
def get_shape(self):
return (
self.points[2, 0] - self.points[1, 0],
self.points[4, 1] - self.points[3, 1],
)
return (self.get_width(), self.get_height())
def get_center(self):
# 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):
return self.focal_distance * self.get_height()
@ -123,7 +128,8 @@ class CameraFrame(Mobject):
def interpolate(self, frame1, frame2, alpha, path_func):
self.euler_angles[:] = interpolate(frame1.euler_angles, frame2.euler_angles, alpha)
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):

View file

@ -165,10 +165,10 @@ class TipableVMobject(VMobject):
return self.tip_length
def get_first_handle(self):
return self.points[1]
return self.get_points()[1]
def get_last_handle(self):
return self.points[-2]
return self.get_points()[-2]
def get_end(self):
if self.has_tip():
@ -234,7 +234,7 @@ class Arc(TipableVMobject):
anchors, and finds their intersection points
"""
# First two anchors and handles
a1, h, a2 = self.points[:3]
a1, h, a2 = self.get_points()[:3]
# Tangent vectors
t1 = h - a1
t2 = h - a2
@ -354,10 +354,10 @@ class AnnularSector(Arc):
for radius in (self.inner_radius, self.outer_radius)
]
outer_arc.reverse_points()
self.append_points(inner_arc.points)
self.add_line_to(outer_arc.points[0])
self.append_points(outer_arc.points)
self.add_line_to(inner_arc.points[0])
self.append_points(inner_arc.get_points())
self.add_line_to(outer_arc.get_points()[0])
self.append_points(outer_arc.get_points())
self.add_line_to(inner_arc.get_points()[0])
class Sector(AnnularSector):
@ -382,8 +382,8 @@ class Annulus(Circle):
outer_circle = Circle(radius=self.outer_radius)
inner_circle = Circle(radius=self.inner_radius)
inner_circle.reverse_points()
self.append_points(outer_circle.points)
self.append_points(inner_circle.points)
self.append_points(outer_circle.get_points())
self.append_points(inner_circle.get_points())
self.shift(self.arc_center)
@ -534,10 +534,10 @@ class DashedLine(Line):
return Line.get_end(self)
def get_first_handle(self):
return self.submobjects[0].points[1]
return self.submobjects[0].get_points()[1]
def get_last_handle(self):
return self.submobjects[-1].points[-2]
return self.submobjects[-1].get_points()[-2]
class TangentLine(Line):
@ -625,7 +625,7 @@ class Arrow(Line):
# Tip
self.add_line_to(tip_width * UP / 2)
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(points2[0])
# Close it out
@ -633,7 +633,7 @@ class Arrow(Line):
self.add_line_to(points1[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.shift(start - self.get_start())
@ -645,10 +645,11 @@ class Arrow(Line):
def get_start(self):
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):
return self.points[self.tip_index]
return self.get_points()[self.tip_index]
def put_start_and_end_on(self, start, end):
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
arcs = [arcs[-1], *arcs[:-1]]
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())
# Make sure anchors are evenly distributed
len_ratio = line.get_length() / arc1.get_arc_length()
@ -783,7 +784,7 @@ class ArrowTip(Triangle):
return self.point_from_proportion(0.5)
def get_tip_point(self):
return self.points[0]
return self.get_points()[0]
def get_vector(self):
return self.get_tip_point() - self.get_base()

View file

@ -63,7 +63,7 @@ class Mobject(object):
self.family = [self]
self.init_updaters()
self.points = np.zeros((0, self.dim))
self.init_data()
self.init_points()
self.init_colors()
self.init_shader_data()
@ -76,9 +76,6 @@ class Mobject(object):
def __str__(self):
return self.__class__.__name__
def reset_points(self):
self.points = np.zeros((0, self.dim))
def init_colors(self):
# For subclasses
pass
@ -87,6 +84,37 @@ class Mobject(object):
# Typically implemented in subclass, unless purposefully left blank
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
def __getitem__(self, value):
self_list = self.split()
@ -102,7 +130,7 @@ class Mobject(object):
return len(self.split())
def split(self):
result = [self] if len(self.points) > 0 else []
result = [self] if self.get_num_points() > 0 else []
return result + self.submobjects
def assemble_family(self):
@ -117,7 +145,7 @@ class Mobject(object):
return self.family
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):
if self in mobjects:
@ -184,7 +212,8 @@ class Mobject(object):
copy_mobject = copy.copy(self)
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.add(*[sm.copy() for sm in self.submobjects])
copy_mobject.match_updaters(self)
@ -307,10 +336,6 @@ class Mobject(object):
return self
# Transforming operations
def set_points(self, points):
self.points = np.array(points)
return self
def apply_to_family(self, func):
for mob in self.family_members_with_points():
func(mob)
@ -318,7 +343,8 @@ class Mobject(object):
def shift(self, *vectors):
total_vector = reduce(op.add, vectors)
for mob in self.get_family():
mob.points += total_vector
for arr in mob.get_all_point_arrays():
arr += total_vector
return self
def scale(self, scale_factor, **kwargs):
@ -403,14 +429,14 @@ class Mobject(object):
def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0):
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 /= max(alphas)
alphas = alphas**wag_factor
mob.points += np.dot(
mob.set_points(mob.get_points() + np.dot(
alphas.reshape((len(alphas), 1)),
np.array(direction).reshape((1, mob.dim))
)
))
return self
def reverse_points(self):
@ -432,9 +458,8 @@ class Mobject(object):
about_edge = ORIGIN
about_point = self.get_bounding_box_point(about_edge)
for mob in self.family_members_with_points():
mob.points -= about_point
mob.points[:] = func(mob.points)
mob.points += about_point
for arr in mob.get_all_point_arrays():
arr[:] = func(arr - about_point) + about_point
return self
# Positioning methods
@ -782,22 +807,19 @@ class Mobject(object):
else:
return getattr(self, array_attr)
def get_all_points(self):
def get_all_points(self): # TODO, use get_all_point_arrays?
if self.submobjects:
return np.vstack([
sm.points for sm in self.get_family()
sm.get_points() for sm in self.get_family()
])
else:
return self.points
return self.get_points()
# Getters
def get_points_defining_boundary(self):
return self.get_all_points()
def get_num_points(self):
return len(self.points)
def get_bounding_box_point(self, direction):
result = np.zeros(self.dim)
bb = self.get_bounding_box()
@ -896,11 +918,11 @@ class Mobject(object):
def get_start(self):
self.throw_error_if_no_points()
return np.array(self.points[0])
return np.array(self.get_points()[0])
def get_end(self):
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):
return self.get_start(), self.get_end()
@ -929,7 +951,7 @@ class Mobject(object):
return z_index_group.get_center()
def has_points(self):
return len(self.points) > 0
return self.get_num_points() > 0
def has_no_points(self):
return not self.has_points()
@ -1166,9 +1188,14 @@ class Mobject(object):
Turns self into an interpolation between mobject1
and mobject2.
"""
self.points[:] = path_func(mobject1.points, mobject2.points, alpha)
self.interpolate_color(mobject1, mobject2, alpha)
self.interpolate_light_style(mobject1, mobject2, alpha)
mobs = [self, mobject1, mobject2]
# for arr, arr1, arr2 in zip(*(m.get_all_data_arrays() for m in mobs)):
# 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
def interpolate_color(self, mobject1, mobject2, alpha):
@ -1209,8 +1236,8 @@ class Mobject(object):
"""
self.align_submobjects(mobject)
for sm1, sm2 in zip(self.get_family(), mobject.get_family()):
sm1.set_points(sm2.points)
sm1.interpolate_color(sm1, sm2, 1)
for arr1, arr2 in zip(sm1.get_all_data_arrays(), sm2.get_all_data_arrays()):
arr1[:] = arr2
return self
def cleanup_from_animation(self):
@ -1293,10 +1320,9 @@ class Mobject(object):
# For shader data
def init_shader_data(self):
self.shader_data = np.zeros(len(self.points), dtype=self.shader_dtype)
self.shader_indices = None
self.shader_wrapper = ShaderWrapper(
vert_data=self.shader_data,
vert_data=self.data,
shader_folder=self.shader_folder,
texture_paths=self.texture_paths,
depth_test=self.depth_test,
@ -1307,18 +1333,18 @@ class Mobject(object):
self.shader_wrapper.refresh_id()
return self
def get_blank_shader_data_array(self, size, name="shader_data"):
# If possible, try to populate an existing array, rather
# than recreating it each frame
arr = getattr(self, name)
if arr.size != size:
new_arr = np.resize(arr, size)
setattr(self, name, new_arr)
return new_arr
return arr
# def get_blank_shader_data_array(self, size, name="data"):
# # If possible, try to populate an existing array, rather
# # than recreating it each frame
# arr = getattr(self, name)
# if arr.size != size:
# new_arr = np.resize(arr, size)
# setattr(self, name, new_arr)
# return new_arr
# return arr
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.uniforms = self.get_shader_uniforms()
self.shader_wrapper.depth_test = self.depth_test
@ -1348,11 +1374,6 @@ class Mobject(object):
"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):
return self.shader_indices
@ -1390,10 +1411,10 @@ class Point(Mobject):
return self.artificial_height
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):
return self.get_location()
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
# checks if the symbol is (probably) a percentage sign, and
# 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
sym = sym.family_members_with_points()[0]
new_sym = VMobject()
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])
p1 = sym.points[:n]
p2 = sym.points[n:]
elif len(sym.points) in [468, 483]:
p1 = sym_points[:n]
p2 = sym_points[n:]
elif len(sym_points) in [468, 483]:
p1 = np.vstack([
sym.points[:path_lengths[0]],
sym.points[-path_lengths[3]:]
sym_points[:path_lengths[0]],
sym_points[-path_lengths[3]:]
])
p2 = sym.points[path_lengths[0]:sum(path_lengths[:3])]
sym.points = p1
new_sym.points = p2
p2 = sym_points[path_lengths[0]:sum(path_lengths[:3])]
sym.set_points(p1)
new_sym.set_points(p2)
sym.add(new_sym)
sym.refresh_triangulation()
@ -280,7 +281,7 @@ class SVGMobject(VMobject):
matrix[:, 1] *= -1
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)
except:
pass
@ -356,7 +357,7 @@ class VMobjectFromSVGPathstring(VMobject):
filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}.npy")
if os.path.exists(filepath):
self.points = np.load(filepath)
self.set_points(np.load(filepath))
else:
self.relative_point = np.array(ORIGIN)
for command, coord_string in self.get_commands_and_coord_strings():
@ -367,11 +368,11 @@ class VMobjectFromSVGPathstring(VMobject):
self.subdivide_sharp_curves()
if self.should_remove_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
self.stretch(-1, 1, about_point=ORIGIN)
# Save to a file for future use
np.save(filepath, self.points)
np.save(filepath, self.get_points())
check_and_fix_percent_bug(self)
def get_commands_and_coord_strings(self):
@ -399,11 +400,11 @@ class VMobjectFromSVGPathstring(VMobject):
command = "l"
if command.islower():
leftover_points -= self.relative_point
self.relative_point = self.points[-1]
self.relative_point = self.get_last_point()
self.handle_command(command, leftover_points)
else:
# 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):
numbers = string_to_numbers(coord_string)

View file

@ -53,14 +53,8 @@ class VMobject(Mobject):
"long_lines": False,
# For shaders
"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_vert_shader_file": "quadratic_bezier_fill_vert.glsl",
# "fill_geom_shader_file": "quadratic_bezier_fill_geom.glsl",
# "fill_frag_shader_file": "quadratic_bezier_fill_frag.glsl",
# Could also be Bevel, Miter, Round
# Could also be "bevel", "miter", "round"
"joint_type": "auto",
"flat_stroke": True,
"render_primitive": moderngl.TRIANGLES,
@ -69,7 +63,6 @@ class VMobject(Mobject):
('point', np.float32, (3,)),
('unit_normal', np.float32, (3,)),
('color', np.float32, (4,)),
# ('fill_all', np.float32, (1,)),
('vert_index', np.float32, (1,)),
],
"stroke_dtype": [
@ -92,6 +85,56 @@ class VMobject(Mobject):
def get_group_class(self):
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
def init_colors(self):
self.fill_rgbas = np.zeros((1, 4))
@ -379,36 +422,25 @@ class VMobject(Mobject):
return result
# 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):
assert(len(anchors1) == len(handles) == len(anchors2))
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]
for index, array in enumerate(arrays):
self.points[index::nppc] = array
new_points[index::nppc] = array
self.set_points(new_points)
return self
def clear_points(self):
self.points = np.zeros((0, self.dim))
def append_points(self, new_points):
# 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?
self.points = np.vstack([self.points, new_points])
self.set_points(np.vstack([self.get_points(), new_points]))
return self
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])
return self
@ -422,7 +454,7 @@ class VMobject(Mobject):
"""
self.throw_error_if_no_points()
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():
self.append_points(quadratic_approx[1:])
@ -434,10 +466,10 @@ class VMobject(Mobject):
if self.has_new_path_started():
self.append_points([handle, anchor])
else:
self.append_points([self.points[-1], handle, anchor])
self.append_points([self.get_last_point(), handle, anchor])
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)
if self.long_lines:
halfway = interpolate(end, point, 0.5)
@ -473,13 +505,14 @@ class VMobject(Mobject):
self.add_cubic_bezier_curve_to(new_handle, handle, point)
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):
return self.points[-1]
return self.get_points()[-1]
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):
if not self.is_closed():
@ -487,7 +520,7 @@ class VMobject(Mobject):
def is_closed(self):
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):
@ -509,7 +542,7 @@ class VMobject(Mobject):
])
else:
new_points.append(tup)
vmob.points = np.vstack(new_points)
vmob.set_points(np.vstack(new_points))
return self
def add_points_as_corners(self, points):
@ -566,12 +599,12 @@ class VMobject(Mobject):
self.append_points(points)
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():
# Remove last point, which is starting
# a new path
self.points = self.points[:-1]
self.resize_data(len(self.get_points() - 1))
self.append_points(new_points)
# TODO, how to be smart about tangents here?
@ -627,13 +660,13 @@ class VMobject(Mobject):
def get_nth_curve_points(self, n):
assert(n < self.get_num_curves())
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):
return bezier(self.get_nth_curve_points(n))
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):
num_curves = self.get_num_curves()
@ -649,21 +682,23 @@ class VMobject(Mobject):
for any i in range(0, len(anchors1))
"""
nppc = self.n_points_per_curve
points = self.get_points()
return [
self.points[i::nppc]
points[i::nppc]
for i in range(nppc)
]
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):
nppc = self.n_points_per_curve
return self.points[nppc - 1::nppc]
return self.get_points()[nppc - 1::nppc]
def get_anchors(self):
if len(self.points) == 1:
return self.points
points = self.get_points()
if len(points) == 1:
return points
return np.array(list(it.chain(*zip(
self.get_start_anchors(),
self.get_end_anchors(),
@ -671,11 +706,12 @@ class VMobject(Mobject):
def get_points_without_null_curves(self, atol=1e-9):
nppc = self.n_points_per_curve
points = self.get_points()
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)
])
return self.points[distinct_curves.repeat(nppc)]
return points[distinct_curves.repeat(nppc)]
def get_arc_length(self, n_sample_points=None):
if n_sample_points is None:
@ -697,8 +733,9 @@ class VMobject(Mobject):
return np.zeros(3)
nppc = self.n_points_per_curve
p0 = self.points[0::nppc]
p1 = self.points[nppc - 1::nppc]
points = self.get_points()
p0 = points[0::nppc]
p1 = points[nppc - 1::nppc]
# Each term goes through all edges [(x1, y1, z1), (x2, y2, z2)]
return 0.5 * np.array([
@ -711,7 +748,7 @@ class VMobject(Mobject):
if self.unit_normal_locked:
return self.saved_unit_normal
if len(self.points) < 3:
if self.get_num_points() < 3:
return OUT
area_vect = self.get_area_vector()
@ -719,9 +756,10 @@ class VMobject(Mobject):
if area > 0:
return area_vect / area
else:
points = self.get_points()
return get_unit_normal(
self.points[1] - self.points[0],
self.points[2] - self.points[1],
points[1] - points[0],
points[2] - points[1],
)
def lock_unit_normal(self, family=True):
@ -747,7 +785,7 @@ class VMobject(Mobject):
# Alignment
def align_points(self, vmobject):
self.align_rgbas(vmobject)
if len(self.points) == len(vmobject.points):
if self.get_num_points() == len(vmobject.get_points()):
return
for mob in self, vmobject:
@ -758,7 +796,7 @@ class VMobject(Mobject):
# If there's only one point, turn it into
# a null curve
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
subpaths1 = self.get_subpaths()
@ -862,7 +900,7 @@ class VMobject(Mobject):
def pointwise_become_partial(self, vmobject, a, b):
assert(isinstance(vmobject, VMobject))
self.points[:] = vmobject.points[:]
self.set_points(vmobject.get_points())
if a <= 0 and b >= 1:
return self
num_curves = self.get_num_curves()
@ -880,23 +918,26 @@ class VMobject(Mobject):
i3 = nppc * upper_index
i4 = nppc * (upper_index + 1)
points = self.get_points()
vm_points = vmobject.get_points()
if num_curves == 0:
self.points[:] = 0
points[:] = 0
return self
if lower_index == upper_index:
tup = partial_quadratic_bezier_points(vmobject.points[i1:i2], lower_residue, upper_residue)
self.points[:i1] = tup[0]
self.points[i1:i4] = tup
self.points[i4:] = tup[2]
self.points[nppc:] = self.points[nppc - 1]
tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, upper_residue)
points[:i1] = tup[0]
points[i1:i4] = tup
points[i4:] = tup[2]
points[nppc:] = points[nppc - 1]
else:
low_tup = partial_quadratic_bezier_points(vmobject.points[i1:i2], lower_residue, 1)
high_tup = partial_quadratic_bezier_points(vmobject.points[i3:i4], 0, upper_residue)
self.points[0:i1] = low_tup[0]
self.points[i1:i2] = low_tup
low_tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, 1)
high_tup = partial_quadratic_bezier_points(vm_points[i3:i4], 0, upper_residue)
points[0:i1] = low_tup[0]
points[i1:i2] = low_tup
# Keep points i2:i3 as they are
self.points[i3:i4] = high_tup
self.points[i4:] = high_tup[2]
points[i3:i4] = high_tup
points[i4:] = high_tup[2]
self.set_points(points)
return self
def get_subcurve(self, a, b):
@ -912,8 +953,6 @@ class VMobject(Mobject):
# For shaders
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(
vert_data=self.fill_data,
vert_indices=np.zeros(0, dtype='i4'),
@ -980,6 +1019,7 @@ class VMobject(Mobject):
return result
def get_stroke_shader_data(self):
# TODO, make even simpler after fixing colors
rgbas = self.get_stroke_rgbas()
if len(rgbas) > 1:
rgbas = self.stretched_style_array_matching_points(rgbas)
@ -988,16 +1028,7 @@ class VMobject(Mobject):
if len(stroke_width) > 1:
stroke_width = self.stretched_style_array_matching_points(stroke_width)
points = self.points
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 = self.stroke_data
data["stroke_width"][:, 0] = stroke_width
data["color"] = rgbas
return data
@ -1033,12 +1064,14 @@ class VMobject(Mobject):
if self.triangulation_locked:
return self.saved_triangulation
if len(self.points) <= 1:
points = self.get_points()
if len(points) <= 1:
return np.zeros(0, dtype='i4')
# 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(self.points, z_to_vector(normal_vector))
points = np.dot(points, z_to_vector(normal_vector))
indices = np.arange(len(points), dtype=int)
b0s = points[0::3]
@ -1074,18 +1107,10 @@ class VMobject(Mobject):
return tri_indices
def get_fill_shader_data(self):
points = self.points
n_points = len(points)
unit_normal = self.get_unit_normal()
# TODO, best way to enable multiple colors?
# TODO, make simpler
rgbas = self.get_fill_rgbas()[:1]
data = self.get_blank_shader_data_array(n_points, "fill_data")
data["point"] = points
data["unit_normal"] = unit_normal
data = self.fill_data
data["color"] = rgbas
data["vert_index"][:, 0] = range(n_points)
return data
def get_fill_shader_vert_indices(self):

View file

@ -64,7 +64,14 @@ def partial_quadratic_bezier_points(points, a, b):
# Linear interpolation variants
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):

View file

@ -81,6 +81,7 @@ def listify(obj):
def stretch_array_to_length(nparray, length):
# TODO, rename to "resize"?
indices = np.arange(length) * len(nparray) // length
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
-