Merge pull request #424 from 3b1b/clacks

Clacks
This commit is contained in:
Grant Sanderson 2019-01-30 11:29:33 -08:00 committed by GitHub
commit cb18daab37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1860 additions and 104 deletions

View file

@ -1,8 +0,0 @@
from active_projects import clacks
output_directory = "clacks_question"
all_scenes = [
clacks.NameIntro,
clacks.MathAndPhysicsConspiring,
clacks.LightBouncing,
]

View file

@ -0,0 +1,28 @@
# from active_projects.clacks import question
# from active_projects.clacks import solution1
from active_projects.clacks.solution2 import block_collision_scenes
from active_projects.clacks.solution2 import simple_scenes
from active_projects.clacks.solution2 import wordy_scenes
from active_projects.clacks.solution2 import pi_creature_scenes
from active_projects.clacks.solution2 import position_phase_space
OUTPUT_DIRECTORY = "clacks_solution2"
ALL_SCENE_CLASSES = [
block_collision_scenes.IntroducePreviousTwoVideos,
block_collision_scenes.PreviousTwoVideos,
wordy_scenes.ConnectionToOptics,
pi_creature_scenes.OnAnsweringTwice,
simple_scenes.LastVideoWrapper,
position_phase_space.IntroducePositionPhaseSpace,
position_phase_space.UnscaledPositionPhaseSpaceMass100,
position_phase_space.EqualMassCase,
pi_creature_scenes.AskAboutEqualMassMomentumTransfer,
position_phase_space.FailedAngleRelation,
position_phase_space.UnscaledPositionPhaseSpaceMass10,
pi_creature_scenes.ComplainAboutRelevanceOfAnalogy,
simple_scenes.LastVideoWrapper,
position_phase_space.RescaleCoordinates,
wordy_scenes.ConnectionToOpticsTransparent,
position_phase_space.RescaleCoordinatesMass16,
position_phase_space.RescaleCoordinatesMass64,
]

View file

