mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 21:12:35 +08:00
Interpolation for vectorized mobjects implemented
This commit is contained in:
@ -12,8 +12,7 @@ from mobject import Mobject, Point
|
|||||||
|
|
||||||
class Transform(Animation):
|
class Transform(Animation):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"path_func" : straight_path,
|
"path_func" : straight_path
|
||||||
"should_black_out_extra_points" : False
|
|
||||||
}
|
}
|
||||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||||
mobject = instantiate(mobject)
|
mobject = instantiate(mobject)
|
||||||
@ -22,58 +21,24 @@ class Transform(Animation):
|
|||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
||||||
if count2 == 0:
|
if count2 == 0:
|
||||||
ending_mobject.add_points(
|
ending_mobject = mobject.get_point_mobject()
|
||||||
[mobject.get_center()],
|
|
||||||
color = BLACK
|
|
||||||
)
|
|
||||||
count2 = ending_mobject.get_num_points()
|
count2 = ending_mobject.get_num_points()
|
||||||
Mobject.align_data(mobject, ending_mobject)
|
mobject.align_data(ending_mobject)
|
||||||
if self.should_black_out_extra_points and count2 < count1:
|
|
||||||
self.black_out_extra_points(count1, count2)
|
|
||||||
|
|
||||||
Animation.__init__(self, mobject, **kwargs)
|
Animation.__init__(self, mobject, **kwargs)
|
||||||
self.name += "To" + str(ending_mobject)
|
self.name += "To" + str(ending_mobject)
|
||||||
self.mobject.point_thickness = ending_mobject.point_thickness
|
self.mobject.stroke_width = ending_mobject.stroke_width
|
||||||
|
|
||||||
|
|
||||||
def black_out_extra_points(self, count1, count2):
|
|
||||||
#Ensure redundant pixels fade to black
|
|
||||||
indices = np.arange(
|
|
||||||
0, count1-1, float(count1) / count2
|
|
||||||
).astype('int')
|
|
||||||
temp = np.zeros(self.ending_mobject.points.shape)
|
|
||||||
temp[indices] = self.ending_mobject.rgbs[indices]
|
|
||||||
self.ending_mobject.rgbs = temp
|
|
||||||
self.non_redundant_m2_indices = indices
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
def update_mobject(self, alpha):
|
||||||
families = map(
|
families = map(
|
||||||
Mobject.submobject_family,
|
Mobject.submobject_family,
|
||||||
[self.mobject, self.starting_mobject, self.ending_mobject]
|
[self.mobject, self.starting_mobject, self.ending_mobject]
|
||||||
)
|
)
|
||||||
for m, start, end in zip(*families):
|
for m, start, end in zip(*families):
|
||||||
# print m, start, end
|
m.interpolate(start, end, alpha, self.path_func)
|
||||||
m.points = self.path_func(
|
|
||||||
start.points, end.points, alpha
|
|
||||||
)
|
|
||||||
m.rgbs = straight_path(start.rgbs, end.rgbs, alpha)
|
|
||||||
|
|
||||||
|
|
||||||
def clean_up(self):
|
|
||||||
Animation.clean_up(self)
|
|
||||||
if hasattr(self, "non_redundant_m2_indices"):
|
|
||||||
#Reduce mobject (which has become identical to mobject2), as
|
|
||||||
#well as mobject2 itself
|
|
||||||
for mobject in [self.mobject, self.ending_mobject]:
|
|
||||||
for attr in ['points', 'rgbs']:
|
|
||||||
setattr(
|
|
||||||
mobject, attr,
|
|
||||||
getattr(
|
|
||||||
self.ending_mobject,
|
|
||||||
attr
|
|
||||||
)[self.non_redundant_m2_indices]
|
|
||||||
)
|
|
||||||
|
|
||||||
class ClockwiseTransform(Transform):
|
class ClockwiseTransform(Transform):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"path_func" : clockwise_path()
|
"path_func" : clockwise_path()
|
||||||
@ -219,10 +184,10 @@ class TransformAnimations(Transform):
|
|||||||
anim.set_run_time(self.run_time)
|
anim.set_run_time(self.run_time)
|
||||||
|
|
||||||
if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points():
|
if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points():
|
||||||
Mobject.align_data(start_anim.starting_mobject, end_anim.starting_mobject)
|
start_anim.starting_mobject.align_data(end_anim.starting_mobject)
|
||||||
for anim in start_anim, end_anim:
|
for anim in start_anim, end_anim:
|
||||||
if hasattr(anim, "ending_mobject"):
|
if hasattr(anim, "ending_mobject"):
|
||||||
Mobject.align_data(anim.starting_mobject, anim.ending_mobject)
|
anim.starting_mobject.align_data(anim.ending_mobject)
|
||||||
|
|
||||||
Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs)
|
Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs)
|
||||||
#Rewire starting and ending mobjects
|
#Rewire starting and ending mobjects
|
||||||
|
29
camera.py
29
camera.py
@ -71,19 +71,18 @@ class Camera(object):
|
|||||||
mob.nonempty_family_members()
|
mob.nonempty_family_members()
|
||||||
for mob in mobjects
|
for mob in mobjects
|
||||||
])
|
])
|
||||||
vect_mobjects = []
|
|
||||||
for mobject in mobjects:
|
for mobject in mobjects:
|
||||||
if isinstance(mobject, VectorizedMobject):
|
if isinstance(mobject, VectorizedMobject):
|
||||||
vect_mobjects.append(mobject)
|
self.display_vectorized(mobject)
|
||||||
elif isinstance(mobject, PointCloudMobject):
|
elif isinstance(mobject, PointCloudMobject):
|
||||||
self.display_point_cloud(
|
self.display_point_cloud(
|
||||||
mobject.points, mobject.rgbs,
|
mobject.points, mobject.rgbs,
|
||||||
self.adjusted_thickness(mobject.point_thickness)
|
self.adjusted_thickness(mobject.stroke_width)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception("I don't know how to display that")
|
#TODO
|
||||||
if vect_mobjects:
|
print mobject
|
||||||
self.display_vectorized(vect_mobjects)
|
# raise Exception("I don't know how to display that")
|
||||||
|
|
||||||
# def display_region(self, region):
|
# def display_region(self, region):
|
||||||
# (h, w) = self.pixel_shape
|
# (h, w) = self.pixel_shape
|
||||||
@ -98,25 +97,23 @@ class Camera(object):
|
|||||||
# self.pixel_array[covered] = rgb
|
# self.pixel_array[covered] = rgb
|
||||||
|
|
||||||
|
|
||||||
def display_vectorized(self, vect_mobjects):
|
def display_vectorized(self, vect_mobject):
|
||||||
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
||||||
canvas = aggdraw.Draw(im)
|
canvas = aggdraw.Draw(im)
|
||||||
for mob in vect_mobjects:
|
pen, fill = self.get_pen_and_fill(vect_mobject)
|
||||||
pen, fill = self.get_pen_and_fill(mob)
|
|
||||||
#TODO, fill
|
|
||||||
pathstring = self.get_pathstring(
|
pathstring = self.get_pathstring(
|
||||||
self.points_to_pixel_coords(mob.points),
|
self.points_to_pixel_coords(vect_mobject.points),
|
||||||
closed = mob.is_closed()
|
closed = vect_mobject.is_closed()
|
||||||
)
|
)
|
||||||
symbol = aggdraw.Symbol(pathstring)
|
symbol = aggdraw.Symbol(pathstring)
|
||||||
canvas.symbol((0, 0), symbol, pen, fill)
|
canvas.symbol((0, 0), symbol, pen, fill)
|
||||||
canvas.flush()
|
canvas.flush()
|
||||||
self.pixel_array = np.array(im)
|
self.pixel_array[:,:] = np.array(im)
|
||||||
|
|
||||||
def get_pen_and_fill(self, vect_mobject):
|
def get_pen_and_fill(self, vect_mobject):
|
||||||
pen = aggdraw.Pen(
|
pen = aggdraw.Pen(
|
||||||
vect_mobject.get_color().get_web(),
|
vect_mobject.get_stroke_color().get_web(),
|
||||||
vect_mobject.point_thickness
|
vect_mobject.stroke_width
|
||||||
)
|
)
|
||||||
fill = aggdraw.Brush(
|
fill = aggdraw.Brush(
|
||||||
vect_mobject.get_fill_color().get_web(),
|
vect_mobject.get_fill_color().get_web(),
|
||||||
@ -124,8 +121,6 @@ class Camera(object):
|
|||||||
)
|
)
|
||||||
return (pen, fill)
|
return (pen, fill)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_pathstring(self, cubic_bezier_points, closed = False):
|
def get_pathstring(self, cubic_bezier_points, closed = False):
|
||||||
start = "m%d,%d"%tuple(cubic_bezier_points[0])
|
start = "m%d,%d"%tuple(cubic_bezier_points[0])
|
||||||
#(handle1, handle2, anchor) tripletes
|
#(handle1, handle2, anchor) tripletes
|
||||||
|
17
helpers.py
17
helpers.py
@ -25,6 +25,23 @@ def compass_directions(n = 4, start_vect = UP):
|
|||||||
for k in range(n)
|
for k in range(n)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def diag_to_matrix(l_and_u, diag):
|
||||||
|
"""
|
||||||
|
Converts array whose rows represent diagonal
|
||||||
|
entries of a matrix into the matrix itself.
|
||||||
|
See scipy.linalg.solve_banded
|
||||||
|
"""
|
||||||
|
l, u = l_and_u
|
||||||
|
dim = diag.shape[1]
|
||||||
|
matrix = np.zeros((dim, dim))
|
||||||
|
for i in range(l+u+1):
|
||||||
|
np.fill_diagonal(
|
||||||
|
matrix[max(0,i-u):,max(0,u-i):],
|
||||||
|
diag[i,max(0,u-i):]
|
||||||
|
)
|
||||||
|
return matrix
|
||||||
|
|
||||||
|
|
||||||
def bezier(points):
|
def bezier(points):
|
||||||
n = len(points) - 1
|
n = len(points) - 1
|
||||||
return lambda t : sum([
|
return lambda t : sum([
|
||||||
|
@ -15,7 +15,7 @@ class ImageMobject(Mobject):
|
|||||||
"filter_color" : "black",
|
"filter_color" : "black",
|
||||||
"invert" : True,
|
"invert" : True,
|
||||||
"use_cache" : True,
|
"use_cache" : True,
|
||||||
"point_thickness" : 1,
|
"stroke_width" : 1,
|
||||||
"scale_value" : 1.0,
|
"scale_value" : 1.0,
|
||||||
"should_center" : True,
|
"should_center" : True,
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class Mobject(object):
|
|||||||
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
|
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : WHITE,
|
"color" : WHITE,
|
||||||
"point_thickness" : DEFAULT_POINT_THICKNESS,
|
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
||||||
"name" : None,
|
"name" : None,
|
||||||
"display_mode" : "points", #TODO, REMOVE
|
"display_mode" : "points", #TODO, REMOVE
|
||||||
"dim" : 3,
|
"dim" : 3,
|
||||||
@ -109,7 +109,7 @@ class Mobject(object):
|
|||||||
def rotate(self, angle, axis = OUT, axes = []):
|
def rotate(self, angle, axis = OUT, axes = []):
|
||||||
if len(axes) == 0:
|
if len(axes) == 0:
|
||||||
axes = [axis]
|
axes = [axis]
|
||||||
rot_matrix = np.identity(self.DIM)
|
rot_matrix = np.identity(self.dim)
|
||||||
for axis in axes:
|
for axis in axes:
|
||||||
rot_matrix = np.dot(rot_matrix, rotation_matrix(angle, axis))
|
rot_matrix = np.dot(rot_matrix, rotation_matrix(angle, axis))
|
||||||
t_rot_matrix = np.transpose(rot_matrix)
|
t_rot_matrix = np.transpose(rot_matrix)
|
||||||
@ -135,7 +135,7 @@ class Mobject(object):
|
|||||||
alphas = alphas**wag_factor
|
alphas = alphas**wag_factor
|
||||||
mob.points += np.dot(
|
mob.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
|
||||||
|
|
||||||
@ -310,7 +310,7 @@ class Mobject(object):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_merged_array(self, array_attr):
|
def get_merged_array(self, array_attr):
|
||||||
result = np.zeros((0, self.DIM))
|
result = np.zeros((0, self.dim))
|
||||||
for mob in self.nonempty_family_members():
|
for mob in self.nonempty_family_members():
|
||||||
result = np.append(result, getattr(mob, array_attr), 0)
|
result = np.append(result, getattr(mob, array_attr), 0)
|
||||||
return result
|
return result
|
||||||
@ -327,7 +327,7 @@ class Mobject(object):
|
|||||||
return len(self.points)
|
return len(self.points)
|
||||||
|
|
||||||
def get_critical_point(self, direction):
|
def get_critical_point(self, direction):
|
||||||
result = np.zeros(self.DIM)
|
result = np.zeros(self.dim)
|
||||||
for dim in [0, 1]:
|
for dim in [0, 1]:
|
||||||
if direction[dim] <= 0:
|
if direction[dim] <= 0:
|
||||||
min_point = self.reduce_across_dimension(np.min, np.min, dim)
|
min_point = self.reduce_across_dimension(np.min, np.min, dim)
|
||||||
@ -350,7 +350,7 @@ class Mobject(object):
|
|||||||
return self.get_critical_point(direction)
|
return self.get_critical_point(direction)
|
||||||
|
|
||||||
def get_center(self):
|
def get_center(self):
|
||||||
return self.get_critical_point(np.zeros(self.DIM))
|
return self.get_critical_point(np.zeros(self.dim))
|
||||||
|
|
||||||
def get_center_of_mass(self):
|
def get_center_of_mass(self):
|
||||||
return np.apply_along_axis(np.mean, 0, self.get_all_points())
|
return np.apply_along_axis(np.mean, 0, self.get_all_points())
|
||||||
@ -405,51 +405,51 @@ class Mobject(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
## Alignment
|
## Alignment
|
||||||
|
def align_data(self, mobject):
|
||||||
@staticmethod
|
self.align_points(mobject)
|
||||||
def align_data(mobject1, mobject2):
|
|
||||||
count1 = len(mobject1.points)
|
|
||||||
count2 = len(mobject2.points)
|
|
||||||
if count1 != count2:
|
|
||||||
if count1 < count2:
|
|
||||||
smaller = mobject1
|
|
||||||
target_size = count2
|
|
||||||
else:
|
|
||||||
smaller = mobject2
|
|
||||||
target_size = count1
|
|
||||||
if len(smaller.points) == 0:
|
|
||||||
smaller.add_points(
|
|
||||||
[np.zeros(smaller.DIM)],
|
|
||||||
color = BLACK
|
|
||||||
)
|
|
||||||
smaller.apply_over_attr_arrays(
|
|
||||||
lambda a : streth_array_to_length(a, target_size)
|
|
||||||
)
|
|
||||||
#Recurse
|
#Recurse
|
||||||
diff = len(mobject1.sub_mobjects) - len(mobject2.sub_mobjects)
|
diff = len(self.sub_mobjects) - len(mobject.sub_mobjects)
|
||||||
|
|
||||||
if diff < 0:
|
|
||||||
larger, smaller = mobject2, mobject1
|
|
||||||
elif diff > 0:
|
|
||||||
larger, smaller = mobject1, mobject2
|
|
||||||
if diff != 0:
|
if diff != 0:
|
||||||
|
if diff < 0:
|
||||||
|
larger, smaller = mobject, self
|
||||||
|
elif diff > 0:
|
||||||
|
larger, smaller = self, mobject
|
||||||
for sub_mob in larger.sub_mobjects[-abs(diff):]:
|
for sub_mob in larger.sub_mobjects[-abs(diff):]:
|
||||||
smaller.add(Point(sub_mob.get_center()))
|
smaller.add(sub_mob.get_point_mobject())
|
||||||
for m1, m2 in zip(mobject1.sub_mobjects, mobject2.sub_mobjects):
|
for m1, m2 in zip(self.sub_mobjects, mobject.sub_mobjects):
|
||||||
Mobject.align_data(m1, m2)
|
m1.align_data(m2)
|
||||||
|
|
||||||
def interpolate(self, mobject1, mobject2, alpha):
|
def get_point_mobject(self):
|
||||||
|
"""
|
||||||
|
The simplest mobject to be transformed to or from self.
|
||||||
|
Should by a point of the appropriate type
|
||||||
|
"""
|
||||||
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
def align_points(self, mobject):
|
||||||
|
count1 = self.get_num_points()
|
||||||
|
count2 = mobject.get_num_points()
|
||||||
|
if count1 < count2:
|
||||||
|
self.align_points_with_larger(mobject)
|
||||||
|
elif count2 < count1:
|
||||||
|
mobject.align_points_with_larger(self)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def align_points_with_larger(self, larger_mobject):
|
||||||
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
def interpolate(self, mobject1, mobject2, alpha, path_func):
|
||||||
"""
|
"""
|
||||||
Turns target_mobject into an interpolation between mobject1
|
Turns target_mobject into an interpolation between mobject1
|
||||||
and mobject2.
|
and mobject2.
|
||||||
"""
|
"""
|
||||||
#TODO
|
self.points = path_func(
|
||||||
Mobject.align_data(mobject1, mobject2)
|
mobject1.points, mobject2.points, alpha
|
||||||
for attr in self.get_array_attrs():
|
)
|
||||||
setattr(self, attr, interpolate(
|
self.interpolate_color(mobject1, mobject2, alpha)
|
||||||
getattr(mobject1, attr),
|
|
||||||
getattr(mobject2, attr),
|
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||||
alpha))
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,6 +110,23 @@ class PointCloudMobject(Mobject):
|
|||||||
index = alpha*(self.get_num_points()-1)
|
index = alpha*(self.get_num_points()-1)
|
||||||
return self.points[index]
|
return self.points[index]
|
||||||
|
|
||||||
|
# Alignment
|
||||||
|
def align_points_with_larger(self, larger_mobject):
|
||||||
|
assert(isinstance(larger_mobject, PointCloudMobject))
|
||||||
|
self.apply_over_attr_arrays(
|
||||||
|
lambda a : streth_array_to_length(
|
||||||
|
a, larger_mobject.get_num_points()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_point_mobject(self):
|
||||||
|
return Point(self.get_center())
|
||||||
|
|
||||||
|
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||||
|
self.rgbs = interpolate(
|
||||||
|
mobject1.rgbs, mobject2.rgbs, alpha
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#TODO, Make the two implementations bellow non-redundant
|
#TODO, Make the two implementations bellow non-redundant
|
||||||
class Mobject1D(PointCloudMobject):
|
class Mobject1D(PointCloudMobject):
|
||||||
@ -146,13 +163,11 @@ class Mobject2D(PointCloudMobject):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Point(Mobject):
|
class Point(PointCloudMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : BLACK,
|
"color" : BLACK,
|
||||||
}
|
}
|
||||||
def __init__(self, location = ORIGIN, **kwargs):
|
def __init__(self, location = ORIGIN, **kwargs):
|
||||||
digest_locals(self)
|
PointCloudMobject.__init__(self, **kwargs)
|
||||||
Mobject.__init__(self, **kwargs)
|
self.add_points([location])
|
||||||
|
|
||||||
def generate_points(self):
|
|
||||||
self.add_points([self.location])
|
|
||||||
|
@ -8,7 +8,7 @@ class TexMobject(Mobject):
|
|||||||
CONFIG = {
|
CONFIG = {
|
||||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||||
"color" : WHITE,
|
"color" : WHITE,
|
||||||
"point_thickness" : 1,
|
"stroke_width" : 1,
|
||||||
"should_center" : True,
|
"should_center" : True,
|
||||||
}
|
}
|
||||||
def __init__(self, expression, **kwargs):
|
def __init__(self, expression, **kwargs):
|
||||||
|
@ -35,7 +35,7 @@ class VectorizedMobject(Mobject):
|
|||||||
def get_fill_opacity(self):
|
def get_fill_opacity(self):
|
||||||
return self.fill_opacity
|
return self.fill_opacity
|
||||||
|
|
||||||
def get_storke_color(self):
|
def get_stroke_color(self):
|
||||||
return Color(rgb = self.stroke_rgb)
|
return Color(rgb = self.stroke_rgb)
|
||||||
|
|
||||||
#TODO, get color? Specify if stroke or fill
|
#TODO, get color? Specify if stroke or fill
|
||||||
@ -88,18 +88,18 @@ class VectorizedMobject(Mobject):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def set_points_as_corners(self, points):
|
def set_points_as_corners(self, points):
|
||||||
|
if len(points) <= 1:
|
||||||
|
return self
|
||||||
|
points = self.close_if_needed(points)
|
||||||
handles1 = points[:-1]
|
handles1 = points[:-1]
|
||||||
handles2 = points[1:]
|
handles2 = points[1:]
|
||||||
self.set_anchors_and_handles(points, handles1, handles2)
|
self.set_anchors_and_handles(points, handles1, handles2)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_points_smoothly(self, points):
|
def set_points_smoothly(self, points):
|
||||||
if self.is_closed():
|
if len(points) <= 1:
|
||||||
points = np.append(
|
return self
|
||||||
points,
|
points = self.close_if_needed(points)
|
||||||
[points[0], points[1]],
|
|
||||||
axis = 0
|
|
||||||
)
|
|
||||||
num_handles = len(points) - 1
|
num_handles = len(points) - 1
|
||||||
#Must solve 2*num_handles equations to get the handles.
|
#Must solve 2*num_handles equations to get the handles.
|
||||||
#l and u are the number of lower an upper diagonal rows
|
#l and u are the number of lower an upper diagonal rows
|
||||||
@ -115,6 +115,8 @@ class VectorizedMobject(Mobject):
|
|||||||
diag[1,1::2] = 1
|
diag[1,1::2] = 1
|
||||||
diag[2,1:-2:2] = -2
|
diag[2,1:-2:2] = -2
|
||||||
diag[3,0:-3:2] = 1
|
diag[3,0:-3:2] = 1
|
||||||
|
diag[2,-2] = 1
|
||||||
|
diag[1,-1] = -2
|
||||||
#This is the b as in Ax = b, where we are solving for x,
|
#This is the b as in Ax = b, where we are solving for x,
|
||||||
#and A is represented using diag. However, think of entries
|
#and A is represented using diag. However, think of entries
|
||||||
#to x and b as being points in space, not numbers
|
#to x and b as being points in space, not numbers
|
||||||
@ -122,25 +124,36 @@ class VectorizedMobject(Mobject):
|
|||||||
b[1::2] = 2*points[1:]
|
b[1::2] = 2*points[1:]
|
||||||
b[0] = points[0]
|
b[0] = points[0]
|
||||||
b[-1] = points[-1]
|
b[-1] = points[-1]
|
||||||
|
solve_func = lambda b : linalg.solve_banded(
|
||||||
|
(l, u), diag, b
|
||||||
|
)
|
||||||
|
if self.is_closed():
|
||||||
|
#Get equations to relate first and last points
|
||||||
|
matrix = diag_to_matrix((l, u), diag)
|
||||||
|
#last row handles second derivative
|
||||||
|
matrix[-1, [0, 1]] = matrix[0, [0, 1]]
|
||||||
|
#first row handles first derivative
|
||||||
|
matrix[0,:] = np.zeros(matrix.shape[1])
|
||||||
|
matrix[0,[0, -1]] = [1, 1]
|
||||||
|
b[0] = 2*points[0]
|
||||||
|
b[-1] = np.zeros(self.dim)
|
||||||
|
solve_func = lambda b : linalg.solve(matrix, b)
|
||||||
handle_pairs = np.zeros((2*num_handles, self.dim))
|
handle_pairs = np.zeros((2*num_handles, self.dim))
|
||||||
for i in range(self.dim):
|
for i in range(self.dim):
|
||||||
handle_pairs[:,i] = linalg.solve_banded(
|
handle_pairs[:,i] = solve_func(b[:,i])
|
||||||
(l, u), diag, b[:,i]
|
|
||||||
)
|
|
||||||
handles1 = handle_pairs[0::2]
|
handles1 = handle_pairs[0::2]
|
||||||
handles2 = handle_pairs[1::2]
|
handles2 = handle_pairs[1::2]
|
||||||
if self.is_closed():
|
|
||||||
#Ignore last point that was artificially added
|
|
||||||
#to smooth out the closing.
|
|
||||||
#TODO, is the the best say to handle this?
|
|
||||||
handles1[0] = handles1[-1]
|
|
||||||
points = points[:-1]
|
|
||||||
handles1 = handles1[:-1]
|
|
||||||
handles2 = handles2[:-1]
|
|
||||||
|
|
||||||
self.set_anchors_and_handles(points, handles1, handles2)
|
self.set_anchors_and_handles(points, handles1, handles2)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def close_if_needed(self, points):
|
||||||
|
if self.is_closed() and not np.all(points[0] == points[-1]):
|
||||||
|
points = np.append(
|
||||||
|
points,
|
||||||
|
[points[0]],
|
||||||
|
axis = 0
|
||||||
|
)
|
||||||
|
return points
|
||||||
|
|
||||||
def set_points(self, points, mode = "smooth"):
|
def set_points(self, points, mode = "smooth"):
|
||||||
points = np.array(points)
|
points = np.array(points)
|
||||||
@ -157,17 +170,63 @@ class VectorizedMobject(Mobject):
|
|||||||
## Information about line
|
## Information about line
|
||||||
|
|
||||||
def get_num_points(self):
|
def get_num_points(self):
|
||||||
pass
|
return (len(self.points) - 1)/3 + 1
|
||||||
|
|
||||||
def point_from_proportion(self, alpha):
|
def point_from_proportion(self, alpha):
|
||||||
pass
|
num_cubics = self.get_num_points()-1
|
||||||
|
interpoint_alpha = num_cubics*(alpha % (1./num_cubics))
|
||||||
|
index = 3*int(alpha*num_cubics)
|
||||||
|
cubic = bezier(self.points[index:index+4])
|
||||||
|
return cubic(interpoint_alpha)
|
||||||
|
|
||||||
|
|
||||||
|
## Alignment
|
||||||
|
def align_points_with_larger(self, larger_mobject):
|
||||||
|
assert(isinstance(larger_mobject, VectorizedMobject))
|
||||||
|
anchors, handles1, handles2 = self.get_anchors_and_handles()
|
||||||
|
old_n = len(anchors)
|
||||||
|
new_n = larger_mobject.get_num_points()
|
||||||
|
#Buff up list of anchor points to appropriate length
|
||||||
|
new_anchors = anchors[old_n*np.arange(new_n)/new_n]
|
||||||
|
#At first, handles are on anchor points
|
||||||
|
#the [2:] is because start has no handles
|
||||||
|
new_points = new_anchors.repeat(3, axis = 0)[2:]
|
||||||
|
#These indices indicate the spots between genuinely
|
||||||
|
#different anchor points in new_points list
|
||||||
|
indices = 3*(np.arange(old_n) * new_n / old_n)[1:]
|
||||||
|
new_points[indices+1] = handles1
|
||||||
|
new_points[indices+2] = handles2
|
||||||
|
self.set_points(new_points, mode = "handles_included")
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def get_point_mobject(self):
|
||||||
|
return VectorizedPoint(self.get_center())
|
||||||
|
|
||||||
|
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||||
|
attrs = [
|
||||||
|
"stroke_rgb",
|
||||||
|
"stroke_width",
|
||||||
|
"fill_rgb",
|
||||||
|
"fill_opacity",
|
||||||
|
]
|
||||||
|
for attr in attrs:
|
||||||
|
setattr(self, attr, interpolate(
|
||||||
|
getattr(mobject1, attr),
|
||||||
|
getattr(mobject2, attr),
|
||||||
|
alpha
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class VectorizedPoint(VectorizedMobject):
|
||||||
|
CONFIG = {
|
||||||
|
"color" : BLACK,
|
||||||
|
}
|
||||||
|
def __init__(self, location = ORIGIN, **kwargs):
|
||||||
|
VectorizedMobject.__init__(self, **kwargs)
|
||||||
|
self.set_points([location])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from scene import Scene
|
|||||||
from topics.complex_numbers import *
|
from topics.complex_numbers import *
|
||||||
|
|
||||||
DEFAULT_PLANE_CONFIG = {
|
DEFAULT_PLANE_CONFIG = {
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS
|
||||||
|
|
||||||
|
|
||||||
class SuccessiveComplexMultiplications(ComplexMultiplication):
|
class SuccessiveComplexMultiplications(ComplexMultiplication):
|
||||||
@ -144,7 +144,7 @@ class DrawSolutionsToZToTheNEqualsW(Scene):
|
|||||||
plane = ComplexPlane(**plane_config)
|
plane = ComplexPlane(**plane_config)
|
||||||
circle = Circle(
|
circle = Circle(
|
||||||
radius = radius*zoom_value,
|
radius = radius*zoom_value,
|
||||||
point_thickness = plane.point_thickness
|
stroke_width = plane.stroke_width
|
||||||
)
|
)
|
||||||
solutions = [
|
solutions = [
|
||||||
radius*np.exp(complex(0, 1)*(2*np.pi*k + theta)/n)
|
radius*np.exp(complex(0, 1)*(2*np.pi*k + theta)/n)
|
||||||
@ -222,7 +222,7 @@ class DrawComplexAngleAndMagnitude(Scene):
|
|||||||
Line(
|
Line(
|
||||||
start, end,
|
start, end,
|
||||||
color = color,
|
color = color,
|
||||||
point_thickness = self.plane.point_thickness
|
stroke_width = self.plane.stroke_width
|
||||||
)
|
)
|
||||||
for start, end, color in zip(
|
for start, end, color in zip(
|
||||||
[ORIGIN, point[0]*RIGHT, ORIGIN],
|
[ORIGIN, point[0]*RIGHT, ORIGIN],
|
||||||
|
@ -10,7 +10,7 @@ from helpers import *
|
|||||||
from scene import Scene
|
from scene import Scene
|
||||||
from number_line import NumberLineScene
|
from number_line import NumberLineScene
|
||||||
|
|
||||||
ARROW_CONFIG = {"point_thickness" : 2*DEFAULT_POINT_THICKNESS}
|
ARROW_CONFIG = {"stroke_width" : 2*DEFAULT_POINT_THICKNESS}
|
||||||
LIGHT_RED = RED_E
|
LIGHT_RED = RED_E
|
||||||
|
|
||||||
def matrix_to_string(matrix):
|
def matrix_to_string(matrix):
|
||||||
@ -55,7 +55,7 @@ class ShowMultiplication(NumberLineScene):
|
|||||||
def construct(self, num, show_original_line):
|
def construct(self, num, show_original_line):
|
||||||
config = {
|
config = {
|
||||||
"density" : max(abs(num), 1)*DEFAULT_POINT_DENSITY_1D,
|
"density" : max(abs(num), 1)*DEFAULT_POINT_DENSITY_1D,
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS
|
||||||
}
|
}
|
||||||
if abs(num) < 1:
|
if abs(num) < 1:
|
||||||
config["numerical_radius"] = SPACE_WIDTH/num
|
config["numerical_radius"] = SPACE_WIDTH/num
|
||||||
@ -114,7 +114,7 @@ class ExamplesOfNonlinearOneDimensionalTransforms(NumberLineScene):
|
|||||||
self.clear()
|
self.clear()
|
||||||
self.add(self.nonlinear)
|
self.add(self.nonlinear)
|
||||||
config = {
|
config = {
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS,
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS,
|
||||||
"density" : 5*DEFAULT_POINT_DENSITY_1D,
|
"density" : 5*DEFAULT_POINT_DENSITY_1D,
|
||||||
}
|
}
|
||||||
NumberLineScene.construct(self, **config)
|
NumberLineScene.construct(self, **config)
|
||||||
@ -143,7 +143,7 @@ class ShowTwoThenThree(ShowMultiplication):
|
|||||||
|
|
||||||
def construct(self):
|
def construct(self):
|
||||||
config = {
|
config = {
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS,
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS,
|
||||||
"density" : 6*DEFAULT_POINT_DENSITY_1D,
|
"density" : 6*DEFAULT_POINT_DENSITY_1D,
|
||||||
}
|
}
|
||||||
NumberLineScene.construct(self, **config)
|
NumberLineScene.construct(self, **config)
|
||||||
@ -162,7 +162,7 @@ class TransformScene2D(Scene):
|
|||||||
"x_radius" : 2*SPACE_WIDTH,
|
"x_radius" : 2*SPACE_WIDTH,
|
||||||
"y_radius" : 2*SPACE_WIDTH,
|
"y_radius" : 2*SPACE_WIDTH,
|
||||||
"density" : DEFAULT_POINT_DENSITY_1D*density_factor,
|
"density" : DEFAULT_POINT_DENSITY_1D*density_factor,
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS
|
||||||
}
|
}
|
||||||
if not use_faded_lines:
|
if not use_faded_lines:
|
||||||
config["x_faded_line_frequency"] = None
|
config["x_faded_line_frequency"] = None
|
||||||
@ -318,7 +318,7 @@ class ExamplesOfNonlinearTwoDimensionalTransformations(Scene):
|
|||||||
"x_radius" : 2*SPACE_WIDTH,
|
"x_radius" : 2*SPACE_WIDTH,
|
||||||
"y_radius" : 2*SPACE_WIDTH,
|
"y_radius" : 2*SPACE_WIDTH,
|
||||||
"density" : 3*DEFAULT_POINT_DENSITY_1D,
|
"density" : 3*DEFAULT_POINT_DENSITY_1D,
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS
|
||||||
}
|
}
|
||||||
number_plane = NumberPlane(**config)
|
number_plane = NumberPlane(**config)
|
||||||
numbers = number_plane.get_coordinate_labels()
|
numbers = number_plane.get_coordinate_labels()
|
||||||
@ -372,7 +372,7 @@ class TrickyExamplesOfNonlinearTwoDimensionalTransformations(Scene):
|
|||||||
"x_radius" : 1.2*SPACE_WIDTH,
|
"x_radius" : 1.2*SPACE_WIDTH,
|
||||||
"y_radius" : 1.2*SPACE_WIDTH,
|
"y_radius" : 1.2*SPACE_WIDTH,
|
||||||
"density" : 10*DEFAULT_POINT_DENSITY_1D,
|
"density" : 10*DEFAULT_POINT_DENSITY_1D,
|
||||||
"point_thickness" : 2*DEFAULT_POINT_THICKNESS
|
"stroke_width" : 2*DEFAULT_POINT_THICKNESS
|
||||||
}
|
}
|
||||||
number_plane = NumberPlane(**config)
|
number_plane = NumberPlane(**config)
|
||||||
phrase1, phrase2 = TextMobject([
|
phrase1, phrase2 = TextMobject([
|
||||||
|
@ -95,7 +95,7 @@ class OpenInterval(Mobject):
|
|||||||
|
|
||||||
class Piano(ImageMobject):
|
class Piano(ImageMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"point_thickness" : 1,
|
"stroke_width" : 1,
|
||||||
"invert" : False,
|
"invert" : False,
|
||||||
"scale_value" : 0.5
|
"scale_value" : 0.5
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ class Piano(ImageMobject):
|
|||||||
for count in range(14):
|
for count in range(14):
|
||||||
key = Mobject(
|
key = Mobject(
|
||||||
color = "white",
|
color = "white",
|
||||||
point_thickness = 1
|
stroke_width = 1
|
||||||
)
|
)
|
||||||
x0 = left + count*self.ivory_jump
|
x0 = left + count*self.ivory_jump
|
||||||
x1 = x0 + self.ivory_jump
|
x1 = x0 + self.ivory_jump
|
||||||
@ -664,7 +664,7 @@ class ConstructPiano(Scene):
|
|||||||
askew = deepcopy(keys[-1])
|
askew = deepcopy(keys[-1])
|
||||||
keys[-1].rotate_in_place(np.pi/5)
|
keys[-1].rotate_in_place(np.pi/5)
|
||||||
for key in keys:
|
for key in keys:
|
||||||
key.point_thickness = 1
|
key.stroke_width = 1
|
||||||
key_copy = deepcopy(key).to_corner(DOWN+LEFT)
|
key_copy = deepcopy(key).to_corner(DOWN+LEFT)
|
||||||
key_copy.scale_in_place(0.25)
|
key_copy.scale_in_place(0.25)
|
||||||
key_copy.shift(1.8*random.random()*SPACE_WIDTH*RIGHT)
|
key_copy.shift(1.8*random.random()*SPACE_WIDTH*RIGHT)
|
||||||
|
@ -8,7 +8,7 @@ from helpers import *
|
|||||||
|
|
||||||
class Stars(Mobject):
|
class Stars(Mobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"point_thickness" : 1,
|
"stroke_width" : 1,
|
||||||
"radius" : SPACE_WIDTH,
|
"radius" : SPACE_WIDTH,
|
||||||
"num_points" : 1000,
|
"num_points" : 1000,
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user