mirror of
https://github.com/3b1b/manim.git
synced 2025-08-21 05:44:04 +00:00
commit
10c607af73
23 changed files with 484 additions and 801 deletions
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
|
||||
|
@ -317,7 +316,7 @@ class NumberlineTransformationScene(ZoomedScene):
|
|||
zcbr_group.add(mini_line_copy, mini_line)
|
||||
anims += [FadeIn(mini_line), FadeIn(mini_line_copy)]
|
||||
|
||||
# Add tiny coordinates
|
||||
# Add tiny coordiantes
|
||||
if local_coordinate_values is None:
|
||||
local_coordinate_values = [x]
|
||||
local_coordinates = self.get_local_coordinates(
|
||||
|
@ -661,7 +660,7 @@ class StartingCalc101(PiCreatureScene):
|
|||
circumference = TexMobject("2\\pi r")
|
||||
circumference.move_to(unfilled_circle)
|
||||
equation = TexMobject(
|
||||
"{d (\\pi r^2) \\over dr} = 2\\pi r",
|
||||
"{d (\\pi r^2) \\over dx} = 2\\pi r",
|
||||
tex_to_color_map={
|
||||
"\\pi r^2": BLUE_D,
|
||||
"2\\pi r": YELLOW,
|
||||
|
@ -876,8 +875,8 @@ class Wrapper(Scene):
|
|||
}
|
||||
|
||||
def construct(self):
|
||||
rect = self.rect = ScreenRectangle(height=self.screen_height)
|
||||
title = self.title = TextMobject(self.title, **self.title_kwargs)
|
||||
rect = ScreenRectangle(height=self.screen_height)
|
||||
title = TextMobject(self.title, **self.title_kwargs)
|
||||
title.to_edge(UP)
|
||||
rect.next_to(title, DOWN)
|
||||
|
||||
|
@ -1563,11 +1562,11 @@ class TalkThroughXSquaredExample(IntroduceTransformationView):
|
|||
la, ra = ra, la
|
||||
if factor < 0:
|
||||
kwargs = {
|
||||
"path_arc": np.pi,
|
||||
"path_arc": -np.pi,
|
||||
"use_rectangular_stem": False,
|
||||
}
|
||||
la = Arrow(UP, DOWN, **kwargs)
|
||||
ra = Arrow(DOWN, UP, **kwargs)
|
||||
la = Arrow(DOWN, UP, **kwargs)
|
||||
ra = Arrow(UP, DOWN, **kwargs)
|
||||
for arrow in la, ra:
|
||||
arrow.pointwise_become_partial(arrow, 0, 0.9)
|
||||
arrow.tip.scale(2)
|
||||
|
@ -1872,8 +1871,9 @@ class ZoomInMoreAndMoreToZero(ZoomInOnXSquaredNearZero):
|
|||
frame.scale_to_fit_height(factor * zoomed_display_height)
|
||||
self.local_coordinate_num_decimal_places = int(-np.log10(factor))
|
||||
zoom_words = TextMobject(
|
||||
"Zoomed", "{:,}x \\\\".format(int(1.0 / factor)),
|
||||
"near 0",
|
||||
"Zoomed ", "{:,}".format(int(1.0 / factor)),
|
||||
"x \\\\", "near 0",
|
||||
arg_separator=""
|
||||
)
|
||||
zoom_words.next_to(self.zoomed_display, DOWN)
|
||||
|
||||
|
@ -1954,14 +1954,6 @@ class XSquaredForNegativeInput(TalkThroughXSquaredExample):
|
|||
sample_dots.set_fill(opacity=0.8)
|
||||
|
||||
self.play(LaggedStart(DrawBorderThenFill, sample_dots))
|
||||
self.play(LaggedStart(
|
||||
ApplyFunction, sample_dots[len(sample_dots) / 2:0:-1],
|
||||
lambda mob: (
|
||||
lambda m: m.scale(2).shift(SMALL_BUFF * UP).set_color(PINK),
|
||||
mob,
|
||||
),
|
||||
rate_func=there_and_back,
|
||||
))
|
||||
self.add_sample_dot_ghosts(sample_dots)
|
||||
self.apply_function(self.func, sample_dots=sample_dots)
|
||||
self.wait()
|
||||
|
@ -3215,7 +3207,6 @@ class StabilityAndInstability(AnalyzeFunctionWithTransformations):
|
|||
CONFIG = {
|
||||
"num_initial_applications": 0,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.force_skipping()
|
||||
self.add_function_title()
|
||||
|
@ -3246,10 +3237,9 @@ class StabilityAndInstability(AnalyzeFunctionWithTransformations):
|
|||
self.all_arrows
|
||||
)).copy()
|
||||
arrows.set_fill(PINK, 1)
|
||||
arrows.set_stroke(PINK, 3)
|
||||
arrows.second_anim = LaggedStart(
|
||||
ApplyMethod, arrows,
|
||||
lambda m: (m.set_color, YELLOW),
|
||||
lambda m: (m.set_fill, YELLOW, 1),
|
||||
rate_func=there_and_back_with_pause,
|
||||
lag_ratio=0.7,
|
||||
run_time=2,
|
||||
|
@ -3279,87 +3269,11 @@ class StabilityAndInstability(AnalyzeFunctionWithTransformations):
|
|||
def write_derivative_fact(self):
|
||||
stable_label = self.stable_label
|
||||
unstable_label = self.unstable_label
|
||||
labels = VGroup(stable_label, unstable_label)
|
||||
phi_arrows = self.phi_arrows
|
||||
phi_bro_arrows = self.phi_bro_arrows
|
||||
arrow_groups = VGroup(phi_arrows, phi_bro_arrows)
|
||||
|
||||
deriv_labels = VGroup()
|
||||
for char, label in zip("<>", labels):
|
||||
deriv_label = TexMobject(
|
||||
"\\big|", "\\frac{df}{dx}(", "x", ")", "\\big|",
|
||||
char, "1"
|
||||
)
|
||||
deriv_label.get_parts_by_tex("\\big|").match_height(
|
||||
deriv_label, stretch=True
|
||||
)
|
||||
deriv_label.set_color_by_tex("x", YELLOW, substring=False)
|
||||
deriv_label.next_to(label, UP)
|
||||
deriv_labels.add(deriv_label)
|
||||
|
||||
dot_groups = VGroup()
|
||||
for arrow_group in arrow_groups:
|
||||
dot_group = VGroup()
|
||||
for arrow in arrow_group:
|
||||
start_point, end_point = [
|
||||
line.number_to_point(line.point_to_number(p))
|
||||
for line, p in [
|
||||
(self.input_line, arrow.get_start()),
|
||||
(self.output_line, arrow.get_end()),
|
||||
]
|
||||
]
|
||||
dot = Dot(start_point, radius=0.05)
|
||||
dot.set_color(YELLOW)
|
||||
dot.generate_target()
|
||||
dot.target.move_to(end_point)
|
||||
dot_group.add(dot)
|
||||
dot_groups.add(dot_group)
|
||||
|
||||
for deriv_label, dot_group in zip(deriv_labels, dot_groups):
|
||||
self.play(FadeInFromDown(deriv_label))
|
||||
self.play(LaggedStart(GrowFromCenter, dot_group))
|
||||
self.play(*map(MoveToTarget, dot_group), run_time=2)
|
||||
self.wait()
|
||||
arrow_groups = [phi_arrows, phi_bro_arrows]
|
||||
|
||||
|
||||
class StaticAlgebraicObject(Scene):
|
||||
def construct(self):
|
||||
frac = get_phi_continued_fraction(40)
|
||||
frac.scale_to_fit_width(FRAME_WIDTH - 1)
|
||||
# frac.shift(2 * DOWN)
|
||||
frac.to_edge(DOWN)
|
||||
frac.set_stroke(WHITE, width=0.5)
|
||||
|
||||
title = TexMobject(
|
||||
"\\infty \\ne \\lim",
|
||||
tex_to_color_map={"\\ne": RED}
|
||||
)
|
||||
title.scale(1.5)
|
||||
title.to_edge(UP)
|
||||
|
||||
polynomial = TexMobject("x^2 - x - 1 = 0")
|
||||
polynomial.move_to(title)
|
||||
|
||||
self.add(title)
|
||||
self.play(LaggedStart(
|
||||
GrowFromCenter, frac,
|
||||
lag_ratio=0.1,
|
||||
run_time=3
|
||||
))
|
||||
self.wait()
|
||||
factor = 1.1
|
||||
self.play(frac.scale, factor, run_time=0.5)
|
||||
self.play(
|
||||
frac.scale, 1 / factor,
|
||||
frac.set_color, LIGHT_GREY,
|
||||
run_time=0.5, rate_func=lambda t: t**5,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeOut(title),
|
||||
FadeIn(polynomial)
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class NotBetterThanGraphs(TeacherStudentsScene):
|
||||
|
@ -3391,23 +3305,6 @@ class NotBetterThanGraphs(TeacherStudentsScene):
|
|||
self.wait(3)
|
||||
|
||||
|
||||
class WhatComesAfterWrapper(Wrapper):
|
||||
CONFIG = {"title": "Beyond the first year"}
|
||||
|
||||
def construct(self):
|
||||
Wrapper.construct(self)
|
||||
new_title = TextMobject("Next video")
|
||||
new_title.set_color(BLUE)
|
||||
new_title.move_to(self.title)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(new_title),
|
||||
self.title.shift, UP,
|
||||
self.title.fade, 1,
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
|
||||
CONFIG = {
|
||||
"pi_creatures_start_on_screen": False,
|
||||
|
@ -3459,83 +3356,6 @@ class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
|
|||
self.wait(4)
|
||||
|
||||
|
||||
class ShowJacobianZoomedIn(LinearTransformationScene, ZoomedScene):
|
||||
CONFIG = {
|
||||
"show_basis_vectors": False,
|
||||
"show_coordinates": True,
|
||||
"zoom_factor": 0.05,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
LinearTransformationScene.setup(self)
|
||||
ZoomedScene.setup(self)
|
||||
|
||||
def construct(self):
|
||||
def example_function(point):
|
||||
x, y, z = point
|
||||
return np.array([
|
||||
x + np.sin(y),
|
||||
y + np.sin(x),
|
||||
0
|
||||
])
|
||||
|
||||
zoomed_camera = self.zoomed_camera
|
||||
zoomed_display = self.zoomed_display
|
||||
frame = zoomed_camera.frame
|
||||
frame.move_to(3 * LEFT + 1 * UP)
|
||||
frame.set_color(YELLOW)
|
||||
zoomed_display.display_frame.set_color(YELLOW)
|
||||
zd_rect = BackgroundRectangle(
|
||||
zoomed_display,
|
||||
fill_opacity=1,
|
||||
buff=MED_SMALL_BUFF,
|
||||
)
|
||||
self.add_foreground_mobject(zd_rect)
|
||||
zd_rect.anim = UpdateFromFunc(
|
||||
zd_rect,
|
||||
lambda rect: rect.replace(zoomed_display).scale(1.1)
|
||||
)
|
||||
zd_rect.next_to(FRAME_HEIGHT * UP, UP)
|
||||
|
||||
tiny_grid = NumberPlane(
|
||||
x_radius=2,
|
||||
y_radius=2,
|
||||
color=BLUE_E,
|
||||
secondary_color=DARK_GREY,
|
||||
)
|
||||
tiny_grid.replace(frame)
|
||||
|
||||
jacobian_words = TextMobject("Jacobian")
|
||||
jacobian_words.add_background_rectangle()
|
||||
jacobian_words.scale(1.5)
|
||||
jacobian_words.move_to(zoomed_display, UP)
|
||||
zoomed_display.next_to(jacobian_words, DOWN)
|
||||
|
||||
self.play(self.get_zoom_in_animation())
|
||||
self.activate_zooming()
|
||||
self.play(
|
||||
self.get_zoomed_display_pop_out_animation(),
|
||||
zd_rect.anim
|
||||
)
|
||||
self.play(
|
||||
ShowCreation(tiny_grid),
|
||||
Write(jacobian_words),
|
||||
run_time=2
|
||||
)
|
||||
self.add_transformable_mobject(tiny_grid)
|
||||
self.add_foreground_mobject(jacobian_words)
|
||||
self.wait()
|
||||
self.apply_nonlinear_transformation(
|
||||
example_function,
|
||||
added_anims=[MaintainPositionRelativeTo(
|
||||
zoomed_camera.frame, tiny_grid,
|
||||
)],
|
||||
run_time=5
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Video 2
|
||||
|
||||
class ComplexAnalysisOverlay(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("Complex analysis")
|
||||
|
@ -3545,7 +3365,6 @@ class ComplexAnalysisOverlay(Scene):
|
|||
self.add(words)
|
||||
self.wait()
|
||||
|
||||
|
||||
class CompelxAnalyticFluidFlow(ComplexTransformationScene, MovingCameraScene):
|
||||
CONFIG = {
|
||||
"num_anchors_to_add_per_line": 200,
|
||||
|
@ -3623,7 +3442,6 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
|
|||
CONFIG = {
|
||||
"num_anchors_to_add_per_line": 20,
|
||||
"complex_homotopy": lambda z, t: z**(1.0 + t),
|
||||
"zoom_factor": 0.05,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
|
@ -3633,8 +3451,8 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
|
|||
def construct(self):
|
||||
self.edit_background_plane()
|
||||
self.add_title()
|
||||
# self.add_transforming_planes()
|
||||
# self.preview_some_numbers()
|
||||
self.add_transforming_planes()
|
||||
self.preview_some_numbers()
|
||||
self.zoom_in_to_one_plus_half_i()
|
||||
self.write_derivative()
|
||||
|
||||
|
@ -3730,44 +3548,9 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
|
|||
self.play(FadeOut(dot_groups))
|
||||
self.wait()
|
||||
self.play(FadeOut(self.plane))
|
||||
self.transformable_mobjects.remove(self.plane)
|
||||
|
||||
def zoom_in_to_one_plus_half_i(self):
|
||||
z = complex(1, 0.5)
|
||||
point = self.background.number_to_point(z)
|
||||
point_mob = VectorizedPoint(point)
|
||||
frame = self.zoomed_camera.frame
|
||||
frame.move_to(point)
|
||||
tiny_plane = NumberPlane(
|
||||
x_radius=2, y_radius=2,
|
||||
color=GREEN,
|
||||
secondary_color=GREEN_E
|
||||
)
|
||||
tiny_plane.replace(frame)
|
||||
|
||||
plane = self.get_plane()
|
||||
|
||||
words = TextMobject("What does this look like")
|
||||
words.add_background_rectangle()
|
||||
words.next_to(self.zoomed_display, LEFT, aligned_edge=UP)
|
||||
arrow = Arrow(words.get_bottom(), self.zoomed_display.get_left())
|
||||
VGroup(words, arrow).set_color(YELLOW)
|
||||
|
||||
self.play(FadeIn(plane))
|
||||
self.activate_zooming(animate=True)
|
||||
self.play(ShowCreation(tiny_plane))
|
||||
self.wait()
|
||||
self.add_transformable_mobjects(plane, tiny_plane, point_mob)
|
||||
self.add_foreground_mobjects(words, arrow)
|
||||
self.apply_complex_homotopy(
|
||||
self.complex_homotopy,
|
||||
added_anims=[
|
||||
Write(words),
|
||||
GrowArrow(arrow),
|
||||
MaintainPositionRelativeTo(frame, point_mob)
|
||||
]
|
||||
)
|
||||
self.wait(2)
|
||||
pass
|
||||
|
||||
def write_derivative(self):
|
||||
pass
|
||||
|
@ -3784,244 +3567,3 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
|
|||
top_plane.next_to(ORIGIN, UP, buff=tiny_tiny_buff)
|
||||
bottom_plane.next_to(ORIGIN, DOWN, buff=tiny_tiny_buff)
|
||||
return VGroup(top_plane, bottom_plane)
|
||||
|
||||
|
||||
class PrinciplesOverlay(PiCreatureScene):
|
||||
CONFIG = {
|
||||
"default_pi_creature_kwargs": {
|
||||
"color": GREY_BROWN,
|
||||
"flip_at_start": True,
|
||||
},
|
||||
"default_pi_creature_start_corner": DR,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
morty = self.pi_creature
|
||||
q_marks = VGroup(*[TexMobject("?") for x in range(40)])
|
||||
q_marks.arrange_submobjects_in_grid(4, 10)
|
||||
q_marks.space_out_submobjects(1.4)
|
||||
for mark in q_marks:
|
||||
mark.shift(
|
||||
random.random() * RIGHT,
|
||||
random.random() * UP,
|
||||
)
|
||||
mark.scale(1.5)
|
||||
mark.set_stroke(BLACK, 1)
|
||||
q_marks.next_to(morty, UP)
|
||||
q_marks.shift_onto_screen()
|
||||
q_marks.sort_submobjects(
|
||||
lambda p: np.linalg.norm(p - morty.get_top())
|
||||
)
|
||||
|
||||
self.play(morty.change, "pondering")
|
||||
self.wait(2)
|
||||
self.play(morty.change, "raise_right_hand")
|
||||
self.wait()
|
||||
self.play(morty.change, "thinking")
|
||||
self.wait(4)
|
||||
self.play(FadeInFromDown(q_marks[0]))
|
||||
self.wait(2)
|
||||
self.play(LaggedStart(
|
||||
FadeInFromDown, q_marks[1:],
|
||||
run_time=3
|
||||
))
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class ManyInfiniteExpressions(Scene):
|
||||
def construct(self):
|
||||
frac = get_phi_continued_fraction(10)
|
||||
frac.scale_to_fit_height(2)
|
||||
frac.to_corner(UL)
|
||||
|
||||
n = 9
|
||||
radical_str_parts = [
|
||||
"%d + \\sqrt{" % d
|
||||
for d in range(1, n + 1)
|
||||
]
|
||||
radical_str_parts += ["\\cdots"]
|
||||
radical_str_parts += ["}"] * n
|
||||
radical = TexMobject("".join(radical_str_parts))
|
||||
radical.to_corner(UR)
|
||||
radical.to_edge(DOWN)
|
||||
radical.set_color_by_gradient(YELLOW, RED)
|
||||
|
||||
n = 12
|
||||
power_tower = TexMobject(
|
||||
*["\\sqrt{2}^{"] * n + ["\\dots"] + ["}"] * n
|
||||
)
|
||||
power_tower.to_corner(UR)
|
||||
power_tower.set_color_by_gradient(BLUE, GREEN)
|
||||
|
||||
self.play(*[
|
||||
LaggedStart(
|
||||
GrowFromCenter, group,
|
||||
lag_ratio=0.1,
|
||||
run_time=8,
|
||||
)
|
||||
for group in frac, radical, power_tower
|
||||
])
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class HoldUpPromo(PrinciplesOverlay):
|
||||
def construct(self):
|
||||
morty = self.pi_creature
|
||||
|
||||
url = TextMobject("https://brilliant.org/3b1b/")
|
||||
url.to_corner(UL)
|
||||
|
||||
rect = ScreenRectangle(height=5.5)
|
||||
rect.next_to(url, DOWN)
|
||||
rect.to_edge(LEFT)
|
||||
|
||||
self.play(
|
||||
Write(url),
|
||||
self.pi_creature.change, "raise_right_hand"
|
||||
)
|
||||
self.play(ShowCreation(rect))
|
||||
self.wait(2)
|
||||
self.change_mode("thinking")
|
||||
self.wait()
|
||||
self.look_at(url)
|
||||
self.wait(10)
|
||||
self.change_mode("happy")
|
||||
self.wait(10)
|
||||
self.change_mode("raise_right_hand")
|
||||
self.wait(10)
|
||||
|
||||
self.play(FadeOut(rect), FadeOut(url))
|
||||
self.play(morty.change, "raise_right_hand")
|
||||
self.wait()
|
||||
self.play(morty.change, "hooray")
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class EndScreen(PatreonEndScreen):
|
||||
CONFIG = {
|
||||
"specific_patrons": [
|
||||
"Juan Benet",
|
||||
"Keith Smith",
|
||||
"Chloe Zhou",
|
||||
"Desmos",
|
||||
"Burt Humburg",
|
||||
"CrypticSwarm",
|
||||
"Andrew Sachs",
|
||||
"Ho\\`ang T\\`ung L\\^am",
|
||||
# "Hoàng Tùng Lâm",
|
||||
"Devin Scott",
|
||||
"Akash Kumar",
|
||||
"Felix Tripier",
|
||||
"Arthur Zey",
|
||||
"David Kedmey",
|
||||
"Ali Yahya",
|
||||
"Mayank M. Mehrotra",
|
||||
"Lukas Biewald",
|
||||
"Yana Chernobilsky",
|
||||
"Kaustuv DeBiswas",
|
||||
"Yu Jun",
|
||||
"dave nicponski",
|
||||
"Damion Kistler",
|
||||
"Jordan Scales",
|
||||
"Markus Persson",
|
||||
"Fela",
|
||||
"Fred Ehrsam",
|
||||
"Britt Selvitelle",
|
||||
"Jonathan Wilson",
|
||||
"Ryan Atallah",
|
||||
"Joseph John Cox",
|
||||
"Luc Ritchie",
|
||||
"Matt Roveto",
|
||||
"Jamie Warner",
|
||||
"Marek Cirkos",
|
||||
"Magister Mugit",
|
||||
"Stevie Metke",
|
||||
"Cooper Jones",
|
||||
"James Hughes",
|
||||
"John V Wertheim",
|
||||
"Chris Giddings",
|
||||
"Song Gao",
|
||||
"Alexander Feldman",
|
||||
"Matt Langford",
|
||||
"Max Mitchell",
|
||||
"Richard Burgmann",
|
||||
"John Griffith",
|
||||
"Chris Connett",
|
||||
"Steven Tomlinson",
|
||||
"Jameel Syed",
|
||||
"Bong Choung",
|
||||
"Ignacio Freiberg",
|
||||
"Zhilong Yang",
|
||||
"Giovanni Filippi",
|
||||
"Eric Younge",
|
||||
"Prasant Jagannath",
|
||||
"Cody Brocious",
|
||||
"James H. Park",
|
||||
"Norton Wang",
|
||||
"Kevin Le",
|
||||
"Tianyu Ge",
|
||||
"David MacCumber",
|
||||
"Oliver Steele",
|
||||
"Yaw Etse",
|
||||
"Dave B",
|
||||
"Waleed Hamied",
|
||||
"George Chiesa",
|
||||
"supershabam",
|
||||
"Delton Ding",
|
||||
"Thomas Tarler",
|
||||
"Isak Hietala",
|
||||
"1st ViewMaths",
|
||||
"Jacob Magnuson",
|
||||
"Mark Govea",
|
||||
"Clark Gaebel",
|
||||
"Mathias Jansson",
|
||||
"David Clark",
|
||||
"Michael Gardner",
|
||||
"Mads Elvheim",
|
||||
"Awoo",
|
||||
"Dr . David G. Stork",
|
||||
"Ted Suzman",
|
||||
"Linh Tran",
|
||||
"Andrew Busey",
|
||||
"John Haley",
|
||||
"Ankalagon",
|
||||
"Eric Lavault",
|
||||
"Boris Veselinovich",
|
||||
"Julian Pulgarin",
|
||||
"Jeff Linse",
|
||||
"Robert Teed",
|
||||
"Jason Hise",
|
||||
"Meshal Alshammari",
|
||||
"Bernd Sing",
|
||||
"James Thornton",
|
||||
"Mustafa Mahdi",
|
||||
"Mathew Bramson",
|
||||
"Jerry Ling",
|
||||
"Sh\\`im\\'in Ku\\=ang",
|
||||
"Rish Kundalia",
|
||||
"Achille Brighton",
|
||||
"Ripta Pasay",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# class Thumbnail(GraphicalIntuitions):
|
||||
class Thumbnail(AnalyzeFunctionWithTransformations):
|
||||
CONFIG = {
|
||||
"x_axis_width": 12,
|
||||
"graph_origin": 1.5 * DOWN + 4 * LEFT,
|
||||
"num_initial_applications": 1,
|
||||
"input_line_zero_point": 2 * UP,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_function_title()
|
||||
self.title.fade(1)
|
||||
self.titles.fade(1)
|
||||
self.repeatedly_apply_function()
|
||||
|
||||
full_rect = FullScreenFadeRectangle()
|
||||
cross = Cross(full_rect)
|
||||
cross.set_stroke(width=40)
|
||||
|
||||
self.add(cross)
|
||||
|
|
|
@ -206,7 +206,7 @@ class LeaveItToComputers(TeacherStudentsScene):
|
|||
|
||||
class PrerequisiteKnowledge(TeacherStudentsScene):
|
||||
CONFIG = {
|
||||
"camera_config": {"background_alpha": 255}
|
||||
"camera_config": {"background_opacity": 1}
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
|
|
@ -140,6 +140,13 @@ class ApplyPointwiseFunction(ApplyMethod):
|
|||
)
|
||||
|
||||
|
||||
class ApplyPointwiseFunctionToCenter(ApplyPointwiseFunction):
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
ApplyMethod.__init__(
|
||||
self, mobject.move_to, function(mobject.get_center()), **kwargs
|
||||
)
|
||||
|
||||
|
||||
class FadeToColor(ApplyMethod):
|
||||
def __init__(self, mobject, color, **kwargs):
|
||||
ApplyMethod.__init__(self, mobject.set_color, color, **kwargs)
|
||||
|
|
274
camera/camera.py
274
camera/camera.py
|
@ -1,5 +1,6 @@
|
|||
import itertools as it
|
||||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
import aggdraw
|
||||
import copy
|
||||
|
@ -7,9 +8,10 @@ import time
|
|||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
from scipy.spatial.distance import pdist
|
||||
|
||||
from constants import *
|
||||
from mobject.types.image_mobject import ImageMobject
|
||||
from mobject.types.image_mobject import AbstractImageMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.types.point_cloud_mobject import PMobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
|
@ -21,22 +23,26 @@ from utils.iterables import batch_by_property
|
|||
from utils.iterables import list_difference_update
|
||||
from utils.iterables import remove_list_redundancies
|
||||
from utils.simple_functions import fdiv
|
||||
from utils.space_ops import angle_of_vector
|
||||
|
||||
|
||||
class Camera(object):
|
||||
CONFIG = {
|
||||
"background_image": None,
|
||||
"pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
|
||||
# Note: frame_shape will be resized to match pixel_shape
|
||||
"frame_shape": (FRAME_HEIGHT, FRAME_WIDTH),
|
||||
"space_center": ORIGIN,
|
||||
"pixel_height": DEFAULT_PIXEL_HEIGHT,
|
||||
"pixel_width": DEFAULT_PIXEL_WIDTH,
|
||||
# Note: frame height and width will be resized to match
|
||||
# the pixel aspect ratio
|
||||
"frame_height": FRAME_HEIGHT,
|
||||
"frame_width": FRAME_WIDTH,
|
||||
"frame_center": ORIGIN,
|
||||
"background_color": BLACK,
|
||||
"background_opacity": 0,
|
||||
# Points in vectorized mobjects with norm greater
|
||||
# than this value will be rescaled.
|
||||
"max_allowable_norm": FRAME_WIDTH,
|
||||
"image_mode": "RGBA",
|
||||
"n_rgb_coords": 4,
|
||||
"background_alpha": 0, # Out of rgb_max_val
|
||||
"pixel_array_dtype": 'uint8',
|
||||
"use_z_coordinate_for_display_order": False,
|
||||
# z_buff_func is only used if the flag above is set to True.
|
||||
|
@ -58,36 +64,72 @@ class Camera(object):
|
|||
self.canvas = None
|
||||
return copy.copy(self)
|
||||
|
||||
def reset_pixel_shape(self, new_height, new_width):
|
||||
self.pixel_width = new_width
|
||||
self.pixel_height = new_height
|
||||
self.init_background()
|
||||
self.resize_frame_shape()
|
||||
self.reset()
|
||||
|
||||
def get_pixel_height(self):
|
||||
return self.pixel_height
|
||||
|
||||
def get_pixel_width(self):
|
||||
return self.pixel_width
|
||||
|
||||
def get_frame_height(self):
|
||||
return self.frame_height
|
||||
|
||||
def get_frame_width(self):
|
||||
return self.frame_width
|
||||
|
||||
def get_frame_center(self):
|
||||
return self.frame_center
|
||||
|
||||
def set_frame_height(self, frame_height):
|
||||
self.frame_height = frame_height
|
||||
|
||||
def set_frame_width(self, frame_width):
|
||||
self.frame_width = frame_width
|
||||
|
||||
def set_frame_center(self, frame_center):
|
||||
self.frame_center = frame_center
|
||||
|
||||
def resize_frame_shape(self, fixed_dimension=0):
|
||||
"""
|
||||
Changes frame_shape to match the aspect ratio
|
||||
of pixel_shape, where fixed_dimension determines
|
||||
whether frame_shape[0] (height) or frame_shape[1] (width)
|
||||
of the pixels, where fixed_dimension determines
|
||||
whether frame_height or frame_width
|
||||
remains fixed while the other changes accordingly.
|
||||
"""
|
||||
aspect_ratio = float(self.pixel_shape[1]) / self.pixel_shape[0]
|
||||
frame_width, frame_height = self.frame_shape
|
||||
pixel_height = self.get_pixel_height()
|
||||
pixel_width = self.get_pixel_width()
|
||||
frame_height = self.get_frame_height()
|
||||
frame_width = self.get_frame_width()
|
||||
aspect_ratio = fdiv(pixel_width, pixel_height)
|
||||
if fixed_dimension == 0:
|
||||
frame_height = aspect_ratio * frame_width
|
||||
frame_height = frame_width / aspect_ratio
|
||||
else:
|
||||
frame_width = frame_height / aspect_ratio
|
||||
self.frame_shape = (frame_width, frame_height)
|
||||
frame_width = aspect_ratio * frame_height
|
||||
self.set_frame_height(frame_height)
|
||||
self.set_frame_width(frame_width)
|
||||
|
||||
def init_background(self):
|
||||
height = self.get_pixel_height()
|
||||
width = self.get_pixel_width()
|
||||
if self.background_image is not None:
|
||||
path = get_full_raster_image_path(self.background_image)
|
||||
image = Image.open(path).convert(self.image_mode)
|
||||
height, width = self.pixel_shape
|
||||
# TODO, how to gracefully handle backgrounds
|
||||
# with different sizes?
|
||||
self.background = np.array(image)[:height, :width]
|
||||
self.background = self.background.astype(self.pixel_array_dtype)
|
||||
else:
|
||||
background_rgba = color_to_int_rgba(
|
||||
self.background_color, alpha=self.background_alpha
|
||||
self.background_color, self.background_opacity
|
||||
)
|
||||
self.background = np.zeros(
|
||||
list(self.pixel_shape) + [self.n_rgb_coords],
|
||||
(height, width, self.n_rgb_coords),
|
||||
dtype=self.pixel_array_dtype
|
||||
)
|
||||
self.background[:, :] = background_rgba
|
||||
|
@ -105,10 +147,10 @@ class Camera(object):
|
|||
retval = np.array(pixel_array)
|
||||
if convert_from_floats:
|
||||
retval = np.apply_along_axis(
|
||||
lambda f: (
|
||||
f * self.rgb_max_val).astype(self.pixel_array_dtype),
|
||||
lambda f: (f * self.rgb_max_val).astype(self.pixel_array_dtype),
|
||||
2,
|
||||
retval)
|
||||
retval
|
||||
)
|
||||
return retval
|
||||
|
||||
def set_pixel_array(self, pixel_array, convert_from_floats=False):
|
||||
|
@ -148,6 +190,7 @@ class Camera(object):
|
|||
|
||||
def reset(self):
|
||||
self.set_pixel_array(self.background)
|
||||
return self
|
||||
|
||||
####
|
||||
|
||||
|
@ -201,7 +244,7 @@ class Camera(object):
|
|||
type_func_pairs = [
|
||||
(VMobject, self.display_multiple_vectorized_mobjects),
|
||||
(PMobject, self.display_multiple_point_cloud_mobjects),
|
||||
(ImageMobject, self.display_multiple_image_mobjects),
|
||||
(AbstractImageMobject, self.display_multiple_image_mobjects),
|
||||
(Mobject, lambda batch: batch), # Do nothing
|
||||
]
|
||||
|
||||
|
@ -301,8 +344,7 @@ class Camera(object):
|
|||
# points = self.adjust_out_of_range_points(points)
|
||||
if len(points) == 0:
|
||||
continue
|
||||
aligned_points = self.align_points_to_camera(points)
|
||||
coords = self.points_to_pixel_coords(aligned_points)
|
||||
coords = self.points_to_pixel_coords(points)
|
||||
coord_strings = coords.flatten().astype(str)
|
||||
# Start new path string with M
|
||||
coord_strings[0] = "M" + coord_strings[0]
|
||||
|
@ -342,7 +384,6 @@ class Camera(object):
|
|||
def display_point_cloud(self, points, rgbas, thickness):
|
||||
if len(points) == 0:
|
||||
return
|
||||
points = self.align_points_to_camera(points)
|
||||
pixel_coords = self.points_to_pixel_coords(points)
|
||||
pixel_coords = self.thickened_coordinates(
|
||||
pixel_coords, thickness
|
||||
|
@ -358,7 +399,8 @@ class Camera(object):
|
|||
pixel_coords = pixel_coords[on_screen_indices]
|
||||
rgbas = rgbas[on_screen_indices]
|
||||
|
||||
ph, pw = self.pixel_shape
|
||||
ph = self.get_pixel_height()
|
||||
pw = self.get_pixel_width()
|
||||
|
||||
flattener = np.array([1, pw], dtype='int')
|
||||
flattener = flattener.reshape((2, 1))
|
||||
|
@ -378,94 +420,56 @@ class Camera(object):
|
|||
ul_coords, ur_coords, dl_coords = corner_coords
|
||||
right_vect = ur_coords - ul_coords
|
||||
down_vect = dl_coords - ul_coords
|
||||
center_coords = ul_coords + (right_vect + down_vect) / 2
|
||||
|
||||
impa = image_mobject.pixel_array
|
||||
|
||||
oh, ow = self.pixel_array.shape[:2] # Outer width and height
|
||||
ih, iw = impa.shape[:2] # inner with and height
|
||||
rgb_len = self.pixel_array.shape[2]
|
||||
|
||||
image = np.zeros((oh, ow, rgb_len), dtype=self.pixel_array_dtype)
|
||||
|
||||
if right_vect[1] == 0 and down_vect[0] == 0:
|
||||
rv0 = right_vect[0]
|
||||
dv1 = down_vect[1]
|
||||
x_indices = np.arange(rv0, dtype='int') * iw / rv0
|
||||
y_indices = np.arange(dv1, dtype='int') * ih / dv1
|
||||
stretched_impa = impa[y_indices][:, x_indices]
|
||||
|
||||
x0, x1 = ul_coords[0], ur_coords[0]
|
||||
y0, y1 = ul_coords[1], dl_coords[1]
|
||||
if x0 >= ow or x1 < 0 or y0 >= oh or y1 < 0:
|
||||
return
|
||||
siy0 = max(-y0, 0) # stretched_impa y0
|
||||
siy1 = dv1 - max(y1 - oh, 0)
|
||||
six0 = max(-x0, 0)
|
||||
six1 = rv0 - max(x1 - ow, 0)
|
||||
x0 = max(x0, 0)
|
||||
y0 = max(y0, 0)
|
||||
image[y0:y1, x0:x1] = stretched_impa[siy0:siy1, six0:six1]
|
||||
else:
|
||||
# Alternate (slower) tactic if image is tilted
|
||||
# List of all coordinates of pixels, given as (x, y),
|
||||
# which matches the return type of points_to_pixel_coords,
|
||||
# even though np.array indexing naturally happens as (y, x)
|
||||
all_pixel_coords = np.zeros((oh * ow, 2), dtype='int')
|
||||
a = np.arange(oh * ow, dtype='int')
|
||||
all_pixel_coords[:, 0] = a % ow
|
||||
all_pixel_coords[:, 1] = a / ow
|
||||
|
||||
recentered_coords = all_pixel_coords - ul_coords
|
||||
|
||||
with np.errstate(divide='ignore'):
|
||||
ix_coords, iy_coords = [
|
||||
np.divide(
|
||||
dim * np.dot(recentered_coords, vect),
|
||||
np.dot(vect, vect),
|
||||
)
|
||||
for vect, dim in (right_vect, iw), (down_vect, ih)
|
||||
]
|
||||
to_change = reduce(op.and_, [
|
||||
ix_coords >= 0, ix_coords < iw,
|
||||
iy_coords >= 0, iy_coords < ih,
|
||||
])
|
||||
inner_flat_coords = iw * \
|
||||
iy_coords[to_change] + ix_coords[to_change]
|
||||
flat_impa = impa.reshape((iw * ih, rgb_len))
|
||||
target_rgbas = flat_impa[inner_flat_coords, :]
|
||||
|
||||
image = image.reshape((ow * oh, rgb_len))
|
||||
image[to_change] = target_rgbas
|
||||
image = image.reshape((oh, ow, rgb_len))
|
||||
self.overlay_rgba_array(image)
|
||||
|
||||
def overlay_rgba_array(self, arr):
|
||||
fg = arr
|
||||
bg = self.pixel_array
|
||||
# rgba_max_val = self.rgb_max_val
|
||||
src_rgb, src_a, dst_rgb, dst_a = [
|
||||
a.astype(np.float32) / self.rgb_max_val
|
||||
for a in fg[..., :3], fg[..., 3], bg[..., :3], bg[..., 3]
|
||||
]
|
||||
|
||||
out_a = src_a + dst_a * (1.0 - src_a)
|
||||
|
||||
# When the output alpha is 0 for full transparency,
|
||||
# we have a choice over what RGB value to use in our
|
||||
# output representation. We choose 0 here.
|
||||
out_rgb = fdiv(
|
||||
src_rgb * src_a[..., None] +
|
||||
dst_rgb * dst_a[..., None] * (1.0 - src_a[..., None]),
|
||||
out_a[..., None],
|
||||
zero_over_zero_value=0
|
||||
sub_image = Image.fromarray(
|
||||
image_mobject.get_pixel_array(),
|
||||
mode="RGBA"
|
||||
)
|
||||
|
||||
self.pixel_array[..., :3] = out_rgb * self.rgb_max_val
|
||||
self.pixel_array[..., 3] = out_a * self.rgb_max_val
|
||||
# Reshape
|
||||
pixel_width = int(pdist([ul_coords, ur_coords]))
|
||||
pixel_height = int(pdist([ul_coords, dl_coords]))
|
||||
sub_image = sub_image.resize(
|
||||
(pixel_width, pixel_height), resample=Image.BICUBIC
|
||||
)
|
||||
|
||||
def align_points_to_camera(self, points):
|
||||
# This is where projection should live
|
||||
return points - self.space_center
|
||||
# Rotate
|
||||
angle = angle_of_vector(right_vect)
|
||||
adjusted_angle = -int(360 * angle / TAU)
|
||||
if adjusted_angle != 0:
|
||||
sub_image = sub_image.rotate(
|
||||
adjusted_angle, resample=Image.BICUBIC, expand=1
|
||||
)
|
||||
|
||||
# TODO, there is no accounting for a shear...
|
||||
|
||||
# Paste into an image as large as the camear's pixel array
|
||||
full_image = Image.fromarray(
|
||||
np.zeros((self.get_pixel_height(), self.get_pixel_width())),
|
||||
mode="RGBA"
|
||||
)
|
||||
new_ul_coords = center_coords - np.array(sub_image.size) / 2
|
||||
full_image.paste(
|
||||
sub_image,
|
||||
box=(
|
||||
new_ul_coords[0],
|
||||
new_ul_coords[1],
|
||||
new_ul_coords[0] + sub_image.size[0],
|
||||
new_ul_coords[1] + sub_image.size[1],
|
||||
)
|
||||
)
|
||||
|
||||
# Paint on top of existing pixel array
|
||||
self.overlay_PIL_image(full_image)
|
||||
|
||||
def overlay_rgba_array(self, arr):
|
||||
self.overlay_PIL_image(Image.fromarray(arr, mode="RGBA"))
|
||||
|
||||
def overlay_PIL_image(self, image):
|
||||
self.pixel_array = np.array(
|
||||
Image.alpha_composite(self.get_image(), image)
|
||||
)
|
||||
|
||||
def adjust_out_of_range_points(self, points):
|
||||
if not np.any(points > self.max_allowable_norm):
|
||||
|
@ -483,31 +487,43 @@ class Camera(object):
|
|||
return points
|
||||
|
||||
def points_to_pixel_coords(self, points):
|
||||
shifted_points = points - self.get_frame_center()
|
||||
|
||||
result = np.zeros((len(points), 2))
|
||||
ph, pw = self.pixel_shape
|
||||
sh, sw = self.frame_shape
|
||||
width_mult = pw / sw
|
||||
width_add = pw / 2
|
||||
height_mult = ph / sh
|
||||
height_add = ph / 2
|
||||
pixel_height = self.get_pixel_height()
|
||||
pixel_width = self.get_pixel_width()
|
||||
frame_height = self.get_frame_height()
|
||||
frame_width = self.get_frame_width()
|
||||
width_mult = pixel_width / frame_width
|
||||
width_add = pixel_width / 2
|
||||
height_mult = pixel_height / frame_height
|
||||
height_add = pixel_height / 2
|
||||
# Flip on y-axis as you go
|
||||
height_mult *= -1
|
||||
|
||||
result[:, 0] = points[:, 0] * width_mult + width_add
|
||||
result[:, 1] = points[:, 1] * height_mult + height_add
|
||||
result[:, 0] = shifted_points[:, 0] * width_mult + width_add
|
||||
result[:, 1] = shifted_points[:, 1] * height_mult + height_add
|
||||
return result.astype('int')
|
||||
|
||||
def on_screen_pixels(self, pixel_coords):
|
||||
return reduce(op.and_, [
|
||||
pixel_coords[:, 0] >= 0,
|
||||
pixel_coords[:, 0] < self.pixel_shape[1],
|
||||
pixel_coords[:, 0] < self.get_pixel_width(),
|
||||
pixel_coords[:, 1] >= 0,
|
||||
pixel_coords[:, 1] < self.pixel_shape[0],
|
||||
pixel_coords[:, 1] < self.get_pixel_height(),
|
||||
])
|
||||
|
||||
def adjusted_thickness(self, thickness):
|
||||
big_shape = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_shape"]
|
||||
factor = sum(big_shape) / sum(self.pixel_shape)
|
||||
# TODO: This seems...unsystematic
|
||||
big_sum = op.add(
|
||||
PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_height"],
|
||||
PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_width"],
|
||||
)
|
||||
this_sum = op.add(
|
||||
self.get_pixel_height(),
|
||||
self.get_pixel_width(),
|
||||
)
|
||||
factor = fdiv(big_sum, this_sum)
|
||||
return 1 + (thickness - 1) / factor
|
||||
|
||||
def get_thickening_nudges(self, thickness):
|
||||
|
@ -525,13 +541,20 @@ class Camera(object):
|
|||
|
||||
def get_coords_of_all_pixels(self):
|
||||
# These are in x, y order, to help me keep things straight
|
||||
full_space_dims = np.array(self.frame_shape)[::-1]
|
||||
full_pixel_dims = np.array(self.pixel_shape)[::-1]
|
||||
full_space_dims = np.array([
|
||||
self.get_frame_width(),
|
||||
self.get_frame_height()
|
||||
])
|
||||
full_pixel_dims = np.array([
|
||||
self.get_pixel_width(),
|
||||
self.get_pixel_height()
|
||||
])
|
||||
|
||||
# These are addressed in the same y, x order as in pixel_array, but the values in them
|
||||
# are listed in x, y order
|
||||
uncentered_pixel_coords = np.indices(self.pixel_shape)[
|
||||
::-1].transpose(1, 2, 0)
|
||||
uncentered_pixel_coords = np.indices(
|
||||
[self.get_pixel_height(), self.get_pixel_width()]
|
||||
)[::-1].transpose(1, 2, 0)
|
||||
uncentered_space_coords = fdiv(
|
||||
uncentered_pixel_coords * full_space_dims,
|
||||
full_pixel_dims)
|
||||
|
@ -541,7 +564,8 @@ class Camera(object):
|
|||
# overflow is unlikely to be a problem)
|
||||
|
||||
centered_space_coords = (
|
||||
uncentered_space_coords - fdiv(full_space_dims, 2))
|
||||
uncentered_space_coords - fdiv(full_space_dims, 2)
|
||||
)
|
||||
|
||||
# Have to also flip the y coordinates to account for pixel array being listed in
|
||||
# top-to-bottom order, opposite of screen coordinate convention
|
||||
|
|
|
@ -43,8 +43,9 @@ class MappingCamera(Camera):
|
|||
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
|
||||
# CameraPlusOverlay class)
|
||||
|
||||
# TODO, the classes below should likely be deleted
|
||||
|
||||
class MultiCamera(Camera):
|
||||
class OldMultiCamera(Camera):
|
||||
def __init__(self, *cameras_with_start_positions, **kwargs):
|
||||
self.shifted_cameras = [
|
||||
DictAsObject(
|
||||
|
@ -52,8 +53,8 @@ class MultiCamera(Camera):
|
|||
"camera": camera_with_start_positions[0],
|
||||
"start_x": camera_with_start_positions[1][1],
|
||||
"start_y": camera_with_start_positions[1][0],
|
||||
"end_x": camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
|
||||
"end_y": camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
|
||||
"end_x": camera_with_start_positions[1][1] + camera_with_start_positions[0].get_pixel_width(),
|
||||
"end_y": camera_with_start_positions[1][0] + camera_with_start_positions[0].get_pixel_height(),
|
||||
})
|
||||
for camera_with_start_positions in cameras_with_start_positions
|
||||
]
|
||||
|
@ -92,23 +93,23 @@ class MultiCamera(Camera):
|
|||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.init_background()
|
||||
|
||||
# A MultiCamera which, when called with two full-size cameras, initializes itself
|
||||
# A OldMultiCamera which, when called with two full-size cameras, initializes itself
|
||||
# as a splitscreen, also taking care to resize each individual camera within it
|
||||
|
||||
|
||||
class SplitScreenCamera(MultiCamera):
|
||||
class SplitScreenCamera(OldMultiCamera):
|
||||
def __init__(self, left_camera, right_camera, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.left_camera = left_camera
|
||||
self.right_camera = right_camera
|
||||
|
||||
half_width = self.pixel_shape[1] / 2
|
||||
half_width = self.get_pixel_width() / 2
|
||||
for camera in [self.left_camera, self.right_camera]:
|
||||
# TODO: Round up on one if width is odd
|
||||
camera.pixel_shape = (self.pixel_shape[0], half_width)
|
||||
camera.init_background()
|
||||
camera.resize_frame_shape()
|
||||
camera.reset()
|
||||
camera.reset_pixel_shape(camera.get_pixel_height(), half_width)
|
||||
|
||||
MultiCamera.__init__(self, (left_camera, (0, 0)),
|
||||
(right_camera, (0, half_width)))
|
||||
OldMultiCamera.__init__(
|
||||
self,
|
||||
(left_camera, (0, 0)),
|
||||
(right_camera, (0, half_width)),
|
||||
)
|
||||
|
|
|
@ -1,40 +1,77 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import FRAME_HEIGHT
|
||||
from constants import WHITE
|
||||
|
||||
from camera.camera import Camera
|
||||
from mobject.frame import ScreenRectangle
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
|
||||
class MovingCamera(Camera):
|
||||
"""
|
||||
Stays in line with the height, width and position
|
||||
of a given mobject
|
||||
Stays in line with the height, width and position of it's 'frame', which is a Rectangle
|
||||
"""
|
||||
|
||||
CONFIG = {
|
||||
"aligned_dimension": "width" # or height
|
||||
"fixed_dimension": 0, # width
|
||||
"default_frame_stroke_color": WHITE,
|
||||
"default_frame_stroke_width": 0,
|
||||
}
|
||||
|
||||
def __init__(self, frame=None, **kwargs):
|
||||
"""
|
||||
frame is a Mobject, (should be a rectangle) determining
|
||||
which region of space the camera displys
|
||||
frame is a Mobject, (should almost certainly be a rectangle)
|
||||
determining which region of space the camera displys
|
||||
"""
|
||||
digest_config(self, kwargs)
|
||||
if frame is None:
|
||||
frame = ScreenRectangle(height=FRAME_HEIGHT)
|
||||
frame.fade(1)
|
||||
frame.set_stroke(
|
||||
self.default_frame_stroke_color,
|
||||
self.default_frame_stroke_width,
|
||||
)
|
||||
self.frame = frame
|
||||
Camera.__init__(self, **kwargs)
|
||||
|
||||
def capture_mobjects(self, *args, **kwargs):
|
||||
self.space_center = self.frame.get_center()
|
||||
self.realign_frame_shape()
|
||||
Camera.capture_mobjects(self, *args, **kwargs)
|
||||
# TODO, make these work for a rotated frame
|
||||
def get_frame_height(self):
|
||||
return self.frame.get_height()
|
||||
|
||||
def realign_frame_shape(self):
|
||||
height, width = self.frame_shape
|
||||
if self.aligned_dimension == "height":
|
||||
self.frame_shape = (self.frame.get_height(), width)
|
||||
else:
|
||||
self.frame_shape = (height, self.frame.get_width())
|
||||
self.resize_frame_shape(0 if self.aligned_dimension == "height" else 1)
|
||||
def get_frame_width(self):
|
||||
return self.frame.get_width()
|
||||
|
||||
def get_frame_center(self):
|
||||
return self.frame.get_center()
|
||||
|
||||
def set_frame_height(self, frame_height):
|
||||
self.frame.stretch_to_fit_height(frame_height)
|
||||
|
||||
def set_frame_width(self, frame_width):
|
||||
self.frame.stretch_to_fit_width(frame_width)
|
||||
|
||||
def set_frame_center(self, frame_center):
|
||||
self.frame.move_to(frame_center)
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
# self.reset_frame_center()
|
||||
# self.realign_frame_shape()
|
||||
Camera.capture_mobjects(self, mobjects, **kwargs)
|
||||
|
||||
# def reset_frame_center(self):
|
||||
# self.frame_center = self.frame.get_center()
|
||||
|
||||
# def realign_frame_shape(self):
|
||||
# height, width = self.frame_shape
|
||||
# if self.fixed_dimension == 0:
|
||||
# self.frame_shape = (height, self.frame.get_width())
|
||||
# else:
|
||||
# self.frame_shape = (self.frame.get_height(), width)
|
||||
# self.resize_frame_shape(fixed_dimension=self.fixed_dimension)
|
||||
|
||||
def get_mobjects_indicating_movement(self):
|
||||
"""
|
||||
Returns all mobjets whose movement implies that the camera
|
||||
should think of all other mobjects on the screen as moving
|
||||
"""
|
||||
return [self.frame]
|
||||
|
|
59
camera/multi_camera.py
Normal file
59
camera/multi_camera.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from camera.moving_camera import MovingCamera
|
||||
from utils.iterables import list_difference_update
|
||||
|
||||
|
||||
class MultiCamera(MovingCamera):
|
||||
CONFIG = {
|
||||
"allow_cameras_to_capture_their_own_display": False,
|
||||
}
|
||||
|
||||
def __init__(self, *image_mobjects_from_cameras, **kwargs):
|
||||
self.image_mobjects_from_cameras = []
|
||||
for imfc in image_mobjects_from_cameras:
|
||||
self.add_image_mobject_from_camera(imfc)
|
||||
MovingCamera.__init__(self, **kwargs)
|
||||
|
||||
def add_image_mobject_from_camera(self, image_mobject_from_camera):
|
||||
# A silly method to have right now, but maybe there are things
|
||||
# we want to guarantee about any imfc's added later.
|
||||
imfc = image_mobject_from_camera
|
||||
assert(isinstance(imfc.camera, MovingCamera))
|
||||
self.image_mobjects_from_cameras.append(imfc)
|
||||
|
||||
def update_sub_cameras(self):
|
||||
""" Reshape sub_camera pixel_arrays """
|
||||
for imfc in self.image_mobjects_from_cameras:
|
||||
pixel_height, pixel_width = self.get_pixel_array().shape[:2]
|
||||
imfc.camera.frame_shape = (
|
||||
imfc.camera.frame.get_height(),
|
||||
imfc.camera.frame.get_width(),
|
||||
)
|
||||
imfc.camera.reset_pixel_shape(
|
||||
int(pixel_height * imfc.get_height() / self.get_frame_height()),
|
||||
int(pixel_width * imfc.get_width() / self.get_frame_width()),
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
for imfc in self.image_mobjects_from_cameras:
|
||||
imfc.camera.reset()
|
||||
MovingCamera.reset(self)
|
||||
return self
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
self.update_sub_cameras()
|
||||
for imfc in self.image_mobjects_from_cameras:
|
||||
to_add = list(mobjects)
|
||||
if not self.allow_cameras_to_capture_their_own_display:
|
||||
to_add = list_difference_update(
|
||||
to_add, imfc.submobject_family()
|
||||
)
|
||||
imfc.camera.capture_mobjects(to_add, **kwargs)
|
||||
MovingCamera.capture_mobjects(self, mobjects, **kwargs)
|
||||
|
||||
def get_mobjects_indicating_movement(self):
|
||||
return [self.frame] + [
|
||||
imfc.camera.frame
|
||||
for imfc in self.image_mobjects_from_cameras
|
||||
]
|
|
@ -12,6 +12,8 @@ from utils.bezier import interpolate
|
|||
from utils.space_ops import rotation_about_z
|
||||
from utils.space_ops import rotation_matrix
|
||||
|
||||
# TODO: Make sure this plays well with latest camera updates
|
||||
|
||||
|
||||
class CameraWithPerspective(Camera):
|
||||
CONFIG = {
|
||||
|
@ -49,7 +51,7 @@ class ThreeDCamera(CameraWithPerspective):
|
|||
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.moving_center = VectorizedPoint(self.frame_center)
|
||||
self.set_position(self.phi, self.theta, self.distance)
|
||||
|
||||
def modified_rgb(self, vmobject, rgb):
|
||||
|
@ -163,7 +165,7 @@ class ThreeDCamera(CameraWithPerspective):
|
|||
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]
|
||||
self.frame_center = self.moving_center.points[0]
|
||||
|
||||
def get_view_transformation_matrix(self):
|
||||
return (self.default_distance / self.get_distance()) * np.dot(
|
||||
|
@ -174,6 +176,6 @@ class ThreeDCamera(CameraWithPerspective):
|
|||
def points_to_pixel_coords(self, points):
|
||||
matrix = self.get_view_transformation_matrix()
|
||||
new_points = np.dot(points, matrix.T)
|
||||
self.space_center = self.moving_center.points[0]
|
||||
self.frame_center = self.moving_center.points[0]
|
||||
|
||||
return Camera.points_to_pixel_coords(self, new_points)
|
||||
|
|
11
constants.py
11
constants.py
|
@ -17,17 +17,20 @@ LOW_QUALITY_FRAME_DURATION = 1. / 15
|
|||
MEDIUM_QUALITY_FRAME_DURATION = 1. / 30
|
||||
PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60
|
||||
|
||||
# There might be other configuration than pixel_shape later...
|
||||
# There might be other configuration than pixel shape later...
|
||||
PRODUCTION_QUALITY_CAMERA_CONFIG = {
|
||||
"pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
|
||||
"pixel_height": DEFAULT_PIXEL_HEIGHT,
|
||||
"pixel_width": DEFAULT_PIXEL_WIDTH,
|
||||
}
|
||||
|
||||
MEDIUM_QUALITY_CAMERA_CONFIG = {
|
||||
"pixel_shape": (720, 1280),
|
||||
"pixel_height": 720,
|
||||
"pixel_width": 1280,
|
||||
}
|
||||
|
||||
LOW_QUALITY_CAMERA_CONFIG = {
|
||||
"pixel_shape": (480, 854),
|
||||
"pixel_height": 480,
|
||||
"pixel_width": 854,
|
||||
}
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25
|
||||
|
|
|
@ -224,11 +224,8 @@ def get_module_posix(file_name):
|
|||
module_name = file_name.replace(".py", "")
|
||||
last_module = imp.load_module(".", *imp.find_module("."))
|
||||
for part in module_name.split(os.sep):
|
||||
try:
|
||||
load_args = imp.find_module(part, last_module.__path__)
|
||||
last_module = imp.load_module(part, *load_args)
|
||||
except ImportError:
|
||||
continue
|
||||
load_args = imp.find_module(part, last_module.__path__)
|
||||
last_module = imp.load_module(part, *load_args)
|
||||
return last_module
|
||||
|
||||
|
||||
|
|
|
@ -199,6 +199,15 @@ class Mobject(Container):
|
|||
)
|
||||
return self
|
||||
|
||||
def apply_function_to_position(self, function):
|
||||
self.move_to(function(self.get_center()))
|
||||
return self
|
||||
|
||||
def apply_function_to_submobject_positions(self, function):
|
||||
for submob in self.submobjects:
|
||||
submob.apply_function_to_position(function)
|
||||
return self
|
||||
|
||||
def apply_matrix(self, matrix, **kwargs):
|
||||
# Default to applying matrix about the origin, not mobjects center
|
||||
if len(kwargs) == 0:
|
||||
|
|
|
@ -7,25 +7,51 @@ from PIL import Image
|
|||
from constants import *
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.shape_matchers import SurroundingRectangle
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_to_int_rgb
|
||||
from utils.config_ops import digest_config
|
||||
from utils.images import get_full_raster_image_path
|
||||
|
||||
|
||||
class ImageMobject(Mobject):
|
||||
class AbstractImageMobject(Mobject):
|
||||
"""
|
||||
Automatically filters out black pixels
|
||||
"""
|
||||
CONFIG = {
|
||||
"filter_color": "black",
|
||||
"invert": False,
|
||||
# "use_cache" : True,
|
||||
"height": 2.0,
|
||||
"image_mode": "RGBA",
|
||||
"pixel_array_dtype": "uint8",
|
||||
}
|
||||
|
||||
def get_pixel_array(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
def set_color(self):
|
||||
# Likely to be implemented in subclasses, but no obgligation
|
||||
pass
|
||||
|
||||
def init_points(self):
|
||||
# Corresponding corners of image are fixed to these 3 points
|
||||
self.points = np.array([
|
||||
UP + LEFT,
|
||||
UP + RIGHT,
|
||||
DOWN + LEFT,
|
||||
])
|
||||
self.center()
|
||||
h, w = self.get_pixel_array().shape[:2]
|
||||
self.stretch_to_fit_height(self.height)
|
||||
self.stretch_to_fit_width(self.height * w / h)
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
class ImageMobject(AbstractImageMobject):
|
||||
CONFIG = {
|
||||
"invert": False,
|
||||
"image_mode": "RGBA",
|
||||
}
|
||||
|
||||
def __init__(self, filename_or_array, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if isinstance(filename_or_array, str):
|
||||
|
@ -37,7 +63,7 @@ class ImageMobject(Mobject):
|
|||
self.change_to_rgba_array()
|
||||
if self.invert:
|
||||
self.pixel_array[:, :, :3] = 255 - self.pixel_array[:, :, :3]
|
||||
Mobject.__init__(self, **kwargs)
|
||||
AbstractImageMobject.__init__(self, **kwargs)
|
||||
|
||||
def change_to_rgba_array(self):
|
||||
pa = self.pixel_array
|
||||
|
@ -53,6 +79,9 @@ class ImageMobject(Mobject):
|
|||
pa = np.append(pa, alphas, axis=2)
|
||||
self.pixel_array = pa
|
||||
|
||||
def get_pixel_array(self):
|
||||
return self.pixel_array
|
||||
|
||||
def set_color(self, color, alpha=None, family=True):
|
||||
rgb = color_to_int_rgb(color)
|
||||
self.pixel_array[:, :, :3] = rgb
|
||||
|
@ -63,19 +92,6 @@ class ImageMobject(Mobject):
|
|||
self.color = color
|
||||
return self
|
||||
|
||||
def init_points(self):
|
||||
# Corresponding corners of image are fixed to these
|
||||
# Three points
|
||||
self.points = np.array([
|
||||
UP + LEFT,
|
||||
UP + RIGHT,
|
||||
DOWN + LEFT,
|
||||
])
|
||||
self.center()
|
||||
h, w = self.pixel_array.shape[:2]
|
||||
self.stretch_to_fit_height(self.height)
|
||||
self.stretch_to_fit_width(self.height * w / h)
|
||||
|
||||
def set_opacity(self, alpha):
|
||||
self.pixel_array[:, :, 3] = int(255 * alpha)
|
||||
return self
|
||||
|
@ -90,5 +106,30 @@ class ImageMobject(Mobject):
|
|||
mobject1.pixel_array, mobject2.pixel_array, alpha
|
||||
).astype(self.pixel_array_dtype)
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
# TODO, add the ability to have the dimensions/orientation of this
|
||||
# mobject more strongly tied to the frame of the camera it contains,
|
||||
# in the case where that's a MovingCamera
|
||||
|
||||
|
||||
class ImageMobjectFromCamera(AbstractImageMobject):
|
||||
CONFIG = {
|
||||
"default_display_frame_config": {
|
||||
"stroke_width": 3,
|
||||
"stroke_color": WHITE,
|
||||
"buff": 0,
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, camera, **kwargs):
|
||||
self.camera = camera
|
||||
AbstractImageMobject.__init__(self, **kwargs)
|
||||
|
||||
def get_pixel_array(self):
|
||||
return self.camera.get_pixel_array()
|
||||
|
||||
def add_display_frame(self, **kwargs):
|
||||
config = dict(self.default_display_frame_config)
|
||||
config.update(kwargs)
|
||||
self.display_frame = SurroundingRectangle(self, **config)
|
||||
self.add(self.display_frame)
|
||||
return self
|
||||
|
|
|
@ -668,7 +668,7 @@ class ColorMappedByFuncScene(Scene):
|
|||
# We hash just based on output image
|
||||
# Thus, multiple scenes with same output image can re-use it
|
||||
# without recomputation
|
||||
full_hash = hash((func_hash, self.camera.pixel_shape))
|
||||
full_hash = hash((func_hash, self.camera.get_pixel_width()))
|
||||
self.background_image_file = self.short_path_to_long_path(
|
||||
"color_mapped_bg_hash_" + str(full_hash) + ".png"
|
||||
)
|
||||
|
|
|
@ -2884,7 +2884,7 @@ class ZeroFoundOnBoundary(Scene):
|
|||
class AllOfTheVideos(Scene):
|
||||
CONFIG = {
|
||||
"camera_config" : {
|
||||
"background_alpha" : 255,
|
||||
"background_opacity" : 1,
|
||||
}
|
||||
}
|
||||
def construct(self):
|
||||
|
@ -3274,5 +3274,6 @@ class Thumbnail(SearchSpacePerimeterVsArea):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2056,7 +2056,7 @@ class IPTScene1(PiCreatureScene):
|
|||
# use the following for the zoomed inset
|
||||
if show_detail:
|
||||
self.camera.frame_shape = (0.02 * FRAME_HEIGHT, 0.02 * FRAME_WIDTH)
|
||||
self.camera.space_center = C
|
||||
self.camera.frame_center = C
|
||||
SCREEN_SCALE = 0.01
|
||||
SCREEN_THICKNESS = 0.02
|
||||
|
||||
|
|
|
@ -1479,7 +1479,7 @@ class AskAboutLayers(PreviewMNistNetwork):
|
|||
|
||||
class BreakUpMacroPatterns(IntroduceEachLayer):
|
||||
CONFIG = {
|
||||
"camera_config" : {"background_alpha" : 255},
|
||||
"camera_config" : {"background_opacity" : 1},
|
||||
"prefixes" : [
|
||||
"nine", "eight", "four",
|
||||
"upper_loop", "right_line",
|
||||
|
@ -1868,7 +1868,7 @@ class BreakUpMicroPatterns(BreakUpMacroPatterns):
|
|||
class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer):
|
||||
CONFIG = {
|
||||
"camera_config" : {
|
||||
"background_alpha" : 255,
|
||||
"background_opacity" : 1,
|
||||
},
|
||||
"network_mob_config" : {
|
||||
"layer_to_layer_buff" : 2,
|
||||
|
@ -2081,7 +2081,7 @@ class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer):
|
|||
|
||||
class EdgeDetection(Scene):
|
||||
CONFIG = {
|
||||
"camera_config" : {"background_alpha" : 255}
|
||||
"camera_config" : {"background_opacity" : 1}
|
||||
}
|
||||
def construct(self):
|
||||
lion = ImageMobject("Lion")
|
||||
|
|
|
@ -468,7 +468,7 @@ class TauFalls(Scene):
|
|||
class EulerWrites628(Scene):
|
||||
CONFIG = {
|
||||
"camera_config" : {
|
||||
"background_alpha" : 255,
|
||||
"background_opacity" : 1,
|
||||
}
|
||||
}
|
||||
def construct(self):
|
||||
|
@ -548,7 +548,7 @@ class EulerWrites628(Scene):
|
|||
class HeroAndVillain(Scene):
|
||||
CONFIG = {
|
||||
"camera_config" : {
|
||||
"background_alpha" : 255,
|
||||
"background_opacity" : 1,
|
||||
}
|
||||
}
|
||||
def construct(self):
|
||||
|
@ -987,7 +987,7 @@ class SpecialThanks(Scene):
|
|||
class EndScene(PatreonEndScreen):
|
||||
CONFIG = {
|
||||
"camera_config" : {
|
||||
"background_alpha" : 255,
|
||||
"background_opacity" : 1,
|
||||
}
|
||||
}
|
||||
def construct(self):
|
||||
|
|
|
@ -4958,7 +4958,7 @@ class KeeperAndSailorForSineProduct(KeeperAndSailor):
|
|||
|
||||
class Conclusion(TeacherStudentsScene):
|
||||
CONFIG = {
|
||||
"camera_config": {"background_alpha": 255},
|
||||
"camera_config": {"background_opacity": 1},
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from scene.scene import Scene
|
||||
from camera.moving_camera import MovingCamera
|
||||
from mobject.frame import ScreenRectangle
|
||||
from utils.iterables import list_update
|
||||
|
||||
|
||||
class MovingCameraScene(Scene):
|
||||
CONFIG = {
|
||||
"camera_class": MovingCamera
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.camera_frame = ScreenRectangle(height=FRAME_HEIGHT)
|
||||
self.camera_frame.set_stroke(width=0)
|
||||
self.camera = MovingCamera(
|
||||
self.camera_frame, **self.camera_config
|
||||
)
|
||||
Scene.setup(self)
|
||||
assert(isinstance(self.camera, MovingCamera))
|
||||
self.camera_frame = self.camera.frame
|
||||
# Hmm, this currently relies on the fact that MovingCamera
|
||||
# willd default to a full-sized frame. Is that okay?
|
||||
return self
|
||||
|
||||
def get_moving_mobjects(self, *animations):
|
||||
# TODO: Code repetition from ZoomedScene
|
||||
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
||||
if self.camera_frame in moving_mobjects:
|
||||
# When the camera is moving, so is everything,
|
||||
return self.mobjects
|
||||
else:
|
||||
return moving_mobjects
|
||||
all_moving_mobjects = self.camera.extract_mobject_family_members(
|
||||
moving_mobjects
|
||||
)
|
||||
movement_indicators = self.camera.get_mobjects_indicating_movement()
|
||||
for movement_indicator in movement_indicators:
|
||||
if movement_indicator in all_moving_mobjects:
|
||||
# When one of these is moving, the camera should
|
||||
# consider all mobjects to be moving
|
||||
return list_update(self.mobjects, moving_mobjects)
|
||||
return moving_mobjects
|
||||
|
|
|
@ -572,7 +572,8 @@ class Scene(Container):
|
|||
self.args_to_rename_file = (temp_file_path, file_path)
|
||||
|
||||
fps = int(1 / self.frame_duration)
|
||||
height, width = self.camera.pixel_shape
|
||||
height = self.camera.get_pixel_height()
|
||||
width = self.camera.get_pixel_width()
|
||||
|
||||
command = [
|
||||
FFMPEG_BIN,
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from scene.scene import Scene
|
||||
from animation.creation import FadeIn
|
||||
from scene.moving_camera_scene import MovingCameraScene
|
||||
from camera.moving_camera import MovingCamera
|
||||
from mobject.geometry import Rectangle
|
||||
from camera.multi_camera import MultiCamera
|
||||
from mobject.types.image_mobject import ImageMobjectFromCamera
|
||||
from utils.simple_functions import fdiv
|
||||
|
||||
from constants import *
|
||||
from animation.transform import ApplyMethod
|
||||
|
||||
# Note, any scenes from old videos using ZoomedScene will almost certainly
|
||||
# break, as it was restructured.
|
||||
|
||||
|
||||
class ZoomedScene(Scene):
|
||||
"""
|
||||
Move around self.little_rectangle to determine
|
||||
which part of the screen is zoomed in on.
|
||||
"""
|
||||
class ZoomedScene(MovingCameraScene):
|
||||
CONFIG = {
|
||||
"camera_class": MultiCamera,
|
||||
"zoomed_display_height": 3,
|
||||
|
@ -33,109 +32,62 @@ class ZoomedScene(Scene):
|
|||
"zoom_activated": False,
|
||||
}
|
||||
|
||||
def activate_zooming(self):
|
||||
self.generate_big_rectangle()
|
||||
self.setup_zoomed_canvas()
|
||||
self.setup_zoomed_camera()
|
||||
self.zoom_activated = True
|
||||
|
||||
def animate_activate_zooming(self):
|
||||
self.activate_zooming()
|
||||
self.play(*map(FadeIn, [
|
||||
self.little_rectangle, self.big_rectangle
|
||||
]))
|
||||
|
||||
def disactivate_zooming(self):
|
||||
self.remove(self.big_rectangle, self.little_rectangle)
|
||||
self.zoom_activated = False
|
||||
|
||||
def get_zoomed_camera_mobject(self):
|
||||
return self.little_rectangle
|
||||
|
||||
def get_zoomed_screen(self):
|
||||
return self.big_rectangle
|
||||
|
||||
def generate_big_rectangle(self):
|
||||
height, width = self.zoomed_canvas_frame_shape
|
||||
self.big_rectangle = Rectangle(
|
||||
height=height,
|
||||
width=width,
|
||||
color=self.square_color
|
||||
def setup(self):
|
||||
MovingCameraScene.setup(self)
|
||||
# Initialize camera and display
|
||||
zoomed_camera = MovingCamera(**self.zoomed_camera_config)
|
||||
zoomed_display = ImageMobjectFromCamera(
|
||||
zoomed_camera, **self.zoomed_camera_image_mobject_config
|
||||
)
|
||||
if self.zoomed_canvas_center is not None:
|
||||
self.big_rectangle.shift(self.zoomed_canvas_center)
|
||||
elif self.zoomed_canvas_corner is not None:
|
||||
self.big_rectangle.to_corner(
|
||||
self.zoomed_canvas_corner,
|
||||
buff=self.zoomed_canvas_corner_buff
|
||||
)
|
||||
self.add(self.big_rectangle)
|
||||
zoomed_display.add_display_frame()
|
||||
for mob in zoomed_camera.frame, zoomed_display:
|
||||
mob.stretch_to_fit_height(self.zoomed_display_height)
|
||||
mob.stretch_to_fit_width(self.zoomed_display_width)
|
||||
zoomed_camera.frame.scale(self.zoom_factor)
|
||||
|
||||
def setup_zoomed_canvas(self):
|
||||
upper_left = self.big_rectangle.get_corner(UP + LEFT)
|
||||
lower_right = self.big_rectangle.get_corner(DOWN + RIGHT)
|
||||
pixel_coords = self.camera.points_to_pixel_coords(
|
||||
np.array([upper_left, lower_right])
|
||||
)
|
||||
self.zoomed_canvas_pixel_indices = pixel_coords
|
||||
(up, left), (down, right) = pixel_coords
|
||||
self.zoomed_canvas_pixel_shape = (
|
||||
right - left,
|
||||
down - up,
|
||||
)
|
||||
|
||||
def setup_zoomed_camera(self):
|
||||
self.little_rectangle = self.big_rectangle.copy()
|
||||
self.little_rectangle.scale(1. / self.zoom_factor)
|
||||
self.little_rectangle.move_to(
|
||||
self.little_rectangle_start_position
|
||||
)
|
||||
self.zoomed_camera = MovingCamera(
|
||||
self.little_rectangle,
|
||||
pixel_shape=self.zoomed_canvas_pixel_shape,
|
||||
background=self.zoomed_camera_background
|
||||
)
|
||||
self.add(self.little_rectangle)
|
||||
# TODO, is there a better way to hanld this?
|
||||
self.zoomed_camera.adjusted_thickness = lambda x: x
|
||||
|
||||
def get_frame(self):
|
||||
frame = Scene.get_frame(self)
|
||||
if self.zoom_activated:
|
||||
(up, left), (down, right) = self.zoomed_canvas_pixel_indices
|
||||
frame[left:right, up:down, :] = self.zoomed_camera.get_image()
|
||||
return frame
|
||||
|
||||
def set_camera_pixel_array(self, pixel_array):
|
||||
self.camera.set_pixel_array(pixel_array)
|
||||
if self.zoom_activated:
|
||||
(up, left), (down, right) = self.zoomed_canvas_pixel_indices
|
||||
self.zoomed_camera.set_pixel_array(
|
||||
pixel_array[left:right, up:down])
|
||||
|
||||
def set_camera_background(self, background):
|
||||
self.set_camera_pixel_array(self, background)
|
||||
# TODO, check this...
|
||||
|
||||
def reset_camera(self):
|
||||
self.camera.reset()
|
||||
if self.zoom_activated:
|
||||
self.zoomed_camera.reset()
|
||||
|
||||
def capture_mobjects_in_camera(self, mobjects, **kwargs):
|
||||
self.camera.capture_mobjects(mobjects, **kwargs)
|
||||
if self.zoom_activated:
|
||||
if self.big_rectangle in mobjects:
|
||||
mobjects = list(mobjects)
|
||||
mobjects.remove(self.big_rectangle)
|
||||
self.zoomed_camera.capture_mobjects(
|
||||
mobjects, **kwargs
|
||||
)
|
||||
|
||||
def get_moving_mobjects(self, *animations):
|
||||
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
||||
if self.zoom_activated and self.little_rectangle in moving_mobjects:
|
||||
# When the camera is moving, so is everything,
|
||||
return self.mobjects
|
||||
# Position camera and display
|
||||
zoomed_camera.frame.move_to(self.zoomed_camera_frame_starting_position)
|
||||
if self.zoomed_display_center is not None:
|
||||
zoomed_display.move_to(self.zoomed_display_center)
|
||||
else:
|
||||
return moving_mobjects
|
||||
zoomed_display.to_corner(
|
||||
self.zoomed_display_corner,
|
||||
buff=self.zoomed_display_corner_buff
|
||||
)
|
||||
|
||||
self.zoomed_camera = zoomed_camera
|
||||
self.zoomed_display = zoomed_display
|
||||
|
||||
def activate_zooming(self, animate=False):
|
||||
self.zoom_activated = True
|
||||
self.camera.add_image_mobject_from_camera(self.zoomed_display)
|
||||
if animate:
|
||||
self.play(self.get_zoom_in_animation())
|
||||
self.play(self.get_zoomed_display_pop_out_animation())
|
||||
self.add_foreground_mobjects(
|
||||
self.zoomed_camera.frame,
|
||||
self.zoomed_display,
|
||||
)
|
||||
|
||||
def get_zoom_in_animation(self, run_time=2, **kwargs):
|
||||
frame = self.zoomed_camera.frame
|
||||
full_frame_height = self.camera.get_frame_height()
|
||||
full_frame_width = self.camera.get_frame_width()
|
||||
frame.save_state()
|
||||
frame.stretch_to_fit_width(full_frame_width)
|
||||
frame.stretch_to_fit_height(full_frame_height)
|
||||
frame.center()
|
||||
frame.set_stroke(width=0)
|
||||
return ApplyMethod(frame.restore, run_time=run_time, **kwargs)
|
||||
|
||||
def get_zoomed_display_pop_out_animation(self, **kwargs):
|
||||
display = self.zoomed_display
|
||||
display.save_state(use_deepcopy=True)
|
||||
display.replace(self.zoomed_camera.frame, stretch=True)
|
||||
return ApplyMethod(display.restore)
|
||||
|
||||
def get_zoom_factor(self):
|
||||
return fdiv(
|
||||
self.zoomed_camera.frame.get_height(),
|
||||
self.zoomed_display.get_height()
|
||||
)
|
||||
|
|
|
@ -39,7 +39,8 @@ def color_to_int_rgb(color):
|
|||
return (255 * color_to_rgb(color)).astype('uint8')
|
||||
|
||||
|
||||
def color_to_int_rgba(color, alpha=255):
|
||||
def color_to_int_rgba(color, opacity=1.0):
|
||||
alpha = int(255 * opacity)
|
||||
return np.append(color_to_int_rgb(color), alpha)
|
||||
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ def get_scene_output_directory(scene_class):
|
|||
def get_movie_output_directory(scene_class, camera_config, frame_duration):
|
||||
directory = get_scene_output_directory(scene_class)
|
||||
sub_dir = "%dp%d" % (
|
||||
camera_config["pixel_shape"][0],
|
||||
camera_config["pixel_height"],
|
||||
int(1.0 / frame_duration)
|
||||
)
|
||||
return guarantee_existance(os.path.join(directory, sub_dir))
|
||||
|
|
Loading…
Add table
Reference in a new issue