@ -6,7 +6,7 @@ from active_projects.clacks.question import BlocksAndWallExample
class NameBump(BlocksAndWallExample):
CONFIG = {
"name": "Magnus Lysfjord",
"name": "Grant Sanderson",
"sliding_blocks_config": {
"block1_config": {
"mass": 1e6,

View file

@ -1,6 +1,4 @@
from big_ol_pile_of_manim_imports import *
import subprocess
from pydub import AudioSegment
class Block(Square):
@ -217,6 +215,10 @@ class ClackFlashes(ContinualAnimation):
continue
last_time = time
flash = Flash(location, **self.flash_config)
for sm in flash.mobject.family_members_with_points():
if isinstance(sm, VMobject):
sm.set_stroke(YELLOW, 3)
sm.set_stroke(WHITE, 6, 0.5, background=True)
flash.start_time = time
flash.end_time = time + flash.run_time
self.flashes.append(flash)
@ -224,16 +226,17 @@ class ClackFlashes(ContinualAnimation):
def update_mobject(self, dt):
total_time = self.external_time
group = self.mobject
for flash in self.flashes:
if flash.start_time < total_time < flash.end_time:
if flash.mobject not in self.mobject:
self.mobject.add(flash.mobject)
if flash.mobject not in group:
group.add(flash.mobject)
flash.update(
(total_time - flash.start_time) / flash.run_time
)
else:
if flash.mobject in self.mobject:
self.mobject.remove(flash.mobject)
if flash.mobject in group:
group.remove(flash.mobject)
class Wall(Line):
@ -266,6 +269,7 @@ class Wall(Line):
class BlocksAndWallScene(Scene):
CONFIG = {
"include_sound": True,
"collision_sound": "clack.wav",
"count_clacks": True,
"counter_group_shift_vect": LEFT,
"sliding_blocks_config": {},
@ -273,7 +277,6 @@ class BlocksAndWallScene(Scene):
"wall_x": -6,
"n_wall_ticks": 15,
"counter_label": "\\# Collisions: ",
"collision_sound": "clack.wav",
"show_flash_animations": True,
"min_time_between_sounds": 0.004,
}
@ -345,56 +348,30 @@ class BlocksAndWallScene(Scene):
return
self.counter_mob.set_value(n_clacks)
def create_sound_file(self, clack_data):
clack_file = os.path.join(SOUND_DIR, self.collision_sound)
output_file = self.get_movie_file_path(extension='.wav')
def add_clack_sounds(self, clack_data):
clack_file = self.collision_sound
total_time = self.get_time()
times = [
time
for location, time in clack_data
if time < 300 # In case of any extremes
if time < total_time
]
clack = AudioSegment.from_wav(clack_file)
total_time = max(times) + 1
clacks = AudioSegment.silent(int(1000 * total_time))
last_position = 0
min_diff = int(1000 * self.min_time_between_sounds)
last_time = 0
for time in times:
position = int(1000 * time)
d_position = position - last_position
if d_position < min_diff:
d_time = time - last_time
if d_time < self.min_time_between_sounds:
continue
if time > self.get_time():
break
last_position = position
clacks = clacks.fade(-50, start=position, end=position + 10)
clacks = clacks.overlay(
clack,
position=position
last_time = time
self.add_sound(
clack_file,
time_offset=(time - total_time),
gain=-20,
)
clacks.export(output_file, format="wav")
return output_file
return self
# TODO, this no longer works
# should use Scene.add_sound instead
def combine_movie_files(self):
Scene.combine_movie_files(self)
def tear_down(self):
if self.include_sound:
sound_file_path = self.create_sound_file(self.clack_data)
movie_path = self.get_movie_file_path()
temp_path = self.get_movie_file_path(str(self) + "TempSound")
commands = [
"ffmpeg",
"-i", movie_path,
"-i", sound_file_path,
"-c:v", "copy", "-c:a", "aac",
'-loglevel', 'error',
"-strict", "experimental",
temp_path,
]
subprocess.call(commands)
subprocess.call(["rm", sound_file_path])
subprocess.call(["mv", temp_path, movie_path])
self.add_clack_sounds(self.clack_data)
# Animated scenes
@ -1573,6 +1550,7 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene):
"count_clacks": False,
"show_flash_animations": False,
"floor_y": -3.0,
"include_sound": False,
}
def setup(self):
@ -1580,7 +1558,16 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene):
BlocksAndWallExample.setup(self)
def construct(self):
self.camera_frame.shift(0.9 * UP)
# self.camera_frame.shift(0.9 * UP)
self.mobjects.insert(
0,
FullScreenFadeRectangle(
color=DARK_GREY,
opacity=0.5,
sheen_direction=UL,
sheen=0.5,
),
)
self.thicken_lines()
self.grow_labels()
self.add_vector()

View file

@ -0,0 +1,76 @@
from big_ol_pile_of_manim_imports import *
from active_projects.clacks.question import BlocksAndWallExample
class PreviousTwoVideos(BlocksAndWallExample):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 1e2,
"velocity": -1,
"width": 4,
"distance": 8,
},
"block2_config": {
"width": 4,
"distance": 3,
},
},
"floor_y": -3,
"wait_time": 15,
}
def setup(self):
super().setup()
blocks = self.blocks
videos = Group(
ImageMobject("ClacksSolution1Thumbnail"),
ImageMobject("ClacksQuestionThumbnail"),
)
for n, video, block in zip([2, 1], videos, blocks):
block.fade(1)
video.add(SurroundingRectangle(
video, buff=0,
color=BLUE,
stroke_width=3,
))
video.replace(block)
title = TextMobject("Part {}".format(n))
title.scale(1.5)
title.next_to(video, UP, MED_SMALL_BUFF)
video.add(title)
def update_videos(videos):
for video, block in zip(videos, blocks):
video.move_to(block, DOWN)
video.shift(0.04 * UP)
videos.add_updater(update_videos)
self.add(videos)
if self.show_flash_animations:
self.add(self.clack_flashes.mobject)
self.videos = videos
class IntroducePreviousTwoVideos(PreviousTwoVideos):
CONFIG = {
"show_flash_animations": False,
"include_sound": False,
}
def construct(self):
blocks = self.blocks
videos = self.videos
self.remove(blocks)
videos.clear_updaters()
self.remove(videos)
self.play(FadeInFromLarge(videos[1]))
self.play(TransformFromCopy(
videos[0].copy().fade(1).shift(2 * RIGHT),
videos[0],
rate_func=lambda t: rush_into(t, 3),
))
# self.wait()

View file

@ -0,0 +1,78 @@
from big_ol_pile_of_manim_imports import *
class OnAnsweringTwice(TeacherStudentsScene):
def construct(self):
question = TextMobject("Why $\\pi$?")
question.move_to(self.screen)
question.to_edge(UP)
other_questions = VGroup(
TextMobject("Frequency of collisions?"),
TextMobject("Efficient simulation?"),
TextMobject("Time until last collision?"),
)
for mob in other_questions:
mob.move_to(self.hold_up_spot, DOWN)
self.add(question)
self.student_says(
"But we already \\\\ solved it",
bubble_kwargs={"direction": LEFT},
target_mode="raise_left_hand",
added_anims=[self.teacher.change, "thinking"]
)
self.change_student_modes("sassy", "angry")
self.wait()
self.play(
RemovePiCreatureBubble(self.students[2]),
self.get_student_changes("erm", "erm"),
ApplyMethod(
question.move_to, self.hold_up_spot, DOWN,
path_arc=-90 * DEGREES,
),
self.teacher.change, "raise_right_hand",
)
shown_questions = VGroup(question)
for oq in other_questions:
self.play(
shown_questions.shift, 0.85 * UP,
FadeInFromDown(oq),
self.get_student_changes(
*["pondering"] * 3,
look_at_arg=oq
)
)
shown_questions.add(oq)
self.wait(3)
class AskAboutEqualMassMomentumTransfer(TeacherStudentsScene):
def construct(self):
pass
class ComplainAboutRelevanceOfAnalogy(TeacherStudentsScene):
def construct(self):
self.student_says(
"Why would \\\\ you care",
target_mode="maybe"
)
self.change_student_modes(
"angry", "sassy", "maybe",
added_anims=[self.teacher.change, "guilty"]
)
self.wait(2)
self.play(
self.teacher.change, "raise_right_hand",
self.get_student_changes(
"pondering", "erm", "pondering",
look_at_arg=self.hold_up_spot,
),
RemovePiCreatureBubble(self.students[2])
)
self.play(
self.students[2].change, "thinking",
self.hold_up_spot + UP,
)
self.wait(3)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
from big_ol_pile_of_manim_imports import *
class LastVideoWrapper(Scene):
def construct(self):
title = TextMobject("Last time...")
title.scale(1.5)
title.to_edge(UP)
rect = ScreenRectangle(height=6)
rect.next_to(title, DOWN)
self.play(
FadeInFromDown(title),
ShowCreation(rect)
)
self.wait()

View file

