mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
Merge branch 'master' into lighthouse2
This commit is contained in:
commit
a3d21911fd
9 changed files with 410 additions and 72 deletions
|
@ -39,55 +39,46 @@ class GaussianDistributionWrapper(Line):
|
||||||
"""
|
"""
|
||||||
This is meant to encode a 2d normal distribution as
|
This is meant to encode a 2d normal distribution as
|
||||||
a mobject (so as to be able to have it be interpolated
|
a mobject (so as to be able to have it be interpolated
|
||||||
during animations). It is a line whose start_point coordinates
|
during animations). It is a line whose center is the mean
|
||||||
encode the coordinates of mu, and whose end_point - start_point
|
mu of a distribution, and whose radial vector (center to end)
|
||||||
encodes the coordinates of sigma.
|
is the distribution's standard deviation
|
||||||
"""
|
"""
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"stroke_width" : 0,
|
"stroke_width" : 0,
|
||||||
"mu_x" : 0,
|
"mu" : ORIGIN,
|
||||||
"sigma_x" : 1,
|
"sigma" : RIGHT,
|
||||||
"mu_y" : 0,
|
|
||||||
"sigma_y" : 0,
|
|
||||||
}
|
}
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Line.__init__(self, ORIGIN, RIGHT, **kwargs)
|
Line.__init__(self, ORIGIN, RIGHT, **kwargs)
|
||||||
self.change_parameters(self.mu_x, self.mu_y, self.sigma_x, self.sigma_y)
|
self.change_parameters(self.mu, self.sigma)
|
||||||
|
|
||||||
def change_parameters(self, mu_x = None, mu_y = None, sigma_x = None, sigma_y = None):
|
def change_parameters(self, mu = None, sigma = None):
|
||||||
curr_parameters = self.get_parameteters()
|
curr_mu, curr_sigma = self.get_parameters()
|
||||||
args = [mu_x, mu_y, sigma_x, sigma_y]
|
mu = mu if mu is not None else curr_mu
|
||||||
new_parameters = [
|
sigma = sigma if sigma is not None else curr_sigma
|
||||||
arg or curr
|
self.put_start_and_end_on(mu - sigma, mu + sigma)
|
||||||
for curr, arg in zip(curr_parameters, args)
|
|
||||||
]
|
|
||||||
mu_x, mu_y, sigma_x, sigma_y = new_parameters
|
|
||||||
mu_point = mu_x*RIGHT + mu_y*UP
|
|
||||||
sigma_vect = sigma_x*RIGHT + sigma_y*UP
|
|
||||||
self.put_start_and_end_on(mu_point, mu_point + sigma_vect)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_parameteters(self):
|
def get_parameters(self):
|
||||||
""" Return mu_x, mu_y, sigma_x, sigma_y"""
|
""" Return mu_x, mu_y, sigma_x, sigma_y"""
|
||||||
start, end = self.get_start_and_end()
|
center, end = self.get_center(), self.get_end()
|
||||||
return tuple(it.chain(start[:2], (end - start)[:2]))
|
return center, end-center
|
||||||
|
|
||||||
def get_random_points(self, size = 1):
|
def get_random_points(self, size = 1):
|
||||||
mu_x, mu_y, sigma_x, sigma_y = self.get_parameteters()
|
mu, sigma = self.get_parameters()
|
||||||
x_vals = np.random.normal(mu_x, sigma_x, size)
|
|
||||||
y_vals = np.random.normal(mu_y, sigma_y, size)
|
|
||||||
return np.array([
|
return np.array([
|
||||||
x*RIGHT + y*UP
|
np.array([
|
||||||
for x, y in zip(x_vals, y_vals)
|
np.random.normal(mu_coord, sigma_coord)
|
||||||
|
for mu_coord, sigma_coord in zip(mu, sigma)
|
||||||
|
])
|
||||||
|
for x in range(size)
|
||||||
])
|
])
|
||||||
|
|
||||||
class ProbabalisticMobjectCloud(ContinualAnimation):
|
class ProbabalisticMobjectCloud(ContinualAnimation):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"fill_opacity" : 0.25,
|
"fill_opacity" : 0.25,
|
||||||
"n_copies" : 100,
|
"n_copies" : 100,
|
||||||
"gaussian_distribution_wrapper_config" : {
|
"gaussian_distribution_wrapper_config" : {}
|
||||||
"sigma_x" : 1,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
def __init__(self, prototype, **kwargs):
|
def __init__(self, prototype, **kwargs):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
|
@ -142,6 +133,111 @@ class ProbabalisticVectorCloud(ProbabalisticMobjectCloud):
|
||||||
point
|
point
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class RadarDish(SVGMobject):
|
||||||
|
CONFIG = {
|
||||||
|
"file_name" : "radar_dish",
|
||||||
|
"fill_color" : LIGHT_GREY,
|
||||||
|
"stroke_color" : WHITE,
|
||||||
|
"stroke_width" : 1,
|
||||||
|
"height" : 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Plane(SVGMobject):
|
||||||
|
CONFIG = {
|
||||||
|
"file_name" : "plane",
|
||||||
|
"color" : GREY,
|
||||||
|
"height" : 1,
|
||||||
|
}
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
SVGMobject.__init__(self, **kwargs)
|
||||||
|
self.rotate(-TAU/8)
|
||||||
|
|
||||||
|
class RadarPulseSingleton(ContinualAnimation):
|
||||||
|
CONFIG = {
|
||||||
|
"speed" : 3.0,
|
||||||
|
"direction" : RIGHT,
|
||||||
|
"start_up_time" : 0,
|
||||||
|
"fade_in_time" : 0.5,
|
||||||
|
"color" : WHITE,
|
||||||
|
"stroke_width" : 3,
|
||||||
|
}
|
||||||
|
def __init__(self, radar_dish, target, **kwargs):
|
||||||
|
digest_config(self, kwargs)
|
||||||
|
self.direction = self.direction/np.linalg.norm(self.direction)
|
||||||
|
self.radar_dish = radar_dish
|
||||||
|
self.target = target
|
||||||
|
self.reflection_distance = None
|
||||||
|
self.arc = Arc(
|
||||||
|
start_angle = -30*DEGREES,
|
||||||
|
angle = 60*DEGREES,
|
||||||
|
)
|
||||||
|
self.arc.scale_to_fit_height(0.75*radar_dish.get_height())
|
||||||
|
self.arc.move_to(radar_dish, UP+RIGHT)
|
||||||
|
self.start_points = np.array(self.arc.points)
|
||||||
|
self.start_center = self.arc.get_center()
|
||||||
|
self.finished = False
|
||||||
|
|
||||||
|
ContinualAnimation.__init__(self, self.arc, **kwargs)
|
||||||
|
|
||||||
|
def update_mobject(self, dt):
|
||||||
|
arc = self.arc
|
||||||
|
total_distance = self.speed*self.internal_time
|
||||||
|
arc.points = np.array(self.start_points)
|
||||||
|
arc.shift(total_distance*self.direction)
|
||||||
|
|
||||||
|
if self.internal_time < self.fade_in_time:
|
||||||
|
alpha = np.clip(self.internal_time/self.fade_in_time, 0, 1)
|
||||||
|
arc.set_stroke(self.color, alpha*self.stroke_width)
|
||||||
|
|
||||||
|
if self.reflection_distance is None:
|
||||||
|
#Check if reflection is happening
|
||||||
|
arc_point = arc.get_edge_center(self.direction)
|
||||||
|
target_point = self.target.get_edge_center(-self.direction)
|
||||||
|
arc_distance = np.dot(arc_point, self.direction)
|
||||||
|
target_distance = np.dot(target_point, self.direction)
|
||||||
|
if arc_distance > target_distance:
|
||||||
|
self.reflection_distance = target_distance
|
||||||
|
#Don't use elif in case the above code creates reflection_distance
|
||||||
|
if self.reflection_distance is not None:
|
||||||
|
delta_distance = total_distance - self.reflection_distance
|
||||||
|
point_distances = np.dot(self.direction, arc.points.T)
|
||||||
|
diffs = point_distances - self.reflection_distance
|
||||||
|
shift_vals = np.outer(-2*np.maximum(diffs, 0), self.direction)
|
||||||
|
arc.points += shift_vals
|
||||||
|
|
||||||
|
#Check if done
|
||||||
|
arc_point = arc.get_edge_center(-self.direction)
|
||||||
|
if np.dot(arc_point, self.direction) < np.dot(self.start_center, self.direction):
|
||||||
|
self.finished = True
|
||||||
|
self.arc.fade(1)
|
||||||
|
|
||||||
|
def is_finished(self):
|
||||||
|
return self.finished
|
||||||
|
|
||||||
|
class RadarPulse(ContinualAnimation):
|
||||||
|
CONFIG = {
|
||||||
|
"n_pulse_singletons" : 8,
|
||||||
|
"frequency" : 0.05,
|
||||||
|
"colors" : [BLUE, YELLOW]
|
||||||
|
}
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
digest_config(self, kwargs)
|
||||||
|
colors = color_gradient(self.colors, self.n_pulse_singletons)
|
||||||
|
self.pulse_singletons = [
|
||||||
|
RadarPulseSingleton(*args, color = color, **kwargs)
|
||||||
|
for color in colors
|
||||||
|
]
|
||||||
|
pluse_mobjects = VGroup(*[ps.mobject for ps in self.pulse_singletons])
|
||||||
|
ContinualAnimation.__init__(self, pluse_mobjects, **kwargs)
|
||||||
|
|
||||||
|
def update_mobject(self, dt):
|
||||||
|
for i, ps in enumerate(self.pulse_singletons):
|
||||||
|
ps.internal_time = self.internal_time - i*self.frequency
|
||||||
|
ps.update_mobject(dt)
|
||||||
|
|
||||||
|
def is_finished(self):
|
||||||
|
return all([ps.is_finished() for ps in self.pulse_singletons])
|
||||||
|
|
||||||
###################
|
###################
|
||||||
|
|
||||||
class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
||||||
|
@ -152,32 +248,33 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
||||||
dot_cloud = ProbabalisticDotCloud()
|
dot_cloud = ProbabalisticDotCloud()
|
||||||
vector_cloud = ProbabalisticVectorCloud(
|
vector_cloud = ProbabalisticVectorCloud(
|
||||||
gaussian_distribution_wrapper_config = {"sigma_x" : 0.2},
|
gaussian_distribution_wrapper_config = {"sigma_x" : 0.2},
|
||||||
center_func = dot_cloud.gaussian_distribution_wrapper.get_start,
|
center_func = lambda : dot_cloud.gaussian_distribution_wrapper.get_parameters()[0],
|
||||||
)
|
)
|
||||||
for cloud in dot_cloud, vector_cloud:
|
for cloud in dot_cloud, vector_cloud:
|
||||||
gdw = cloud.gaussian_distribution_wrapper
|
cloud.gaussian_distribution_wrapper.next_to(
|
||||||
gdw.move_to(title.get_center(), LEFT)
|
title, DOWN, 2*LARGE_BUFF
|
||||||
gdw.shift(2*DOWN)
|
)
|
||||||
vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT)
|
vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT)
|
||||||
|
|
||||||
def get_brace_text_group_update(gdw, vect, text):
|
def get_brace_text_group_update(gdw, vect, text, color):
|
||||||
brace = Brace(gdw, vect)
|
brace = Brace(gdw, vect)
|
||||||
text = brace.get_tex("\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF)
|
text = brace.get_tex("2\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF)
|
||||||
group = VGroup(brace, text)
|
group = VGroup(brace, text)
|
||||||
def update_group(group):
|
def update_group(group):
|
||||||
brace, text = group
|
brace, text = group
|
||||||
brace.match_width(gdw, stretch = True)
|
brace.match_width(gdw, stretch = True)
|
||||||
brace.next_to(gdw, vect)
|
brace.next_to(gdw, vect)
|
||||||
text.next_to(brace, vect, buff = SMALL_BUFF)
|
text.next_to(brace, vect, buff = SMALL_BUFF)
|
||||||
|
group.highlight(color)
|
||||||
return ContinualUpdateFromFunc(group, update_group)
|
return ContinualUpdateFromFunc(group, update_group)
|
||||||
|
|
||||||
dot_brace_anim = get_brace_text_group_update(
|
dot_brace_anim = get_brace_text_group_update(
|
||||||
dot_cloud.gaussian_distribution_wrapper,
|
dot_cloud.gaussian_distribution_wrapper,
|
||||||
DOWN, "position",
|
DOWN, "position", dot_cloud.color
|
||||||
)
|
)
|
||||||
vector_brace_anim = get_brace_text_group_update(
|
vector_brace_anim = get_brace_text_group_update(
|
||||||
vector_cloud.gaussian_distribution_wrapper,
|
vector_cloud.gaussian_distribution_wrapper,
|
||||||
UP, "momentum",
|
UP, "momentum", vector_cloud.color
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add(title)
|
self.add(title)
|
||||||
|
@ -195,7 +292,7 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
||||||
# self.wait(2)
|
# self.wait(2)
|
||||||
self.play(
|
self.play(
|
||||||
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
||||||
{"sigma_x" : 0.1},
|
{"sigma" : 0.1*RIGHT},
|
||||||
run_time = 2,
|
run_time = 2,
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
@ -206,7 +303,7 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
||||||
self.add(vector_brace_anim)
|
self.add(vector_brace_anim)
|
||||||
self.play(
|
self.play(
|
||||||
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
||||||
{"sigma_x" : 1},
|
{"sigma" : RIGHT},
|
||||||
self.get_student_changes(*3*["confused"]),
|
self.get_student_changes(*3*["confused"]),
|
||||||
run_time = 3,
|
run_time = 3,
|
||||||
)
|
)
|
||||||
|
@ -214,17 +311,17 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
||||||
for x in range(2):
|
for x in range(2):
|
||||||
self.play(
|
self.play(
|
||||||
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
||||||
{"sigma_x" : 2},
|
{"sigma" : 2*RIGHT},
|
||||||
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
||||||
{"sigma_x" : 0.1},
|
{"sigma" : 0.1*RIGHT},
|
||||||
run_time = 3,
|
run_time = 3,
|
||||||
)
|
)
|
||||||
self.change_student_modes("thinking", "erm", "sassy")
|
self.change_student_modes("thinking", "erm", "sassy")
|
||||||
self.play(
|
self.play(
|
||||||
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
||||||
{"sigma_x" : 0.1},
|
{"sigma" : 0.1*RIGHT},
|
||||||
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
||||||
{"sigma_x" : 1},
|
{"sigma" : 1*RIGHT},
|
||||||
run_time = 3,
|
run_time = 3,
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
@ -299,7 +396,8 @@ class FourierTradeoff(Scene):
|
||||||
t_min = time_mean - time_radius,
|
t_min = time_mean - time_radius,
|
||||||
t_max = time_mean + time_radius,
|
t_max = time_mean + time_radius,
|
||||||
n_samples = 2*time_radius*17,
|
n_samples = 2*time_radius*17,
|
||||||
complex_to_real_func = abs,
|
# complex_to_real_func = abs,
|
||||||
|
complex_to_real_func = lambda z : z.real,
|
||||||
color = FREQUENCY_COLOR,
|
color = FREQUENCY_COLOR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -327,7 +425,7 @@ class FourierTradeoff(Scene):
|
||||||
|
|
||||||
#Draw items
|
#Draw items
|
||||||
self.add(time_axes, frequency_axes)
|
self.add(time_axes, frequency_axes)
|
||||||
self.play(ShowCreation(wave_packet))
|
self.play(ShowCreation(wave_packet, rate_func = double_smooth))
|
||||||
self.play(
|
self.play(
|
||||||
ReplacementTransform(
|
ReplacementTransform(
|
||||||
wave_packet.copy(),
|
wave_packet.copy(),
|
||||||
|
@ -351,8 +449,131 @@ class FourierTradeoff(Scene):
|
||||||
self.wait()
|
self.wait()
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
class ShowPlan(PiCreatureScene):
|
||||||
|
def construct(self):
|
||||||
|
self.add_title()
|
||||||
|
words = self.get_words()
|
||||||
|
self.play_sound_anims(words[0])
|
||||||
|
self.play_doppler_anims(words[1], words[0])
|
||||||
|
self.play_quantum_anims(words[2], words[1])
|
||||||
|
|
||||||
|
def add_title(self):
|
||||||
|
title = TextMobject("The plan")
|
||||||
|
title.scale(1.5)
|
||||||
|
title.to_edge(UP)
|
||||||
|
h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH)
|
||||||
|
h_line.next_to(title, DOWN)
|
||||||
|
self.add(title, h_line)
|
||||||
|
|
||||||
|
def get_words(self):
|
||||||
|
colors = [YELLOW, GREEN, BLUE]
|
||||||
|
topics = ["sound waves", "Doppler radar", "quantum particles"]
|
||||||
|
words = VGroup()
|
||||||
|
for topic, color in zip(topics, colors):
|
||||||
|
word = TextMobject("Uncertainty for", topic)
|
||||||
|
word[1].highlight(color)
|
||||||
|
words.add(word)
|
||||||
|
words.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = LARGE_BUFF)
|
||||||
|
words.to_edge(LEFT)
|
||||||
|
|
||||||
|
return words
|
||||||
|
|
||||||
|
def play_sound_anims(self, word):
|
||||||
|
morty = self.pi_creature
|
||||||
|
wave = FunctionGraph(
|
||||||
|
lambda x : 0.3*np.sin(15*x)*np.sin(0.5*x),
|
||||||
|
x_min = 0, x_max = 30,
|
||||||
|
num_anchor_points = 500,
|
||||||
|
)
|
||||||
|
wave.next_to(word, RIGHT)
|
||||||
|
rect = BackgroundRectangle(wave, fill_opacity = 1)
|
||||||
|
rect.stretch(2, 1)
|
||||||
|
rect.next_to(wave, LEFT, buff = 0)
|
||||||
|
wave_shift = AmbientMovement(
|
||||||
|
wave, direction = LEFT, rate = 5
|
||||||
|
)
|
||||||
|
wave_fader = UpdateFromAlphaFunc(
|
||||||
|
wave,
|
||||||
|
lambda w, a : w.set_stroke(width = 3*a)
|
||||||
|
)
|
||||||
|
checkmark = self.get_checkmark(word)
|
||||||
|
|
||||||
|
self.add(wave_shift)
|
||||||
|
self.add_foreground_mobjects(rect, word)
|
||||||
|
self.play(
|
||||||
|
Animation(word),
|
||||||
|
wave_fader,
|
||||||
|
morty.change, "raise_right_hand", word
|
||||||
|
)
|
||||||
|
self.wait(2)
|
||||||
|
wave_fader.rate_func = lambda a : 1-smooth(a)
|
||||||
|
self.add_foreground_mobjects(checkmark)
|
||||||
|
self.play(
|
||||||
|
Write(checkmark),
|
||||||
|
morty.change, "happy",
|
||||||
|
wave_fader,
|
||||||
|
)
|
||||||
|
self.remove_foreground_mobjects(rect, word)
|
||||||
|
self.add(word)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def play_doppler_anims(self, word, to_fade):
|
||||||
|
morty = self.pi_creature
|
||||||
|
|
||||||
|
radar_dish = RadarDish()
|
||||||
|
radar_dish.next_to(word, DOWN, aligned_edge = LEFT)
|
||||||
|
target = Plane()
|
||||||
|
# target.match_height(radar_dish)
|
||||||
|
target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF)
|
||||||
|
target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.25)
|
||||||
|
|
||||||
|
pulse = RadarPulse(radar_dish, target)
|
||||||
|
|
||||||
|
checkmark = self.get_checkmark(word)
|
||||||
|
|
||||||
|
self.add(target_movement)
|
||||||
|
self.play(
|
||||||
|
to_fade.fade, 0.5,
|
||||||
|
Write(word),
|
||||||
|
DrawBorderThenFill(radar_dish),
|
||||||
|
UpdateFromAlphaFunc(
|
||||||
|
target, lambda m, a : m.set_fill(opacity = a)
|
||||||
|
),
|
||||||
|
morty.change, "pondering",
|
||||||
|
run_time = 1
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.add(pulse)
|
||||||
|
count = it.count() #TODO, this is not a great hack...
|
||||||
|
while not pulse.is_finished() and count.next() < 15:
|
||||||
|
self.play(
|
||||||
|
morty.look_at, pulse.mobject,
|
||||||
|
run_time = 0.5
|
||||||
|
)
|
||||||
|
self.play(
|
||||||
|
Write(checkmark),
|
||||||
|
UpdateFromAlphaFunc(
|
||||||
|
target, lambda m, a : m.set_fill(opacity = 1-a)
|
||||||
|
),
|
||||||
|
FadeOut(radar_dish),
|
||||||
|
morty.change, "happy"
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def play_quantum_anims(self, word, to_fade):
|
||||||
|
pass
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
def get_checkmark(self, word):
|
||||||
|
checkmark = TexMobject("\\checkmark")
|
||||||
|
checkmark.highlight(GREEN)
|
||||||
|
checkmark.scale(1.5)
|
||||||
|
checkmark.next_to(word, UP+RIGHT, buff = 0)
|
||||||
|
return checkmark
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,9 @@ RIGHT = np.array(( 1., 0., 0.))
|
||||||
LEFT = np.array((-1., 0., 0.))
|
LEFT = np.array((-1., 0., 0.))
|
||||||
IN = np.array(( 0., 0.,-1.))
|
IN = np.array(( 0., 0.,-1.))
|
||||||
OUT = np.array(( 0., 0., 1.))
|
OUT = np.array(( 0., 0., 1.))
|
||||||
|
X_AXIS = np.array(( 1., 0., 0.))
|
||||||
|
Y_AXIS = np.array(( 0., 1., 0.))
|
||||||
|
Z_AXIS = np.array(( 0., 0., 1.))
|
||||||
|
|
||||||
TOP = SPACE_HEIGHT*UP
|
TOP = SPACE_HEIGHT*UP
|
||||||
BOTTOM = SPACE_HEIGHT*DOWN
|
BOTTOM = SPACE_HEIGHT*DOWN
|
||||||
|
|
|
@ -24,6 +24,8 @@ from topics.number_line import *
|
||||||
from topics.combinatorics import *
|
from topics.combinatorics import *
|
||||||
from topics.three_dimensions import *
|
from topics.three_dimensions import *
|
||||||
|
|
||||||
|
from topics.three_dimensions import *
|
||||||
|
|
||||||
# To watch one of these scenes, run the following:
|
# To watch one of these scenes, run the following:
|
||||||
# python extract_scene.py file_name <SceneName> -p
|
# python extract_scene.py file_name <SceneName> -p
|
||||||
#
|
#
|
||||||
|
@ -59,6 +61,67 @@ class WriteStuff(Scene):
|
||||||
self.play(Write(TextMobject("Stuff").scale(3)))
|
self.play(Write(TextMobject("Stuff").scale(3)))
|
||||||
|
|
||||||
|
|
||||||
|
class Rotation3d(ThreeDScene):
|
||||||
|
def construct(self):
|
||||||
|
# STEP 1
|
||||||
|
# Build two cube in the 3D scene, one for around the origin,
|
||||||
|
# the other shifted along the vector RIGHT + UP + OUT
|
||||||
|
cube_origin = Cube(fill_opacity = 0.8, stroke_width = 1.,
|
||||||
|
side_length = 1., fill_color = WHITE)
|
||||||
|
|
||||||
|
# RIGHT side: Red
|
||||||
|
# UP side: Green
|
||||||
|
# OUT side: Blue
|
||||||
|
orientations = [IN, OUT, LEFT, RIGHT, UP, DOWN]
|
||||||
|
for face, orient in zip(cube_origin.family_members_with_points(), orientations):
|
||||||
|
if np.array_equal(orient, RIGHT):
|
||||||
|
face.set_style_data(fill_color = RED)
|
||||||
|
elif np.array_equal(orient, UP):
|
||||||
|
face.set_style_data(fill_color = GREEN)
|
||||||
|
elif np.array_equal(orient, OUT):
|
||||||
|
face.set_style_data(fill_color = BLUE)
|
||||||
|
|
||||||
|
cube_shifted = Cube(fill_opacity = 0.8, stroke_width = 1.,
|
||||||
|
side_length = 1., fill_color = BLUE)
|
||||||
|
shift_vec = 2*(RIGHT + UP + OUT)
|
||||||
|
cube_shifted.shift(shift_vec)
|
||||||
|
|
||||||
|
# STEP 2
|
||||||
|
# Add the cubes in the 3D scene
|
||||||
|
self.add(cube_origin)
|
||||||
|
self.add(cube_shifted)
|
||||||
|
|
||||||
|
# STEP 3
|
||||||
|
# Setup the camera position
|
||||||
|
phi, theta, distance = ThreeDCamera().get_spherical_coords()
|
||||||
|
angle_factor = 0.9
|
||||||
|
phi += 2*np.pi/4*angle_factor
|
||||||
|
theta += 3*2*np.pi/8
|
||||||
|
self.set_camera_position(phi, theta, distance)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
# STEP 4
|
||||||
|
# Animation
|
||||||
|
# Animation 1: rotation around the Z-axis with the ORIGIN of the space
|
||||||
|
# as center of rotation
|
||||||
|
theta += 2*np.pi
|
||||||
|
self.move_camera(phi, theta, distance,
|
||||||
|
run_time = 5)
|
||||||
|
|
||||||
|
# Animation 2: shift the space in order of to get the center of the shifted cube
|
||||||
|
# as the next center of rotation
|
||||||
|
cube_center = cube_shifted.get_center()
|
||||||
|
self.move_camera(center_x = cube_center[0],
|
||||||
|
center_y = cube_center[1],
|
||||||
|
center_z = cube_center[2],
|
||||||
|
run_time = 2)
|
||||||
|
|
||||||
|
# Animation 3: rotation around the Z-axis with the center of the shifted cube
|
||||||
|
# as center of rotation
|
||||||
|
theta += 2*np.pi
|
||||||
|
self.move_camera(phi, theta, distance,
|
||||||
|
run_time = 5)
|
||||||
|
|
||||||
|
|
||||||
class SpinAroundCube(ThreeDScene):
|
class SpinAroundCube(ThreeDScene):
|
||||||
# Take a look at ThreeDSCene in three_dimensions.py.
|
# Take a look at ThreeDSCene in three_dimensions.py.
|
||||||
|
|
|
@ -296,6 +296,7 @@ class Mobject(Container):
|
||||||
aligned_edge = ORIGIN,
|
aligned_edge = ORIGIN,
|
||||||
submobject_to_align = None,
|
submobject_to_align = None,
|
||||||
index_of_submobject_to_align = None,
|
index_of_submobject_to_align = None,
|
||||||
|
coor_mask = np.array([1,1,1]),
|
||||||
):
|
):
|
||||||
if isinstance(mobject_or_point, Mobject):
|
if isinstance(mobject_or_point, Mobject):
|
||||||
mob = mobject_or_point
|
mob = mobject_or_point
|
||||||
|
@ -315,7 +316,7 @@ class Mobject(Container):
|
||||||
else:
|
else:
|
||||||
aligner = self
|
aligner = self
|
||||||
point_to_align = aligner.get_critical_point(aligned_edge - direction)
|
point_to_align = aligner.get_critical_point(aligned_edge - direction)
|
||||||
self.shift(target_point - point_to_align + buff*direction)
|
self.shift((target_point - point_to_align + buff*direction)*coor_mask)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP):
|
def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP):
|
||||||
|
@ -403,13 +404,14 @@ class Mobject(Container):
|
||||||
submob.scale(1./factor)
|
submob.scale(1./factor)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def move_to(self, point_or_mobject, aligned_edge = ORIGIN):
|
def move_to(self, point_or_mobject, aligned_edge = ORIGIN,
|
||||||
|
coor_mask = np.array([1,1,1])):
|
||||||
if isinstance(point_or_mobject, Mobject):
|
if isinstance(point_or_mobject, Mobject):
|
||||||
target = point_or_mobject.get_critical_point(aligned_edge)
|
target = point_or_mobject.get_critical_point(aligned_edge)
|
||||||
else:
|
else:
|
||||||
target = point_or_mobject
|
target = point_or_mobject
|
||||||
point_to_align = self.get_critical_point(aligned_edge)
|
point_to_align = self.get_critical_point(aligned_edge)
|
||||||
self.shift(target - point_to_align)
|
self.shift((target - point_to_align)*coor_mask)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def replace(self, mobject, dim_to_match = 0, stretch = False):
|
def replace(self, mobject, dim_to_match = 0, stretch = False):
|
||||||
|
|
|
@ -159,31 +159,52 @@ class SVGMobject(VMobject):
|
||||||
x = float(element.getAttribute('x'))
|
x = float(element.getAttribute('x'))
|
||||||
#Flip y
|
#Flip y
|
||||||
y = -float(element.getAttribute('y'))
|
y = -float(element.getAttribute('y'))
|
||||||
|
mobject.shift(x*RIGHT+y*UP)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
transform = element.getAttribute('transform')
|
||||||
transform = element.getAttribute('transform')
|
|
||||||
|
try: # transform matrix
|
||||||
prefix = "matrix("
|
prefix = "matrix("
|
||||||
suffix = ")"
|
suffix = ")"
|
||||||
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
||||||
transform = transform[len(prefix):-len(suffix)]
|
transform = transform[len(prefix):-len(suffix)]
|
||||||
transform = string_to_numbers(transform)
|
transform = string_to_numbers(transform)
|
||||||
transform = np.array(transform).reshape([3,2])
|
transform = np.array(transform).reshape([3,2])
|
||||||
x += transform[2][0]
|
x = transform[2][0]
|
||||||
y -= transform[2][1]
|
y = -transform[2][1]
|
||||||
matrix = np.identity(self.dim)
|
matrix = np.identity(self.dim)
|
||||||
matrix[:2,:2] = transform[:2,:]
|
matrix[:2,:2] = transform[:2,:]
|
||||||
t_matrix = np.transpose(matrix)
|
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, t_matrix)
|
mob.points = np.dot(mob.points, matrix)
|
||||||
|
mobject.shift(x*RIGHT+y*UP)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
mobject.shift(x*RIGHT+y*UP)
|
try: # transform scale
|
||||||
#TODO, transforms
|
prefix = "scale("
|
||||||
|
suffix = ")"
|
||||||
|
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
||||||
|
transform = transform[len(prefix):-len(suffix)]
|
||||||
|
scale_x, scale_y = string_to_numbers(transform)
|
||||||
|
mobject.scale(np.array([scale_x, scale_y, 1]))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # transform translate
|
||||||
|
prefix = "translate("
|
||||||
|
suffix = ")"
|
||||||
|
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
||||||
|
transform = transform[len(prefix):-len(suffix)]
|
||||||
|
x, y = string_to_numbers(transform)
|
||||||
|
mobject.shift(x*RIGHT + y*DOWN)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#TODO, ...
|
||||||
|
|
||||||
def update_ref_to_element(self, defs):
|
def update_ref_to_element(self, defs):
|
||||||
new_refs = dict([
|
new_refs = dict([
|
||||||
|
|
|
@ -41,6 +41,7 @@ class Scene(Container):
|
||||||
"random_seed" : 0,
|
"random_seed" : 0,
|
||||||
"start_at_animation_number" : None,
|
"start_at_animation_number" : None,
|
||||||
"end_at_animation_number" : None,
|
"end_at_animation_number" : None,
|
||||||
|
"include_render_quality_in_name" : False, #TODO, nothing uses this right now
|
||||||
}
|
}
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter?
|
Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter?
|
||||||
|
@ -55,6 +56,8 @@ class Scene(Container):
|
||||||
self.current_scene_time = 0
|
self.current_scene_time = 0
|
||||||
if self.name is None:
|
if self.name is None:
|
||||||
self.name = self.__class__.__name__
|
self.name = self.__class__.__name__
|
||||||
|
if self.include_render_quality_in_name:
|
||||||
|
self.name += str(self.camera.pixel_shape[0])
|
||||||
if self.random_seed is not None:
|
if self.random_seed is not None:
|
||||||
random.seed(self.random_seed)
|
random.seed(self.random_seed)
|
||||||
np.random.seed(self.random_seed)
|
np.random.seed(self.random_seed)
|
||||||
|
|
|
@ -96,7 +96,6 @@ class Arc(VMobject):
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class Circle(Arc):
|
class Circle(Arc):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : RED,
|
"color" : RED,
|
||||||
|
@ -137,7 +136,6 @@ class Ellipse(VMobject):
|
||||||
circle = circle.stretch_to_fit_height(self.height)
|
circle = circle.stretch_to_fit_height(self.height)
|
||||||
self.points = circle.points
|
self.points = circle.points
|
||||||
|
|
||||||
|
|
||||||
class AnnularSector(VMobject):
|
class AnnularSector(VMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"inner_radius" : 1,
|
"inner_radius" : 1,
|
||||||
|
@ -187,7 +185,6 @@ class AnnularSector(VMobject):
|
||||||
self.shift(v)
|
self.shift(v)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class Sector(AnnularSector):
|
class Sector(AnnularSector):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"outer_radius" : 1,
|
"outer_radius" : 1,
|
||||||
|
|
|
@ -3,10 +3,10 @@ from helpers import *
|
||||||
from mobject import Mobject
|
from mobject import Mobject
|
||||||
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
|
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
|
||||||
from mobject.svg_mobject import SVGMobject
|
from mobject.svg_mobject import SVGMobject
|
||||||
from mobject.tex_mobject import TextMobject, TexMobject
|
from mobject.tex_mobject import TextMobject, TexMobject, Brace
|
||||||
|
|
||||||
from animation import Animation
|
from animation import Animation
|
||||||
from animation.simple_animations import Rotating, LaggedStart
|
from animation.simple_animations import Rotating, LaggedStart, AnimationGroup
|
||||||
from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
|
from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
|
||||||
|
|
||||||
from topics.geometry import Circle, Line, Rectangle, Square, \
|
from topics.geometry import Circle, Line, Rectangle, Square, \
|
||||||
|
|
|
@ -36,15 +36,18 @@ class ThreeDCamera(CameraWithPerspective):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Camera.__init__(self, *args, **kwargs)
|
Camera.__init__(self, *args, **kwargs)
|
||||||
self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect)
|
self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect)
|
||||||
## Lives in the phi-theta-distance space
|
## rotation_mobject lives in the phi-theta-distance space
|
||||||
self.rotation_mobject = VectorizedPoint()
|
self.rotation_mobject = VectorizedPoint()
|
||||||
|
## moving_center lives in the x-y-z space
|
||||||
|
## It representes the center of rotation
|
||||||
|
self.moving_center = VectorizedPoint(self.space_center)
|
||||||
self.set_position(self.phi, self.theta, self.distance)
|
self.set_position(self.phi, self.theta, self.distance)
|
||||||
|
|
||||||
def modified_rgb(self, vmobject, rgb):
|
def modified_rgb(self, vmobject, rgb):
|
||||||
if should_shade_in_3d(vmobject):
|
if should_shade_in_3d(vmobject):
|
||||||
return self.get_shaded_rgb(rgb, self.get_unit_normal_vect(vmobject))
|
return self.get_shaded_rgb(rgb, self.get_unit_normal_vect(vmobject))
|
||||||
else:
|
else:
|
||||||
return color
|
return rgb
|
||||||
|
|
||||||
def get_stroke_rgb(self, vmobject):
|
def get_stroke_rgb(self, vmobject):
|
||||||
return self.modified_rgb(vmobject, vmobject.get_stroke_rgb())
|
return self.modified_rgb(vmobject, vmobject.get_stroke_rgb())
|
||||||
|
@ -128,10 +131,24 @@ class ThreeDCamera(CameraWithPerspective):
|
||||||
np.cos(phi)
|
np.cos(phi)
|
||||||
])
|
])
|
||||||
|
|
||||||
def set_position(self, phi = None, theta = None, distance = None):
|
def get_center_of_rotation(self, x = None, y = None, z = None):
|
||||||
|
curr_x, curr_y, curr_z = self.moving_center.points[0]
|
||||||
|
if x is None:
|
||||||
|
x = curr_x
|
||||||
|
if y is None:
|
||||||
|
y = curr_y
|
||||||
|
if z is None:
|
||||||
|
z = curr_z
|
||||||
|
return np.array([x, y, z])
|
||||||
|
|
||||||
|
def set_position(self, phi = None, theta = None, distance = None,
|
||||||
|
center_x = None, center_y = None, center_z = None):
|
||||||
point = self.get_spherical_coords(phi, theta, distance)
|
point = self.get_spherical_coords(phi, theta, distance)
|
||||||
self.rotation_mobject.move_to(point)
|
self.rotation_mobject.move_to(point)
|
||||||
self.phi, self.theta, self.distance = point
|
self.phi, self.theta, self.distance = point
|
||||||
|
center_of_rotation = self.get_center_of_rotation(center_x, center_y, center_z)
|
||||||
|
self.moving_center.move_to(center_of_rotation)
|
||||||
|
self.space_center = self.moving_center.points[0]
|
||||||
|
|
||||||
def get_view_transformation_matrix(self):
|
def get_view_transformation_matrix(self):
|
||||||
return (self.default_distance / self.get_distance()) * np.dot(
|
return (self.default_distance / self.get_distance()) * np.dot(
|
||||||
|
@ -142,6 +159,8 @@ class ThreeDCamera(CameraWithPerspective):
|
||||||
def points_to_pixel_coords(self, points):
|
def points_to_pixel_coords(self, points):
|
||||||
matrix = self.get_view_transformation_matrix()
|
matrix = self.get_view_transformation_matrix()
|
||||||
new_points = np.dot(points, matrix.T)
|
new_points = np.dot(points, matrix.T)
|
||||||
|
self.space_center = self.moving_center.points[0]
|
||||||
|
|
||||||
return Camera.points_to_pixel_coords(self, new_points)
|
return Camera.points_to_pixel_coords(self, new_points)
|
||||||
|
|
||||||
class ThreeDScene(Scene):
|
class ThreeDScene(Scene):
|
||||||
|
@ -150,8 +169,9 @@ class ThreeDScene(Scene):
|
||||||
"ambient_camera_rotation" : None,
|
"ambient_camera_rotation" : None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_camera_position(self, phi = None, theta = None, distance = None):
|
def set_camera_position(self, phi = None, theta = None, distance = None,
|
||||||
self.camera.set_position(phi, theta, distance)
|
center_x = None, center_y = None, center_z = None):
|
||||||
|
self.camera.set_position(phi, theta, distance, center_x, center_y, center_z)
|
||||||
|
|
||||||
def begin_ambient_camera_rotation(self, rate = 0.01):
|
def begin_ambient_camera_rotation(self, rate = 0.01):
|
||||||
self.ambient_camera_rotation = AmbientMovement(
|
self.ambient_camera_rotation = AmbientMovement(
|
||||||
|
@ -167,8 +187,9 @@ class ThreeDScene(Scene):
|
||||||
self.ambient_camera_rotation = None
|
self.ambient_camera_rotation = None
|
||||||
|
|
||||||
def move_camera(
|
def move_camera(
|
||||||
self,
|
self,
|
||||||
phi = None, theta = None, distance = None,
|
phi = None, theta = None, distance = None,
|
||||||
|
center_x = None, center_y = None, center_z = None,
|
||||||
added_anims = [],
|
added_anims = [],
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
@ -178,10 +199,17 @@ class ThreeDScene(Scene):
|
||||||
target_point,
|
target_point,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z)
|
||||||
|
movement_center = ApplyMethod(
|
||||||
|
self.camera.moving_center.move_to,
|
||||||
|
target_center,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
||||||
if is_camera_rotating:
|
if is_camera_rotating:
|
||||||
self.remove(self.ambient_camera_rotation)
|
self.remove(self.ambient_camera_rotation)
|
||||||
self.play(movement, *added_anims)
|
self.play(movement, movement_center, *added_anims)
|
||||||
|
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
||||||
if is_camera_rotating:
|
if is_camera_rotating:
|
||||||
self.add(self.ambient_camera_rotation)
|
self.add(self.ambient_camera_rotation)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue