mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
d53412d738
9 changed files with 1511 additions and 201 deletions
|
@ -32,6 +32,7 @@ from topics.graph_scene import *
|
||||||
|
|
||||||
USE_ALMOST_FOURIER_BY_DEFAULT = True
|
USE_ALMOST_FOURIER_BY_DEFAULT = True
|
||||||
NUM_SAMPLES_FOR_FFT = 1000
|
NUM_SAMPLES_FOR_FFT = 1000
|
||||||
|
DEFAULT_COMPLEX_TO_REAL_FUNC = lambda z : z.real
|
||||||
|
|
||||||
|
|
||||||
def get_fourier_graph(
|
def get_fourier_graph(
|
||||||
|
@ -53,7 +54,7 @@ def get_fourier_graph(
|
||||||
graph = VMobject()
|
graph = VMobject()
|
||||||
graph.set_points_smoothly([
|
graph.set_points_smoothly([
|
||||||
axes.coords_to_point(
|
axes.coords_to_point(
|
||||||
x, 200.0*complex_to_real_func(y)/n_samples,
|
x, complex_to_real_func(y)/n_samples,
|
||||||
)
|
)
|
||||||
for x, y in zip(frequencies, fft_output[:n_samples//2])
|
for x, y in zip(frequencies, fft_output[:n_samples//2])
|
||||||
])
|
])
|
||||||
|
@ -62,18 +63,17 @@ def get_fourier_graph(
|
||||||
|
|
||||||
def get_fourier_transform(
|
def get_fourier_transform(
|
||||||
func, t_min, t_max,
|
func, t_min, t_max,
|
||||||
complex_to_real_func = lambda z : z.real,
|
complex_to_real_func = DEFAULT_COMPLEX_TO_REAL_FUNC,
|
||||||
use_almost_fourier = USE_ALMOST_FOURIER_BY_DEFAULT,
|
use_almost_fourier = USE_ALMOST_FOURIER_BY_DEFAULT,
|
||||||
|
**kwargs ##Just eats these
|
||||||
):
|
):
|
||||||
scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0
|
scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0
|
||||||
def fourier_transform(f):
|
def fourier_transform(f):
|
||||||
return scalar*scipy.integrate.quad(
|
z = scalar*scipy.integrate.quad(
|
||||||
lambda t : complex_to_real_func(
|
lambda t : func(t)*np.exp(complex(0, -TAU*f*t)),
|
||||||
# f(t) e^{-TAU*i*f*t}
|
|
||||||
func(t)*np.exp(complex(0, -TAU*f*t))
|
|
||||||
),
|
|
||||||
t_min, t_max
|
t_min, t_max
|
||||||
)[0]
|
)[0]
|
||||||
|
return complex_to_real_func(z)
|
||||||
return fourier_transform
|
return fourier_transform
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -939,11 +939,28 @@ class FourierMachineScene(Scene):
|
||||||
if not hasattr(self, "frequency_axes"):
|
if not hasattr(self, "frequency_axes"):
|
||||||
self.get_frequency_axes()
|
self.get_frequency_axes()
|
||||||
func = time_graph.underlying_function
|
func = time_graph.underlying_function
|
||||||
t_min = self.time_axes.x_min
|
t_axis = self.time_axes.x_axis
|
||||||
t_max = self.time_axes.x_max
|
t_min = t_axis.point_to_number(time_graph.points[0])
|
||||||
|
t_max = t_axis.point_to_number(time_graph.points[-1])
|
||||||
|
f_max = self.frequency_axes.x_max
|
||||||
|
# result = get_fourier_graph(
|
||||||
|
# self.frequency_axes, func, t_min, t_max,
|
||||||
|
# **kwargs
|
||||||
|
# )
|
||||||
|
# too_far_right_point_indices = [
|
||||||
|
# i
|
||||||
|
# for i, point in enumerate(result.points)
|
||||||
|
# if self.frequency_axes.x_axis.point_to_number(point) > f_max
|
||||||
|
# ]
|
||||||
|
# if too_far_right_point_indices:
|
||||||
|
# i = min(too_far_right_point_indices)
|
||||||
|
# prop = float(i)/len(result.points)
|
||||||
|
# result.pointwise_become_partial(result, 0, prop)
|
||||||
|
# return result
|
||||||
return self.frequency_axes.get_graph(
|
return self.frequency_axes.get_graph(
|
||||||
get_fourier_transform(func, t_min, t_max, **kwargs),
|
get_fourier_transform(func, t_min, t_max, **kwargs),
|
||||||
color = self.center_of_mass_color,
|
color = self.center_of_mass_color,
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_polarized_mobject(self, mobject, freq = 1.0):
|
def get_polarized_mobject(self, mobject, freq = 1.0):
|
||||||
|
@ -999,7 +1016,8 @@ class FourierMachineScene(Scene):
|
||||||
vector = Vector(UP, color = WHITE)
|
vector = Vector(UP, color = WHITE)
|
||||||
graph_copy = graph.copy()
|
graph_copy = graph.copy()
|
||||||
x_axis = self.time_axes.x_axis
|
x_axis = self.time_axes.x_axis
|
||||||
x_min, x_max = x_axis.x_min, x_axis.x_max
|
x_min = x_axis.point_to_number(graph.points[0])
|
||||||
|
x_max = x_axis.point_to_number(graph.points[-1])
|
||||||
def update_vector(vector, alpha):
|
def update_vector(vector, alpha):
|
||||||
x = interpolate(x_min, x_max, alpha)
|
x = interpolate(x_min, x_max, alpha)
|
||||||
vector.put_start_and_end_on(
|
vector.put_start_and_end_on(
|
||||||
|
@ -1016,7 +1034,13 @@ class FourierMachineScene(Scene):
|
||||||
origin = self.circle_plane.coords_to_point(0, 0)
|
origin = self.circle_plane.coords_to_point(0, 0)
|
||||||
graph_copy = polarized_graph.copy()
|
graph_copy = polarized_graph.copy()
|
||||||
def update_vector(vector, alpha):
|
def update_vector(vector, alpha):
|
||||||
point = graph_copy.point_from_proportion(alpha)
|
# Not sure why this is needed, but without smoothing
|
||||||
|
# out the alpha like this, the vector would occasionally
|
||||||
|
# jump around
|
||||||
|
point = center_of_mass([
|
||||||
|
graph_copy.point_from_proportion(alpha+d)
|
||||||
|
for d in np.linspace(-0.001, 0.001, 5)
|
||||||
|
])
|
||||||
vector.put_start_and_end_on_with_projection(origin, point)
|
vector.put_start_and_end_on_with_projection(origin, point)
|
||||||
return vector
|
return vector
|
||||||
return UpdateFromAlphaFunc(vector, update_vector, **config)
|
return UpdateFromAlphaFunc(vector, update_vector, **config)
|
||||||
|
@ -1299,6 +1323,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"initial_winding_frequency" : 3.0,
|
"initial_winding_frequency" : 3.0,
|
||||||
"center_of_mass_color" : RED,
|
"center_of_mass_color" : RED,
|
||||||
|
"center_of_mass_multiple" : 1,
|
||||||
}
|
}
|
||||||
def construct(self):
|
def construct(self):
|
||||||
self.remove(self.pi_creature)
|
self.remove(self.pi_creature)
|
||||||
|
@ -1581,10 +1606,11 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
|
|
||||||
def get_pol_graph_center_of_mass(self):
|
def get_pol_graph_center_of_mass(self):
|
||||||
pg = self.graph.polarized_mobject
|
pg = self.graph.polarized_mobject
|
||||||
result = center_of_mass([
|
result = center_of_mass(pg.get_anchors())
|
||||||
pg.point_from_proportion(alpha)
|
if self.center_of_mass_multiple != 1:
|
||||||
for alpha in np.linspace(0, 1, 1000)
|
mult = self.center_of_mass_multiple
|
||||||
])
|
origin = self.circle_plane.coords_to_point(0, 0)
|
||||||
|
result = mult*(result - origin) + origin
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def generate_fourier_dot_transform(self, fourier_graph):
|
def generate_fourier_dot_transform(self, fourier_graph):
|
||||||
|
@ -1626,7 +1652,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
|
|
||||||
def change_frequency(self, new_freq, **kwargs):
|
def change_frequency(self, new_freq, **kwargs):
|
||||||
kwargs["run_time"] = kwargs.get("run_time", 3)
|
kwargs["run_time"] = kwargs.get("run_time", 3)
|
||||||
kwargs["rate_func"] = kwargs.get(
|
rate_func = kwargs.pop(
|
||||||
"rate_func", bezier([0, 0, 1, 1])
|
"rate_func", bezier([0, 0, 1, 1])
|
||||||
)
|
)
|
||||||
added_anims = kwargs.get("added_anims", [])
|
added_anims = kwargs.get("added_anims", [])
|
||||||
|
@ -1645,6 +1671,8 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
anims.append(self.fourier_graph_dot_anim)
|
anims.append(self.fourier_graph_dot_anim)
|
||||||
if hasattr(self, "fourier_graph_drawing_update_anim"):
|
if hasattr(self, "fourier_graph_drawing_update_anim"):
|
||||||
anims.append(self.fourier_graph_drawing_update_anim)
|
anims.append(self.fourier_graph_drawing_update_anim)
|
||||||
|
for anim in anims:
|
||||||
|
anim.rate_func = rate_func
|
||||||
anims += added_anims
|
anims += added_anims
|
||||||
self.play(*anims, **kwargs)
|
self.play(*anims, **kwargs)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
116
camera/camera.py
116
camera/camera.py
|
@ -30,7 +30,7 @@ class Camera(object):
|
||||||
"max_allowable_norm" : 2*SPACE_WIDTH,
|
"max_allowable_norm" : 2*SPACE_WIDTH,
|
||||||
"image_mode" : "RGBA",
|
"image_mode" : "RGBA",
|
||||||
"n_rgb_coords" : 4,
|
"n_rgb_coords" : 4,
|
||||||
"background_alpha" : 0, #Out of color_max_val
|
"background_alpha" : 0, #Out of rgb_max_val
|
||||||
"pixel_array_dtype" : 'uint8',
|
"pixel_array_dtype" : 'uint8',
|
||||||
"use_z_coordinate_for_display_order" : False,
|
"use_z_coordinate_for_display_order" : False,
|
||||||
# z_buff_func is only used if the flag above is set to True.
|
# z_buff_func is only used if the flag above is set to True.
|
||||||
|
@ -40,7 +40,7 @@ class Camera(object):
|
||||||
|
|
||||||
def __init__(self, background = None, **kwargs):
|
def __init__(self, background = None, **kwargs):
|
||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
self.color_max_val = np.iinfo(self.pixel_array_dtype).max
|
self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max
|
||||||
self.init_background()
|
self.init_background()
|
||||||
self.resize_space_shape()
|
self.resize_space_shape()
|
||||||
self.reset()
|
self.reset()
|
||||||
|
@ -92,7 +92,7 @@ class Camera(object):
|
||||||
retval = np.array(pixel_array)
|
retval = np.array(pixel_array)
|
||||||
if convert_from_floats:
|
if convert_from_floats:
|
||||||
retval = np.apply_along_axis(
|
retval = np.apply_along_axis(
|
||||||
lambda f : (f * self.color_max_val).astype(self.pixel_array_dtype),
|
lambda f : (f * self.rgb_max_val).astype(self.pixel_array_dtype),
|
||||||
2,
|
2,
|
||||||
retval)
|
retval)
|
||||||
return retval
|
return retval
|
||||||
|
@ -177,29 +177,30 @@ class Camera(object):
|
||||||
|
|
||||||
def capture_mobjects(self, mobjects, **kwargs):
|
def capture_mobjects(self, mobjects, **kwargs):
|
||||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||||
vmobjects = []
|
|
||||||
for mobject in mobjects:
|
# Organize this list into batches of the same type, and
|
||||||
if isinstance(mobject, VMobject):
|
# apply corresponding function to those batches
|
||||||
vmobjects.append(mobject)
|
type_func_pairs = [
|
||||||
elif len(vmobjects) > 0:
|
(VMobject, self.display_multiple_vectorized_mobjects),
|
||||||
self.display_multiple_vectorized_mobjects(vmobjects)
|
(PMobject, self.display_multiple_point_cloud_mobjects),
|
||||||
vmobjects = []
|
(ImageMobject, self.display_multiple_image_mobjects),
|
||||||
|
(Mobject, lambda batch : batch), #Do nothing
|
||||||
if isinstance(mobject, PMobject):
|
]
|
||||||
self.display_point_cloud(
|
def get_mobject_type(mobject):
|
||||||
mobject.points, mobject.rgbas,
|
for mobject_type, func in type_func_pairs:
|
||||||
self.adjusted_thickness(mobject.stroke_width)
|
if isinstance(mobject, mobject_type):
|
||||||
)
|
return mobject_type
|
||||||
elif isinstance(mobject, ImageMobject):
|
raise Exception(
|
||||||
self.display_image_mobject(mobject)
|
"Trying to display something which is not of type Mobject"
|
||||||
elif isinstance(mobject, Mobject):
|
)
|
||||||
pass #Remainder of loop will handle submobjects
|
batch_type_pairs = batch_by_property(mobjects, get_mobject_type)
|
||||||
else:
|
|
||||||
raise Exception(
|
#Display in these batches
|
||||||
"Unknown mobject type: " + mobject.__class__.__name__
|
for batch, batch_type in batch_type_pairs:
|
||||||
)
|
#check what the type is, and call the appropriate function
|
||||||
#TODO, more? Call out if it's unknown?
|
for mobject_type, func in type_func_pairs:
|
||||||
self.display_multiple_vectorized_mobjects(vmobjects)
|
if batch_type == mobject_type:
|
||||||
|
func(batch)
|
||||||
|
|
||||||
## Methods associated with svg rendering
|
## Methods associated with svg rendering
|
||||||
|
|
||||||
|
@ -215,12 +216,12 @@ class Camera(object):
|
||||||
def display_multiple_vectorized_mobjects(self, vmobjects):
|
def display_multiple_vectorized_mobjects(self, vmobjects):
|
||||||
if len(vmobjects) == 0:
|
if len(vmobjects) == 0:
|
||||||
return
|
return
|
||||||
batches = batch_by_property(
|
batch_file_pairs = batch_by_property(
|
||||||
vmobjects,
|
vmobjects,
|
||||||
lambda vm : vm.get_background_image_file()
|
lambda vm : vm.get_background_image_file()
|
||||||
)
|
)
|
||||||
for batch in batches:
|
for batch, file_name in batch_file_pairs:
|
||||||
if batch[0].get_background_image_file():
|
if file_name:
|
||||||
self.display_multiple_background_colored_vmobject(batch)
|
self.display_multiple_background_colored_vmobject(batch)
|
||||||
else:
|
else:
|
||||||
self.display_multiple_non_background_colored_vmobjects(batch)
|
self.display_multiple_non_background_colored_vmobjects(batch)
|
||||||
|
@ -252,7 +253,7 @@ class Camera(object):
|
||||||
stroke_hex = rgb_to_hex(stroke_rgb)
|
stroke_hex = rgb_to_hex(stroke_rgb)
|
||||||
pen = aggdraw.Pen(stroke_hex, stroke_width)
|
pen = aggdraw.Pen(stroke_hex, stroke_width)
|
||||||
|
|
||||||
fill_opacity = int(self.color_max_val*vmobject.get_fill_opacity())
|
fill_opacity = int(self.rgb_max_val*vmobject.get_fill_opacity())
|
||||||
if fill_opacity == 0:
|
if fill_opacity == 0:
|
||||||
fill = None
|
fill = None
|
||||||
else:
|
else:
|
||||||
|
@ -305,13 +306,19 @@ class Camera(object):
|
||||||
def display_multiple_background_colored_vmobject(self, cvmobjects):
|
def display_multiple_background_colored_vmobject(self, cvmobjects):
|
||||||
displayer = self.get_background_colored_vmobject_displayer()
|
displayer = self.get_background_colored_vmobject_displayer()
|
||||||
cvmobject_pixel_array = displayer.display(*cvmobjects)
|
cvmobject_pixel_array = displayer.display(*cvmobjects)
|
||||||
self.pixel_array[:,:] = np.maximum(
|
self.overlay_rgba_array(cvmobject_pixel_array)
|
||||||
self.pixel_array, cvmobject_pixel_array
|
|
||||||
)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
## Methods for other rendering
|
## Methods for other rendering
|
||||||
|
|
||||||
|
def display_multiple_point_cloud_mobjects(self, pmobjects):
|
||||||
|
for pmobject in pmobjects:
|
||||||
|
self.display_point_cloud(
|
||||||
|
pmobject.points,
|
||||||
|
pmobject.rgbas,
|
||||||
|
self.adjusted_thickness(pmobject.stroke_width)
|
||||||
|
)
|
||||||
|
|
||||||
def display_point_cloud(self, points, rgbas, thickness):
|
def display_point_cloud(self, points, rgbas, thickness):
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
return
|
return
|
||||||
|
@ -322,7 +329,7 @@ class Camera(object):
|
||||||
)
|
)
|
||||||
rgba_len = self.pixel_array.shape[2]
|
rgba_len = self.pixel_array.shape[2]
|
||||||
|
|
||||||
rgbas = (self.color_max_val*rgbas).astype(self.pixel_array_dtype)
|
rgbas = (self.rgb_max_val*rgbas).astype(self.pixel_array_dtype)
|
||||||
target_len = len(pixel_coords)
|
target_len = len(pixel_coords)
|
||||||
factor = target_len/len(rgbas)
|
factor = target_len/len(rgbas)
|
||||||
rgbas = np.array([rgbas]*factor).reshape((target_len, rgba_len))
|
rgbas = np.array([rgbas]*factor).reshape((target_len, rgba_len))
|
||||||
|
@ -342,6 +349,10 @@ class Camera(object):
|
||||||
new_pa[indices] = rgbas
|
new_pa[indices] = rgbas
|
||||||
self.pixel_array = new_pa.reshape((ph, pw, rgba_len))
|
self.pixel_array = new_pa.reshape((ph, pw, rgba_len))
|
||||||
|
|
||||||
|
def display_multiple_image_mobjects(self, image_mobjects):
|
||||||
|
for image_mobject in image_mobjects:
|
||||||
|
self.display_image_mobject(image_mobject)
|
||||||
|
|
||||||
def display_image_mobject(self, image_mobject):
|
def display_image_mobject(self, image_mobject):
|
||||||
corner_coords = self.points_to_pixel_coords(image_mobject.points)
|
corner_coords = self.points_to_pixel_coords(image_mobject.points)
|
||||||
ul_coords, ur_coords, dl_coords = corner_coords
|
ul_coords, ur_coords, dl_coords = corner_coords
|
||||||
|
@ -410,17 +421,23 @@ class Camera(object):
|
||||||
self.overlay_rgba_array(image)
|
self.overlay_rgba_array(image)
|
||||||
|
|
||||||
def overlay_rgba_array(self, arr):
|
def overlay_rgba_array(self, arr):
|
||||||
# """ Overlays arr onto self.pixel_array with relevant alphas"""
|
fg = arr
|
||||||
bg, fg = fdiv(self.pixel_array, self.color_max_val), fdiv(arr, self.color_max_val)
|
bg = self.pixel_array
|
||||||
bga, fga = [arr[:,:,3:] for arr in bg, fg]
|
# rgba_max_val = self.rgb_max_val
|
||||||
alpha_sum = fga + (1-fga)*bga
|
src_rgb, src_a, dst_rgb, dst_a = [
|
||||||
with np.errstate(divide = 'ignore', invalid='ignore'):
|
a.astype(np.float32)/self.rgb_max_val
|
||||||
bg[:,:,:3] = reduce(op.add, [
|
for a in fg[...,:3], fg[...,3], bg[...,:3], bg[...,3]
|
||||||
np.divide(fg[:,:,:3]*fga, alpha_sum),
|
]
|
||||||
np.divide(bg[:,:,:3]*(1-fga)*bga, alpha_sum),
|
|
||||||
])
|
out_a = src_a + dst_a*(1.0-src_a)
|
||||||
bg[:,:,3:] = 1 - (1 - bga)*(1 - fga)
|
out_rgb = fdiv(
|
||||||
self.pixel_array = (self.color_max_val*bg).astype(self.pixel_array_dtype)
|
src_rgb*src_a[..., None] + \
|
||||||
|
dst_rgb*dst_a[..., None]*(1.0-src_a[..., None]),
|
||||||
|
out_a[..., None]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pixel_array[..., :3] = out_rgb*self.rgb_max_val
|
||||||
|
self.pixel_array[..., 3] = out_a*self.rgb_max_val
|
||||||
|
|
||||||
def align_points_to_camera(self, points):
|
def align_points_to_camera(self, points):
|
||||||
## This is where projection should live
|
## This is where projection should live
|
||||||
|
@ -537,8 +554,7 @@ class BackgroundColoredVMobjectDisplayer(object):
|
||||||
mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
|
mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
|
||||||
return self.resize_background_array(background_array, width, height, mode)
|
return self.resize_background_array(background_array, width, height, mode)
|
||||||
|
|
||||||
def get_background_array(self, cvmobject):
|
def get_background_array(self, file_name):
|
||||||
file_name = cvmobject.get_background_image_file()
|
|
||||||
if file_name in self.file_name_to_pixel_array_map:
|
if file_name in self.file_name_to_pixel_array_map:
|
||||||
return self.file_name_to_pixel_array_map[file_name]
|
return self.file_name_to_pixel_array_map[file_name]
|
||||||
full_path = get_full_raster_image_path(file_name)
|
full_path = get_full_raster_image_path(file_name)
|
||||||
|
@ -553,12 +569,12 @@ class BackgroundColoredVMobjectDisplayer(object):
|
||||||
return array
|
return array
|
||||||
|
|
||||||
def display(self, *cvmobjects):
|
def display(self, *cvmobjects):
|
||||||
batches = batch_by_property(
|
batch_image_file_pairs = batch_by_property(
|
||||||
cvmobjects, lambda cv : cv.get_background_image_file()
|
cvmobjects, lambda cv : cv.get_background_image_file()
|
||||||
)
|
)
|
||||||
curr_array = None
|
curr_array = None
|
||||||
for batch in batches:
|
for batch, image_file in batch_image_file_pairs:
|
||||||
background_array = self.get_background_array(batch[0])
|
background_array = self.get_background_array(image_file)
|
||||||
for cvmobject in batch:
|
for cvmobject in batch:
|
||||||
self.camera.display_vectorized(cvmobject, self.canvas)
|
self.camera.display_vectorized(cvmobject, self.canvas)
|
||||||
self.canvas.flush()
|
self.canvas.flush()
|
||||||
|
|
20
helpers.py
20
helpers.py
|
@ -227,22 +227,30 @@ def adjacent_pairs(objects):
|
||||||
return zip(objects, list(objects[1:])+[objects[0]])
|
return zip(objects, list(objects[1:])+[objects[0]])
|
||||||
|
|
||||||
def batch_by_property(items, property_func):
|
def batch_by_property(items, property_func):
|
||||||
batches = []
|
"""
|
||||||
def add_batch(batch):
|
Takes in a list, and returns a list of tuples, (batch, prop)
|
||||||
|
such that all items in a batch have the same output when
|
||||||
|
put into property_func, and such that chaining all these
|
||||||
|
batches together would give the original list.
|
||||||
|
"""
|
||||||
|
batch_prop_pairs = []
|
||||||
|
def add_batch_prop_pair(batch):
|
||||||
if len(batch) > 0:
|
if len(batch) > 0:
|
||||||
batches.append(batch)
|
batch_prop_pairs.append(
|
||||||
|
(batch, property_func(batch[0]))
|
||||||
|
)
|
||||||
curr_batch = []
|
curr_batch = []
|
||||||
curr_prop = None
|
curr_prop = None
|
||||||
for item in items:
|
for item in items:
|
||||||
prop = property_func(item)
|
prop = property_func(item)
|
||||||
if prop != curr_prop:
|
if prop != curr_prop:
|
||||||
add_batch(curr_batch)
|
add_batch_prop_pair(curr_batch)
|
||||||
curr_prop = prop
|
curr_prop = prop
|
||||||
curr_batch = [item]
|
curr_batch = [item]
|
||||||
else:
|
else:
|
||||||
curr_batch.append(item)
|
curr_batch.append(item)
|
||||||
add_batch(curr_batch)
|
add_batch_prop_pair(curr_batch)
|
||||||
return batches
|
return batch_prop_pairs
|
||||||
|
|
||||||
def complex_to_R3(complex_num):
|
def complex_to_R3(complex_num):
|
||||||
return np.array((complex_num.real, complex_num.imag, 0))
|
return np.array((complex_num.real, complex_num.imag, 0))
|
||||||
|
|
|
@ -141,9 +141,15 @@ class TexMobject(SVGMobject):
|
||||||
self.submobjects = new_submobjects
|
self.submobjects = new_submobjects
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_parts_by_tex(self, tex, substring = True):
|
def get_parts_by_tex(self, tex, substring = True, case_sensitive = True):
|
||||||
def test(tex1, tex2):
|
def test(tex1, tex2):
|
||||||
return tex1 == tex2 or (substring and tex1 in tex2)
|
if not case_sensitive:
|
||||||
|
tex1 = tex1.lower()
|
||||||
|
tex2 = tex2.lower()
|
||||||
|
if substring:
|
||||||
|
return tex1 in tex2
|
||||||
|
else:
|
||||||
|
return tex1 == tex2
|
||||||
|
|
||||||
tex_submobjects = filter(
|
tex_submobjects = filter(
|
||||||
lambda m : isinstance(m, TexMobject),
|
lambda m : isinstance(m, TexMobject),
|
||||||
|
|
|
@ -122,7 +122,7 @@ class VMobject(Mobject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_fill_rgb(self):
|
def get_fill_rgb(self):
|
||||||
return self.fill_rgb
|
return np.clip(self.fill_rgb, 0, 1)
|
||||||
|
|
||||||
def get_fill_color(self):
|
def get_fill_color(self):
|
||||||
try:
|
try:
|
||||||
|
@ -135,7 +135,7 @@ class VMobject(Mobject):
|
||||||
return np.clip(self.fill_opacity, 0, 1)
|
return np.clip(self.fill_opacity, 0, 1)
|
||||||
|
|
||||||
def get_stroke_rgb(self):
|
def get_stroke_rgb(self):
|
||||||
return self.stroke_rgb
|
return np.clip(self.stroke_rgb, 0, 1)
|
||||||
|
|
||||||
def get_stroke_color(self):
|
def get_stroke_color(self):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -35,90 +35,6 @@ VELOCITY_COLOR = GREEN
|
||||||
#### Warning, scenes here not updated based on most recent GraphScene changes #######
|
#### Warning, scenes here not updated based on most recent GraphScene changes #######
|
||||||
|
|
||||||
|
|
||||||
class Car(SVGMobject):
|
|
||||||
CONFIG = {
|
|
||||||
"file_name" : "Car",
|
|
||||||
"height" : 1,
|
|
||||||
"color" : "#BBBBBB",
|
|
||||||
}
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
SVGMobject.__init__(self, **kwargs)
|
|
||||||
self.scale_to_fit_height(self.height)
|
|
||||||
self.set_stroke(color = WHITE, width = 0)
|
|
||||||
self.set_fill(self.color, opacity = 1)
|
|
||||||
|
|
||||||
randy = Randolph(mode = "happy")
|
|
||||||
randy.scale_to_fit_height(0.6*self.get_height())
|
|
||||||
randy.stretch(0.8, 0)
|
|
||||||
randy.look(RIGHT)
|
|
||||||
randy.move_to(self)
|
|
||||||
randy.shift(0.07*self.height*(RIGHT+UP))
|
|
||||||
self.randy = randy
|
|
||||||
self.add_to_back(randy)
|
|
||||||
|
|
||||||
orientation_line = Line(self.get_left(), self.get_right())
|
|
||||||
orientation_line.set_stroke(width = 0)
|
|
||||||
self.add(orientation_line)
|
|
||||||
self.orientation_line = orientation_line
|
|
||||||
|
|
||||||
self.add_treds_to_tires()
|
|
||||||
|
|
||||||
def move_to(self, point_or_mobject):
|
|
||||||
vect = rotate_vector(
|
|
||||||
UP+LEFT, self.orientation_line.get_angle()
|
|
||||||
)
|
|
||||||
self.next_to(point_or_mobject, vect, buff = 0)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_front_line(self):
|
|
||||||
return DashedLine(
|
|
||||||
self.get_corner(UP+RIGHT),
|
|
||||||
self.get_corner(DOWN+RIGHT),
|
|
||||||
color = DISTANCE_COLOR,
|
|
||||||
dashed_segment_length = 0.05,
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_treds_to_tires(self):
|
|
||||||
for tire in self.get_tires():
|
|
||||||
radius = tire.get_width()/2
|
|
||||||
center = tire.get_center()
|
|
||||||
tred = Line(
|
|
||||||
0.9*radius*RIGHT, 1.4*radius*RIGHT,
|
|
||||||
stroke_width = 2,
|
|
||||||
color = BLACK
|
|
||||||
)
|
|
||||||
tred.rotate_in_place(np.pi/4)
|
|
||||||
for theta in np.arange(0, 2*np.pi, np.pi/4):
|
|
||||||
new_tred = tred.copy()
|
|
||||||
new_tred.rotate(theta)
|
|
||||||
new_tred.shift(center)
|
|
||||||
tire.add(new_tred)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_tires(self):
|
|
||||||
return VGroup(self[1][1], self[1][3])
|
|
||||||
|
|
||||||
class MoveCar(ApplyMethod):
|
|
||||||
CONFIG = {
|
|
||||||
"moving_forward" : True,
|
|
||||||
}
|
|
||||||
def __init__(self, car, target_point, **kwargs):
|
|
||||||
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
|
|
||||||
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
|
|
||||||
distance = np.linalg.norm(displacement)
|
|
||||||
if not self.moving_forward:
|
|
||||||
distance *= -1
|
|
||||||
tire_radius = car.get_tires()[0].get_width()/2
|
|
||||||
self.total_tire_radians = -distance/tire_radius
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
ApplyMethod.update_mobject(self, alpha)
|
|
||||||
if alpha == 0:
|
|
||||||
return
|
|
||||||
radians = alpha*self.total_tire_radians
|
|
||||||
for tire in self.mobject.get_tires():
|
|
||||||
tire.rotate_in_place(radians)
|
|
||||||
|
|
||||||
class IncrementNumber(Succession):
|
class IncrementNumber(Succession):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"start_num" : 0,
|
"start_num" : 0,
|
||||||
|
|
|
@ -219,7 +219,29 @@ class Axes(VGroup):
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
def input_to_graph_point(self, x, graph):
|
def input_to_graph_point(self, x, graph):
|
||||||
return self.coords_to_point(x, graph.underlying_function(x))
|
if hasattr(graph, "underlying_function"):
|
||||||
|
return self.coords_to_point(x, graph.underlying_function(x))
|
||||||
|
else:
|
||||||
|
#binary search
|
||||||
|
lh, rh = 0, 1
|
||||||
|
while abs(lh - rh) > 0.001:
|
||||||
|
mh = np.mean([lh, rh])
|
||||||
|
hands = [lh, mh, rh]
|
||||||
|
points = map(graph.point_from_proportion, hands)
|
||||||
|
lx, mx, rx = map(self.x_axis.point_to_number, points)
|
||||||
|
if lx <= x and rx >= x:
|
||||||
|
if mx > x:
|
||||||
|
rh = mh
|
||||||
|
else:
|
||||||
|
lh = mh
|
||||||
|
elif lx <= x and rx <= x:
|
||||||
|
return points[2]
|
||||||
|
elif lx >= x and rx >= x:
|
||||||
|
return points[0]
|
||||||
|
elif lx > x and rx < x:
|
||||||
|
lh, rh = rh, lh
|
||||||
|
return points[1]
|
||||||
|
|
||||||
|
|
||||||
class ThreeDAxes(Axes):
|
class ThreeDAxes(Axes):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
|
|
@ -486,6 +486,104 @@ class ThoughtBubble(Bubble):
|
||||||
self.submobjects[-1].set_fill(GREEN_SCREEN, opacity = 1)
|
self.submobjects[-1].set_fill(GREEN_SCREEN, opacity = 1)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
class Car(SVGMobject):
|
||||||
|
CONFIG = {
|
||||||
|
"file_name" : "Car",
|
||||||
|
"height" : 1,
|
||||||
|
"color" : LIGHT_GREY,
|
||||||
|
"light_colors" : [BLACK, BLACK],
|
||||||
|
}
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
SVGMobject.__init__(self, **kwargs)
|
||||||
|
self.scale_to_fit_height(self.height)
|
||||||
|
self.set_stroke(color = WHITE, width = 0)
|
||||||
|
self.set_fill(self.color, opacity = 1)
|
||||||
|
|
||||||
|
from topics.characters import Randolph
|
||||||
|
randy = Randolph(mode = "happy")
|
||||||
|
randy.scale_to_fit_height(0.6*self.get_height())
|
||||||
|
randy.stretch(0.8, 0)
|
||||||
|
randy.look(RIGHT)
|
||||||
|
randy.move_to(self)
|
||||||
|
randy.shift(0.07*self.height*(RIGHT+UP))
|
||||||
|
self.randy = self.pi_creature = randy
|
||||||
|
self.add_to_back(randy)
|
||||||
|
|
||||||
|
orientation_line = Line(self.get_left(), self.get_right())
|
||||||
|
orientation_line.set_stroke(width = 0)
|
||||||
|
self.add(orientation_line)
|
||||||
|
self.orientation_line = orientation_line
|
||||||
|
|
||||||
|
for light, color in zip(self.get_lights(), self.light_colors):
|
||||||
|
light.set_fill(color, 1)
|
||||||
|
light.is_subpath = False
|
||||||
|
|
||||||
|
self.add_treds_to_tires()
|
||||||
|
|
||||||
|
def move_to(self, point_or_mobject):
|
||||||
|
vect = rotate_vector(
|
||||||
|
UP+LEFT, self.orientation_line.get_angle()
|
||||||
|
)
|
||||||
|
self.next_to(point_or_mobject, vect, buff = 0)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_front_line(self):
|
||||||
|
return DashedLine(
|
||||||
|
self.get_corner(UP+RIGHT),
|
||||||
|
self.get_corner(DOWN+RIGHT),
|
||||||
|
color = DISTANCE_COLOR,
|
||||||
|
dashed_segment_length = 0.05,
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_treds_to_tires(self):
|
||||||
|
for tire in self.get_tires():
|
||||||
|
radius = tire.get_width()/2
|
||||||
|
center = tire.get_center()
|
||||||
|
tred = Line(
|
||||||
|
0.9*radius*RIGHT, 1.4*radius*RIGHT,
|
||||||
|
stroke_width = 2,
|
||||||
|
color = BLACK
|
||||||
|
)
|
||||||
|
tred.rotate_in_place(np.pi/4)
|
||||||
|
for theta in np.arange(0, 2*np.pi, np.pi/4):
|
||||||
|
new_tred = tred.copy()
|
||||||
|
new_tred.rotate(theta, about_point = ORIGIN)
|
||||||
|
new_tred.shift(center)
|
||||||
|
tire.add(new_tred)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_tires(self):
|
||||||
|
return VGroup(self[1][1], self[1][3])
|
||||||
|
|
||||||
|
def get_lights(self):
|
||||||
|
return VGroup(self.get_front_light(), self.get_rear_light())
|
||||||
|
|
||||||
|
def get_front_light(self):
|
||||||
|
return self[1][5]
|
||||||
|
|
||||||
|
def get_rear_light(self):
|
||||||
|
return self[1][8]
|
||||||
|
|
||||||
|
class MoveCar(ApplyMethod):
|
||||||
|
CONFIG = {
|
||||||
|
"moving_forward" : True,
|
||||||
|
}
|
||||||
|
def __init__(self, car, target_point, **kwargs):
|
||||||
|
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
|
||||||
|
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
|
||||||
|
distance = np.linalg.norm(displacement)
|
||||||
|
if not self.moving_forward:
|
||||||
|
distance *= -1
|
||||||
|
tire_radius = car.get_tires()[0].get_width()/2
|
||||||
|
self.total_tire_radians = -distance/tire_radius
|
||||||
|
|
||||||
|
def update_mobject(self, alpha):
|
||||||
|
ApplyMethod.update_mobject(self, alpha)
|
||||||
|
if alpha == 0:
|
||||||
|
return
|
||||||
|
radians = alpha*self.total_tire_radians
|
||||||
|
for tire in self.mobject.get_tires():
|
||||||
|
tire.rotate_in_place(radians)
|
||||||
|
|
||||||
#TODO: Where should this live?
|
#TODO: Where should this live?
|
||||||
class Broadcast(LaggedStart):
|
class Broadcast(LaggedStart):
|
||||||
|
|
Loading…
Add table
Reference in a new issue