@ -0,0 +1,205 @@
from big_ol_pile_of_manim_imports import *
class ConnectionToOptics(Scene):
def construct(self):
e_group, m_group = k_groups = self.get_kinematics_groups()
c_group, a_group = o_groups = self.get_optics_groups()
arrows = VGroup()
for g1, g2 in zip(k_groups, o_groups):
g2.align_to(g1, UP)
g2.to_edge(RIGHT)
arrow = TexMobject("\\Rightarrow")
arrow.scale(1.5)
arrow.move_to(interpolate(
g1[0].get_right(), g2[0].get_left(), 0.5
))
arrows.add(arrow)
everything = VGroup(k_groups, arrows, o_groups)
everything.to_edge(UP)
everything.generate_target()
everything.target.scale(0.9)
everything.target.to_edge(DOWN)
width = max([m.get_width() for m in everything.target])
width += 2 * MED_SMALL_BUFF
rects = VGroup()
for k in [0, 2]:
rect = DashedMobject(Rectangle(
height=FRAME_HEIGHT - 1.5,
width=width
), dashes_num=100)
rect.move_to(everything.target[k])
rect.to_edge(DOWN, buff=SMALL_BUFF)
rects.add(rect)
titles = VGroup(
TextMobject("Kinematics"),
TextMobject("Optics"),
)
titles.scale(1.5)
for title, rect in zip(titles, rects):
title.next_to(rect, UP)
titles[0].align_to(titles[1], UP)
self.play(FadeInFromDown(e_group))
self.play(
Write(arrows[0]),
FadeInFrom(c_group, LEFT)
)
self.wait()
self.play(FadeInFromDown(m_group))
self.play(
Write(arrows[1]),
FadeInFrom(a_group, LEFT)
)
self.wait(4)
for k in range(2):
anims = [
ShowCreation(rects[k]),
FadeInFromDown(titles[k]),
]
if k == 0:
anims.append(MoveToTarget(everything))
self.play(*anims)
self.wait()
self.wait()
self.wait(4)
def get_kinematics_groups(self):
tex_to_color_map = {
"m_1": BLUE,
"m_2": BLUE,
"v_1": RED,
"v_2": RED,
}
energy_eq = TexMobject(
"\\frac{1}{2} m_1 (v_1)^2 + "
"\\frac{1}{2} m_2 (v_2)^2 = "
"\\text{const.}",
tex_to_color_map=tex_to_color_map
)
energy_eq.scale(0.8)
momentum_eq = TexMobject(
"m_1 v_1 + m_2 v_2 = \\text{const.}",
tex_to_color_map=tex_to_color_map
)
energy_label = TextMobject(
"Conservation of energy"
)
momentum_label = TextMobject(
"Conservation of momentum"
)
energy_group = VGroup(energy_label, energy_eq)
momentum_group = VGroup(momentum_label, momentum_eq)
groups = VGroup(energy_group, momentum_group)
for group in groups:
group.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
group[0].set_color(GREEN)
groups.arrange_submobjects(DOWN, buff=2)
groups.to_edge(LEFT)
return groups
def get_optics_groups(self):
self.time_tracker = ValueTracker(0)
self.time_tracker.add_updater(
lambda m, dt: m.increment_value(dt)
)
self.add(self.time_tracker)
return VGroup(
self.get_speed_group(),
self.get_angle_group()
)
def get_speed_group(self):
speed_label = TextMobject("Constant speed of light")
speed_label.set_color(YELLOW)
speed_light_template = Line(LEFT, RIGHT)
speed_light_template.fade(1)
speed_light_template.match_width(speed_label)
speed_light_template.next_to(speed_label, DOWN, MED_LARGE_BUFF)
speed_light = speed_light_template.deepcopy()
def update_speed_light(light, period=2, time_width=0.05):
time = self.time_tracker.get_value()
alpha = (time / period) % 1
# alpha = 1 - 2 * abs(alpha - 0.5)
alpha *= 1.5
a = alpha - time_width / 2
b = alpha + time_width / 2
light.pointwise_become_partial(
speed_light_template, max(a, 0), min(b, 1)
)
opacity = speed_label.family_members_with_points()[0].get_fill_opacity()
light.set_stroke(YELLOW, width=3, opacity=opacity)
# light.stretch(0.5, 0)
# point = speed_light_template.point_from_proportion(0.25)
# light.stretch(2, 0, about_point=point)
speed_light.add_updater(update_speed_light)
result = VGroup(
speed_label, speed_light_template, speed_light
)
return result
def get_angle_group(self):
title = VGroup(*map(TextMobject, [
"Angle of\\\\Incidence",
"=",
"Angle of\\\\Refraction",
])).arrange_submobjects(RIGHT)
title.set_color(YELLOW)
h_line = Line(LEFT, RIGHT)
h_line.match_width(title)
h_line.set_stroke(LIGHT_GREY)
h_line.set_sheen(1, UL)
points = [
h_line.get_left() + UP,
h_line.get_center(),
h_line.get_right() + UP,
]
dashed_lines = VGroup(
DashedLine(*points[0:2]), DashedLine(*points[1:3])
)
dashed_lines.set_stroke(WHITE, 2)
v_shape = VMobject()
v_shape.set_points_as_corners(points)
v_shape.fade(1)
theta = dashed_lines[1].get_angle()
arcs = VGroup(
Arc(start_angle=0, angle=theta),
Arc(start_angle=PI, angle=-theta),
)
arcs.set_stroke(WHITE, 2)
thetas = VGroup()
for v in LEFT, RIGHT:
theta = TexMobject("\\theta")
theta.next_to(arcs, v, aligned_edge=DOWN)
theta.shift(SMALL_BUFF * UP)
thetas.add(theta)
beam = VMobject()
def update_beam(beam, period=2, time_width=0.05):
time = self.time_tracker.get_value()
alpha = (time / period) % 1
alpha *= 1.5
a = alpha - time_width / 2
b = alpha + time_width / 2
beam.pointwise_become_partial(
v_shape, max(a, 0), min(b, 1)
)
opacity = title.family_members_with_points()[0].get_fill_opacity()
beam.set_stroke(YELLOW, width=3, opacity=opacity)
beam.add_updater(update_beam)
title.next_to(v_shape, UP, MED_LARGE_BUFF)
return VGroup(
title, h_line, arcs, thetas,
dashed_lines, v_shape, beam
)
class ConnectionToOpticsTransparent(ConnectionToOptics):
pass

View file

@ -220,10 +220,14 @@ class LaggedStart(Animation):
}
def __init__(self, AnimationClass, mobject, arg_creator=None, **kwargs):
for key in ["rate_func", "run_time"]:
if key in AnimationClass.CONFIG:
setattr(self, key, AnimationClass.CONFIG[key])
digest_config(self, kwargs)
for key in "rate_func", "run_time", "lag_ratio":
if key in kwargs:
kwargs.pop(key)
if arg_creator is None:
def arg_creator(mobject):
return (mobject,)

View file

@ -24,7 +24,6 @@ from manimlib.utils.rate_functions import smooth
from manimlib.utils.rate_functions import squish_rate_func
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from manimlib.utils.rate_functions import double_smooth
class FocusOn(Transform):
@ -125,10 +124,11 @@ class ShowPassingFlash(ShowPartial):
}
def get_bounds(self, alpha):
alpha *= (1 + self.time_width)
alpha -= self.time_width / 2.0
lower = max(0, alpha - self.time_width / 2.0)
upper = min(1, alpha + self.time_width / 2.0)
tw = self.time_width
upper = interpolate(0, 1 + tw, alpha)
lower = upper - tw
upper = min(upper, 1)
lower = max(lower, 0)
return (lower, upper)
def clean_up(self, *args, **kwargs):

View file

@ -298,6 +298,8 @@ class TeacherStudentsScene(PiCreatureScene):
"raise_left_hand",
])
kwargs["target_mode"] = target_mode
if "bubble_kwargs" not in kwargs:
kwargs["bubble_kwargs"] = {"direction": LEFT}
student = self.get_students()[kwargs.get("student_index", 2)]
return self.pi_creature_says(
student, *content, **kwargs

View file

@ -17,7 +17,7 @@ from manimlib.utils.color import interpolate_color
from manimlib.utils.iterables import list_update
from manimlib.utils.iterables import remove_list_redundancies
from manimlib.utils.paths import straight_path
from manimlib.utils.simple_functions import get_num_args
from manimlib.utils.simple_functions import get_parameters
from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import rotation_matrix
@ -45,6 +45,7 @@ class Mobject(Container):
if self.name is None:
self.name = self.__class__.__name__
self.updaters = []
self.updating_suspended = False
self.reset_points()
self.generate_points()
self.init_colors()
@ -145,24 +146,23 @@ class Mobject(Container):
# Updating
def update(self, dt=0):
for updater in self.updaters:
num_args = get_num_args(updater)
if num_args == 1:
updater(self)
elif num_args == 2:
updater(self, dt)
else:
raise Exception(
"Mobject updater expected 1 or 2 "
"arguments, %d given" % num_args
)
def update(self, dt=0, recursive=True):
if not self.updating_suspended:
for updater in self.updaters:
parameters = get_parameters(updater)
if "dt" in parameters:
updater(self, dt)
else:
updater(self)
if recursive:
for submob in self.submobjects:
submob.update(dt, recursive)
return self
def get_time_based_updaters(self):
return [
updater
for updater in self.updaters
if get_num_args(updater) == 2
updater for updater in self.updaters
if "dt" in get_parameters(updater)
]
def get_updaters(self):
@ -186,6 +186,21 @@ class Mobject(Container):
self.updaters = []
return self
def suspend_updating(self, recursive=True):
self.updating_suspended = True
if recursive:
for submob in self.submobjects:
submob.suspend_updating(recursive)
return self
def resume_updating(self, recursive=True):
self.updating_suspended = False
if recursive:
for submob in self.submobjects:
submob.resume_updating(recursive)
self.update(dt=0, recursive=recursive)
return self
# Transforming operations
def apply_to_family(self, func):

View file

@ -15,7 +15,7 @@ class DecimalNumber(VMobject):
"edge_to_fix": LEFT,
}
def __init__(self, number, **kwargs):
def __init__(self, number=0, **kwargs):
VMobject.__init__(self, **kwargs)
self.number = number
self.initial_config = kwargs

View file

@ -69,10 +69,6 @@ class Scene(Container):
def tear_down(self):
pass
def setup_bases(self):
for base in self.__class__.__bases__:
base.setup(self)
def construct(self):
pass # To be implemented in subclasses
@ -151,7 +147,7 @@ class Scene(Container):
###
def continual_update(self, dt):
for mobject in self.get_mobject_family_members():
for mobject in self.mobjects:
mobject.update(dt)
for continual_animation in self.continual_animations:
continual_animation.update(dt)
@ -470,6 +466,9 @@ class Scene(Container):
# scene gets added to the scene
if animation.mobject not in self.get_mobject_family_members():
self.add(animation.mobject)
# Don't call the update functions of a mobject
# being animated
animation.mobject.suspend_updating()
moving_mobjects = self.get_moving_mobjects(*animations)
# Paint all non-moving objects onto the screen, so they don't
@ -500,6 +499,7 @@ class Scene(Container):
def clean_up_animations(self, *animations):
for animation in animations:
animation.clean_up(self)
animation.mobject.resume_updating()
return self
def get_mobjects_from_last_animation(self):
@ -568,9 +568,9 @@ class Scene(Container):
for frame in frames:
self.file_writer.write_frame(frame)
def add_sound(self, sound_file, time_offset=0):
def add_sound(self, sound_file, time_offset=0, gain=None, **kwargs):
time = self.get_time() + time_offset
self.file_writer.add_sound(sound_file, time)
self.file_writer.add_sound(sound_file, time, gain, **kwargs)
def show_frame(self):
self.update_frame(ignore_skipping=True)

View file

@ -119,7 +119,9 @@ class SceneFileWriter(object):
def create_audio_segment(self):
self.audio_segment = AudioSegment.silent()
def add_audio_segment(self, new_segment, time=None):
def add_audio_segment(self, new_segment,
time=None,
gain_to_background=None):
if not self.includes_sound:
self.includes_sound = True
self.create_audio_segment()
@ -138,13 +140,17 @@ class SceneFileWriter(object):
crossfade=0,
)
self.audio_segment = segment.overlay(
new_segment, position=int(1000 * time)
new_segment,
position=int(1000 * time),
gain_during_overlay=gain_to_background,
)
def add_sound(self, sound_file, time):
def add_sound(self, sound_file, time=None, gain=None, **kwargs):
file_path = get_full_sound_file_path(sound_file)
new_segment = AudioSegment.from_file(file_path)
self.add_audio_segment(new_segment, time)
if gain:
new_segment = new_segment.apply_gain(gain)
self.add_audio_segment(new_segment, time, **kwargs)
# Writers
def begin_animation(self, allow_write=False):
@ -305,17 +311,25 @@ class SceneFileWriter(object):
)
# Makes sure sound file length will match video file
self.add_audio_segment(AudioSegment.silent(0))
self.audio_segment.export(sound_file_path)
self.audio_segment.export(
sound_file_path,
bitrate='312k',
)
temp_file_path = movie_file_path.replace(".", "_temp.")
commands = commands = [
commands = [
"ffmpeg",
"-i", movie_file_path,
"-i", sound_file_path,
'-y', # overwrite output file if it exists
"-c:v", "copy", "-c:a", "aac",
"-c:v", "copy",
"-c:a", "aac",
"-b:a", "320k",
# select video stream from first file
"-map", "0:v:0",
# select audio stream from second file
"-map", "1:a:0",
'-loglevel', 'error',
"-shortest",
"-strict", "experimental",
# "-shortest",
temp_file_path,
]
subprocess.call(commands)

View file

@ -16,12 +16,12 @@ def smooth(t, inflection=10.0):
)
def rush_into(t):
return 2 * smooth(t / 2.0)
def rush_into(t, inflection=10.0):
return 2 * smooth(t / 2.0, inflection)
def rush_from(t):
return 2 * smooth(t / 2.0 + 0.5) - 1
def rush_from(t, inflection=10.0):
return 2 * smooth(t / 2.0 + 0.5, inflection) - 1
def slow_into(t):

View file

@ -30,7 +30,11 @@ def choose(n, r):
def get_num_args(function):
return len(inspect.signature(function).parameters)
return len(get_parameters(function))
def get_parameters(function):
return inspect.signature(function).parameters
# Just to have a less heavyweight name for this extremely common operation
#

View file

@ -142,9 +142,8 @@ def angle_between_vectors(v1, v2):
Returns the angle between two 3D vectors.
This angle will always be btw 0 and TAU/2.
"""
l1 = get_norm(v1)
l2 = get_norm(v2)
return np.arccos(np.dot(v1, v2) / (l1 * l2))
diff = (angle_of_vector(v1) - angle_of_vector(v2)) % TAU
return min(diff, TAU - diff)
def project_along_vector(point, vector):