Remove old constructs that were too specific to old 3b1b videos and are not worth fixing

Or rather, move them over to 3b1b/videos repo
This commit is contained in:
Grant Sanderson 2022-12-16 10:26:04 -08:00
parent 0ad2f18ca6
commit e4aebaf791
6 changed files with 0 additions and 1607 deletions

View file

@ -1,156 +0,0 @@
from manimlib.animation.animation import Animation
from manimlib.animation.movement import ComplexHomotopy
from manimlib.animation.transform import MoveToTarget
from manimlib.constants import *
from manimlib.mobject.coordinate_systems import ComplexPlane
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.scene.scene import Scene
# TODO, refactor this full scene
class ComplexTransformationScene(Scene):
CONFIG = {
"plane_config": {},
"background_fade_factor": 0.5,
"use_multicolored_plane": False,
"vert_start_color": BLUE, # TODO
"vert_end_color": BLUE,
"horiz_start_color": BLUE,
"horiz_end_color": BLUE,
"num_anchors_to_add_per_line": 50,
"post_transformation_stroke_width": None,
"default_apply_complex_function_kwargs": {
"run_time": 5,
},
"background_label_scale_val": 0.5,
"include_coordinate_labels": True,
}
def setup(self):
self.foreground_mobjects = []
self.transformable_mobjects = []
self.add_background_plane()
if self.include_coordinate_labels:
self.add_coordinate_labels()
def add_foreground_mobject(self, mobject):
self.add_foreground_mobjects(mobject)
def add_transformable_mobjects(self, *mobjects):
self.transformable_mobjects += list(mobjects)
self.add(*mobjects)
def add_foreground_mobjects(self, *mobjects):
self.foreground_mobjects += list(mobjects)
Scene.add(self, *mobjects)
def add(self, *mobjects):
Scene.add(self, *list(mobjects) + self.foreground_mobjects)
def play(self, *animations, **kwargs):
Scene.play(
self,
*list(animations) + list(map(Animation, self.foreground_mobjects)),
**kwargs
)
def add_background_plane(self):
background = ComplexPlane(**self.plane_config)
background.fade(self.background_fade_factor)
self.add(background)
self.background = background
def add_coordinate_labels(self):
self.background.add_coordinates()
self.add(self.background)
def add_transformable_plane(self, **kwargs):
self.plane = self.get_transformable_plane()
self.add(self.plane)
def get_transformable_plane(self, x_range=None, y_range=None):
"""
x_range and y_range would be tuples (min, max)
"""
plane_config = dict(self.plane_config)
shift_val = ORIGIN
if x_range is not None:
x_min, x_max = x_range
plane_config["x_radius"] = x_max - x_min
shift_val += (x_max + x_min) * RIGHT / 2.
if y_range is not None:
y_min, y_max = y_range
plane_config["y_radius"] = y_max - y_min
shift_val += (y_max + y_min) * UP / 2.
plane = ComplexPlane(**plane_config)
plane.shift(shift_val)
if self.use_multicolored_plane:
self.paint_plane(plane)
return plane
def prepare_for_transformation(self, mob):
if hasattr(mob, "prepare_for_nonlinear_transform"):
mob.prepare_for_nonlinear_transform(
self.num_anchors_to_add_per_line
)
# TODO...
def paint_plane(self, plane):
for lines in planes, plane.secondary_lines:
lines.set_color_by_gradient(
self.vert_start_color,
self.vert_end_color,
self.horiz_start_color,
self.horiz_end_color,
)
# plane.axes.set_color_by_gradient(
# self.horiz_start_color,
# self.vert_start_color
# )
def z_to_point(self, z):
return self.background.number_to_point(z)
def get_transformer(self, **kwargs):
transform_kwargs = dict(self.default_apply_complex_function_kwargs)
transform_kwargs.update(kwargs)
transformer = VGroup()
if hasattr(self, "plane"):
self.prepare_for_transformation(self.plane)
transformer.add(self.plane)
transformer.add(*self.transformable_mobjects)
return transformer, transform_kwargs
def apply_complex_function(self, func, added_anims=[], **kwargs):
transformer, transform_kwargs = self.get_transformer(**kwargs)
transformer.generate_target()
# Rescale, apply function, scale back
transformer.target.shift(-self.background.get_center_point())
transformer.target.scale(1. / self.background.unit_size)
transformer.target.apply_complex_function(func)
transformer.target.scale(self.background.unit_size)
transformer.target.shift(self.background.get_center_point())
#
for mob in transformer.target[0].family_members_with_points():
mob.make_smooth()
if self.post_transformation_stroke_width is not None:
transformer.target.set_stroke(
width=self.post_transformation_stroke_width)
self.play(
MoveToTarget(transformer, **transform_kwargs),
*added_anims
)
def apply_complex_homotopy(self, complex_homotopy, added_anims=[], **kwargs):
transformer, transform_kwargs = self.get_transformer(**kwargs)
# def homotopy(x, y, z, t):
# output = complex_homotopy(complex(x, y), t)
# rescaled_output = self.z_to_point(output)
# return (rescaled_output.real, rescaled_output.imag, z)
self.play(
ComplexHomotopy(complex_homotopy, transformer, **transform_kwargs),
*added_anims
)

View file

@ -1,263 +0,0 @@
from manimlib.animation.creation import ShowCreation
from manimlib.animation.fading import FadeIn
from manimlib.animation.transform import MoveToTarget
from manimlib.animation.transform import Transform
from manimlib.constants import *
from manimlib.mobject.geometry import Arrow
from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Dot
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.scene.scene import Scene
import itertools as it
class CountingScene(Scene):
CONFIG = {
"digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D],
"counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP,
"count_dot_starting_radius": 0.5,
"dot_configuration_height": 2,
"ones_configuration_location": UP + 2 * RIGHT,
"num_scale_factor": 2,
"num_start_location": 2 * DOWN,
}
def setup(self):
self.dots = VGroup()
self.number = 0
self.max_place = 0
self.number_mob = VGroup(Tex(str(self.number)))
self.number_mob.scale(self.num_scale_factor)
self.number_mob.shift(self.num_start_location)
self.dot_templates = []
self.dot_template_iterators = []
self.curr_configurations = []
self.arrows = VGroup()
self.add(self.number_mob)
def get_template_configuration(self, place):
# This should probably be replaced for non-base-10 counting scenes
down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN
result = []
for down_right_steps in range(5):
for left_steps in range(down_right_steps):
result.append(
down_right_steps * down_right + left_steps * LEFT
)
return reversed(result[:self.get_place_max(place)])
def get_dot_template(self, place):
# This should be replaced for non-base-10 counting scenes
dots = VGroup(*[
Dot(
point,
radius=0.25,
fill_opacity=0,
stroke_width=2,
stroke_color=WHITE,
)
for point in self.get_template_configuration(place)
])
dots.set_height(self.dot_configuration_height)
return dots
def add_configuration(self):
new_template = self.get_dot_template(len(self.dot_templates))
new_template.move_to(self.ones_configuration_location)
left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT
new_template.shift(
left_vect * len(self.dot_templates)
)
self.dot_templates.append(new_template)
self.dot_template_iterators.append(
it.cycle(new_template)
)
self.curr_configurations.append(VGroup())
def count(self, max_val, run_time_per_anim=1):
for x in range(max_val):
self.increment(run_time_per_anim)
def increment(self, run_time_per_anim=1):
moving_dot = Dot(
self.counting_dot_starting_position,
radius=self.count_dot_starting_radius,
color=self.digit_place_colors[0],
)
moving_dot.generate_target()
moving_dot.set_fill(opacity=0)
kwargs = {
"run_time": run_time_per_anim
}
continue_rolling_over = True
first_move = True
place = 0
while continue_rolling_over:
added_anims = []
if first_move:
added_anims += self.get_digit_increment_animations()
first_move = False
moving_dot.target.replace(
next(self.dot_template_iterators[place])
)
self.play(MoveToTarget(moving_dot), *added_anims, **kwargs)
self.curr_configurations[place].add(moving_dot)
if len(self.curr_configurations[place].split()) == self.get_place_max(place):
full_configuration = self.curr_configurations[place]
self.curr_configurations[place] = VGroup()
place += 1
center = full_configuration.get_center_of_mass()
radius = 0.6 * max(
full_configuration.get_width(),
full_configuration.get_height(),
)
circle = Circle(
radius=radius,
stroke_width=0,
fill_color=self.digit_place_colors[place],
fill_opacity=0.5,
)
circle.move_to(center)
moving_dot = VGroup(circle, full_configuration)
moving_dot.generate_target()
moving_dot[0].set_fill(opacity=0)
else:
continue_rolling_over = False
def get_digit_increment_animations(self):
result = []
self.number += 1
is_next_digit = self.is_next_digit()
if is_next_digit:
self.max_place += 1
new_number_mob = self.get_number_mob(self.number)
new_number_mob.move_to(self.number_mob, RIGHT)
if is_next_digit:
self.add_configuration()
place = len(new_number_mob.split()) - 1
result.append(FadeIn(self.dot_templates[place]))
arrow = Arrow(
new_number_mob[place].get_top(),
self.dot_templates[place].get_bottom(),
color=self.digit_place_colors[place]
)
self.arrows.add(arrow)
result.append(ShowCreation(arrow))
result.append(Transform(
self.number_mob, new_number_mob,
lag_ratio=0.5
))
return result
def get_number_mob(self, num):
result = VGroup()
place = 0
max_place = self.max_place
while place < max_place:
digit = Tex(str(self.get_place_num(num, place)))
if place >= len(self.digit_place_colors):
self.digit_place_colors += self.digit_place_colors
digit.set_color(self.digit_place_colors[place])
digit.scale(self.num_scale_factor)
digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN)
result.add(digit)
place += 1
return result
def is_next_digit(self):
return False
def get_place_num(self, num, place):
return 0
def get_place_max(self, place):
return 0
class PowerCounter(CountingScene):
def is_next_digit(self):
number = self.number
while number > 1:
if number % self.base != 0:
return False
number /= self.base
return True
def get_place_max(self, place):
return self.base
def get_place_num(self, num, place):
return (num / (self.base ** place)) % self.base
class CountInDecimal(PowerCounter):
CONFIG = {
"base": 10,
}
def construct(self):
for x in range(11):
self.increment()
for x in range(85):
self.increment(0.25)
for x in range(20):
self.increment()
class CountInTernary(PowerCounter):
CONFIG = {
"base": 3,
"dot_configuration_height": 1,
"ones_configuration_location": UP + 4 * RIGHT
}
def construct(self):
self.count(27)
# def get_template_configuration(self, place):
# return [ORIGIN, UP]
class CountInBinaryTo256(PowerCounter):
CONFIG = {
"base": 2,
"dot_configuration_height": 1,
"ones_configuration_location": UP + 5 * RIGHT
}
def construct(self):
self.count(128, 0.3)
def get_template_configuration(self, place):
return [ORIGIN, UP]
class FactorialBase(CountingScene):
CONFIG = {
"dot_configuration_height": 1,
"ones_configuration_location": UP + 4 * RIGHT
}
def construct(self):
self.count(30, 0.4)
def is_next_digit(self):
return self.number == self.factorial(self.max_place + 1)
def get_place_max(self, place):
return place + 2
def get_place_num(self, num, place):
return (num / self.factorial(place + 1)) % self.get_place_max(place)
def factorial(self, n):
if (n == 1):
return 1
else:
return n * self.factorial(n - 1)

View file

@ -1,413 +0,0 @@
from functools import reduce
import itertools as it
import operator as op
import numpy as np
from manimlib.constants import *
from manimlib.scene.scene import Scene
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.space_ops import center_of_mass
class Graph():
def __init__(self):
# List of points in R^3
# vertices = []
# List of pairs of indices of vertices
# edges = []
# List of tuples of indices of vertices. The last should
# be a cycle whose interior is the entire graph, and when
# regions are computed its complement will be taken.
# region_cycles = []
self.construct()
def construct(self):
pass
def __str__(self):
return self.__class__.__name__
class CubeGraph(Graph):
"""
5 7
12
03
4 6
"""
def construct(self):
self.vertices = [
(x, y, 0)
for r in (1, 2)
for x, y in it.product([-r, r], [-r, r])
]
self.edges = [
(0, 1),
(0, 2),
(3, 1),
(3, 2),
(4, 5),
(4, 6),
(7, 5),
(7, 6),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
]
self.region_cycles = [
[0, 2, 3, 1],
[4, 0, 1, 5],
[4, 6, 2, 0],
[6, 7, 3, 2],
[7, 5, 1, 3],
[4, 6, 7, 5], # By convention, last region will be "outside"
]
class SampleGraph(Graph):
"""
4 2 3 8
0 1
7
5 6
"""
def construct(self):
self.vertices = [
(0, 0, 0),
(2, 0, 0),
(1, 2, 0),
(3, 2, 0),
(-1, 2, 0),
(-2, -2, 0),
(2, -2, 0),
(4, -1, 0),
(6, 2, 0),
]
self.edges = [
(0, 1),
(1, 2),
(1, 3),
(3, 2),
(2, 4),
(4, 0),
(2, 0),
(4, 5),
(0, 5),
(1, 5),
(5, 6),
(6, 7),
(7, 1),
(7, 8),
(8, 3),
]
self.region_cycles = [
(0, 1, 2),
(1, 3, 2),
(2, 4, 0),
(4, 5, 0),
(0, 5, 1),
(1, 5, 6, 7),
(1, 7, 8, 3),
(4, 5, 6, 7, 8, 3, 2),
]
class OctohedronGraph(Graph):
"""
3
1 0
2
4 5
"""
def construct(self):
self.vertices = [
(r * np.cos(angle), r * np.sin(angle) - 1, 0)
for r, s in [(1, 0), (3, 3)]
for angle in (np.pi / 6) * np.array([s, 4 + s, 8 + s])
]
self.edges = [
(0, 1),
(1, 2),
(2, 0),
(5, 0),
(0, 3),
(3, 5),
(3, 1),
(3, 4),
(1, 4),
(4, 2),
(4, 5),
(5, 2),
]
self.region_cycles = [
(0, 1, 2),
(0, 5, 3),
(3, 1, 0),
(3, 4, 1),
(1, 4, 2),
(2, 4, 5),
(5, 0, 2),
(3, 4, 5),
]
class CompleteGraph(Graph):
def __init__(self, num_vertices, radius=3):
self.num_vertices = num_vertices
self.radius = radius
Graph.__init__(self)
def construct(self):
self.vertices = [
(self.radius * np.cos(theta), self.radius * np.sin(theta), 0)
for x in range(self.num_vertices)
for theta in [2 * np.pi * x / self.num_vertices]
]
self.edges = it.combinations(list(range(self.num_vertices)), 2)
def __str__(self):
return Graph.__str__(self) + str(self.num_vertices)
class DiscreteGraphScene(Scene):
args_list = [
(CubeGraph(),),
(SampleGraph(),),
(OctohedronGraph(),),
]
@staticmethod
def args_to_string(*args):
return str(args[0])
def __init__(self, graph, *args, **kwargs):
# See CubeGraph() above for format of graph
self.graph = graph
Scene.__init__(self, *args, **kwargs)
def construct(self):
self._points = list(map(np.array, self.graph.vertices))
self.vertices = self.dots = [Dot(p) for p in self._points]
self.edges = self.lines = [
Line(self._points[i], self._points[j])
for i, j in self.graph.edges
]
self.add(*self.dots + self.edges)
def generate_regions(self):
regions = [
self.region_from_cycle(cycle)
for cycle in self.graph.region_cycles
]
regions[-1].complement() # Outer region painted outwardly...
self.regions = regions
def region_from_cycle(self, cycle):
point_pairs = [
[
self._points[cycle[i]],
self._points[cycle[(i + 1) % len(cycle)]]
]
for i in range(len(cycle))
]
return region_from_line_boundary(
*point_pairs, shape=self.shape
)
def draw_vertices(self, **kwargs):
self.clear()
self.play(ShowCreation(Mobject(*self.vertices), **kwargs))
def draw_edges(self):
self.play(*[
ShowCreation(edge, run_time=1.0)
for edge in self.edges
])
def accent_vertices(self, **kwargs):
self.remove(*self.vertices)
start = Mobject(*self.vertices)
end = Mobject(*[
Dot(point, radius=3 * Dot.DEFAULT_RADIUS, color="lightgreen")
for point in self._points
])
self.play(Transform(
start, end, rate_func=there_and_back,
**kwargs
))
self.remove(start)
self.add(*self.vertices)
def replace_vertices_with(self, mobject):
mobject.center()
diameter = max(mobject.get_height(), mobject.get_width())
self.play(*[
CounterclockwiseTransform(
vertex,
mobject.copy().shift(vertex.get_center())
)
for vertex in self.vertices
] + [
ApplyMethod(
edge.scale,
(edge.get_length() - diameter) / edge.get_length()
)
for edge in self.edges
])
def annotate_edges(self, mobject, fade_in=True, **kwargs):
angles = list(map(np.arctan, list(map(Line.get_slope, self.edges))))
self.edge_annotations = [
mobject.copy().rotate(angle).move_to(edge.get_center())
for angle, edge in zip(angles, self.edges)
]
if fade_in:
self.play(*[
FadeIn(ann, **kwargs)
for ann in self.edge_annotations
])
def trace_cycle(self, cycle=None, color="yellow", run_time=2.0):
if cycle is None:
cycle = self.graph.region_cycles[0]
next_in_cycle = it.cycle(cycle)
next(next_in_cycle) # jump one ahead
self.traced_cycle = Mobject(*[
Line(self._points[i], self._points[j]).set_color(color)
for i, j in zip(cycle, next_in_cycle)
])
self.play(
ShowCreation(self.traced_cycle),
run_time=run_time
)
def generate_spanning_tree(self, root=0, color="yellow"):
self.spanning_tree_root = 0
pairs = deepcopy(self.graph.edges)
pairs += [tuple(reversed(pair)) for pair in pairs]
self.spanning_tree_index_pairs = []
curr = root
spanned_vertices = set([curr])
to_check = set([curr])
while len(to_check) > 0:
curr = to_check.pop()
for pair in pairs:
if pair[0] == curr and pair[1] not in spanned_vertices:
self.spanning_tree_index_pairs.append(pair)
spanned_vertices.add(pair[1])
to_check.add(pair[1])
self.spanning_tree = Mobject(*[
Line(
self._points[pair[0]],
self._points[pair[1]]
).set_color(color)
for pair in self.spanning_tree_index_pairs
])
def generate_treeified_spanning_tree(self):
bottom = -FRAME_Y_RADIUS + 1
x_sep = 1
y_sep = 2
if not hasattr(self, "spanning_tree"):
self.generate_spanning_tree()
root = self.spanning_tree_root
color = self.spanning_tree.get_color()
indices = list(range(len(self._points)))
# Build dicts
parent_of = dict([
tuple(reversed(pair))
for pair in self.spanning_tree_index_pairs
])
children_of = dict([(index, []) for index in indices])
for child in parent_of:
children_of[parent_of[child]].append(child)
x_coord_of = {root: 0}
y_coord_of = {root: bottom}
# width to allocate to a given node, computed as
# the maximum number of decendents in a single generation,
# minus 1, multiplied by x_sep
width_of = {}
for index in indices:
next_generation = children_of[index]
curr_max = max(1, len(next_generation))
while next_generation != []:
next_generation = reduce(op.add, [
children_of[node]
for node in next_generation
])
curr_max = max(curr_max, len(next_generation))
width_of[index] = x_sep * (curr_max - 1)
to_process = [root]
while to_process != []:
index = to_process.pop()
if index not in y_coord_of:
y_coord_of[index] = y_sep + y_coord_of[parent_of[index]]
children = children_of[index]
left_hand = x_coord_of[index] - width_of[index] / 2.0
for child in children:
x_coord_of[child] = left_hand + width_of[child] / 2.0
left_hand += width_of[child] + x_sep
to_process += children
new_points = [
np.array([
x_coord_of[index],
y_coord_of[index],
0
])
for index in indices
]
self.treeified_spanning_tree = Mobject(*[
Line(new_points[i], new_points[j]).set_color(color)
for i, j in self.spanning_tree_index_pairs
])
def generate_dual_graph(self):
point_at_infinity = np.array([np.inf] * 3)
cycles = self.graph.region_cycles
self.dual_points = [
center_of_mass([
self._points[index]
for index in cycle
])
for cycle in cycles
]
self.dual_vertices = [
Dot(point).set_color("green")
for point in self.dual_points
]
self.dual_vertices[-1] = Circle().scale(FRAME_X_RADIUS + FRAME_Y_RADIUS)
self.dual_points[-1] = point_at_infinity
self.dual_edges = []
for pair in self.graph.edges:
dual_point_pair = []
for cycle in cycles:
if not (pair[0] in cycle and pair[1] in cycle):
continue
index1, index2 = cycle.index(pair[0]), cycle.index(pair[1])
if abs(index1 - index2) in [1, len(cycle) - 1]:
dual_point_pair.append(
self.dual_points[cycles.index(cycle)]
)
assert(len(dual_point_pair) == 2)
for i in 0, 1:
if all(dual_point_pair[i] == point_at_infinity):
new_point = np.array(dual_point_pair[1 - i])
vect = center_of_mass([
self._points[pair[0]],
self._points[pair[1]]
]) - new_point
new_point += FRAME_X_RADIUS * vect / get_norm(vect)
dual_point_pair[i] = new_point
self.dual_edges.append(
Line(*dual_point_pair).set_color()
)

View file

@ -1,602 +0,0 @@
from traceback import *
from scipy.spatial import ConvexHull
from manimlib.animation.composition import LaggedStartMap
from manimlib.animation.fading import FadeIn
from manimlib.animation.fading import FadeOut
from manimlib.animation.transform import Transform
from manimlib.constants import *
from manimlib.mobject.geometry import AnnularSector
from manimlib.mobject.geometry import Annulus
from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
from manimlib.utils.space_ops import angle_between_vectors
from manimlib.utils.space_ops import project_along_vector
from manimlib.utils.space_ops import rotate_vector
from manimlib.utils.space_ops import z_to_vector
LIGHT_COLOR = YELLOW
SHADOW_COLOR = BLACK
SWITCH_ON_RUN_TIME = 1.5
FAST_SWITCH_ON_RUN_TIME = 0.1
NUM_LEVELS = 30
NUM_CONES = 7 # in first lighthouse scene
NUM_VISIBLE_CONES = 5 # ibidem
ARC_TIP_LENGTH = 0.2
AMBIENT_FULL = 0.8
AMBIENT_DIMMED = 0.5
SPOTLIGHT_FULL = 0.8
SPOTLIGHT_DIMMED = 0.5
LIGHTHOUSE_HEIGHT = 0.8
DEGREES = TAU / 360
def inverse_power_law(maxint, scale, cutoff, exponent):
return (lambda r: maxint * (cutoff / (r / scale + cutoff))**exponent)
def inverse_quadratic(maxint, scale, cutoff):
return inverse_power_law(maxint, scale, cutoff, 2)
class SwitchOn(LaggedStartMap):
CONFIG = {
"lag_ratio": 0.2,
"run_time": SWITCH_ON_RUN_TIME
}
def __init__(self, light, **kwargs):
if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)):
raise Exception(
"Only AmbientLights and Spotlights can be switched on")
LaggedStartMap.__init__(
self, FadeIn, light, **kwargs
)
class SwitchOff(LaggedStartMap):
CONFIG = {
"lag_ratio": 0.2,
"run_time": SWITCH_ON_RUN_TIME
}
def __init__(self, light, **kwargs):
if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)):
raise Exception(
"Only AmbientLights and Spotlights can be switched off")
light.set_submobjects(light.submobjects[::-1])
LaggedStartMap.__init__(self, FadeOut, light, **kwargs)
light.set_submobjects(light.submobjects[::-1])
class Lighthouse(SVGMobject):
CONFIG = {
"height": LIGHTHOUSE_HEIGHT,
"fill_color": WHITE,
"fill_opacity": 1.0,
}
def __init__(self, **kwargs):
super().__init__("lighthouse", **kwargs)
def move_to(self, point):
self.next_to(point, DOWN, buff=0)
class AmbientLight(VMobject):
# Parameters are:
# * a source point
# * an opacity function
# * a light color
# * a max opacity
# * a radius (larger than the opacity's dropoff length)
# * the number of subdivisions (levels, annuli)
CONFIG = {
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
"opacity_function": lambda r: 1.0 / (r + 1.0)**2,
"color": LIGHT_COLOR,
"max_opacity": 1.0,
"num_levels": NUM_LEVELS,
"radius": 5.0
}
def init_points(self):
# in theory, this method is only called once, right?
# so removing submobs shd not be necessary
#
# Note: Usually, yes, it is only called within Mobject.__init__,
# but there is no strong guarantee of that, and you may want certain
# update functions to regenerate points here and there.
for submob in self.submobjects:
self.remove(submob)
self.add(self.source_point)
# create annuli
self.radius = float(self.radius)
dr = self.radius / self.num_levels
for r in np.arange(0, self.radius, dr):
alpha = self.max_opacity * self.opacity_function(r)
annulus = Annulus(
inner_radius=r,
outer_radius=r + dr,
color=self.color,
fill_opacity=alpha
)
annulus.move_to(self.get_source_point())
self.add(annulus)
def move_source_to(self, point):
# old_source_point = self.get_source_point()
# self.shift(point - old_source_point)
self.move_to(point)
return self
def get_source_point(self):
return self.source_point.get_location()
def dimming(self, new_alpha):
old_alpha = self.max_opacity
self.max_opacity = new_alpha
for submob in self.submobjects:
old_submob_alpha = submob.fill_opacity
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
submob.set_fill(opacity=new_submob_alpha)
class Spotlight(VMobject):
CONFIG = {
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
"opacity_function": lambda r: 1.0 / (r / 2 + 1.0)**2,
"color": GREEN, # LIGHT_COLOR,
"max_opacity": 1.0,
"num_levels": 10,
"radius": 10.0,
"screen": None,
"camera_mob": None
}
def projection_direction(self):
# Note: This seems reasonable, though for it to work you'd
# need to be sure that any 3d scene including a spotlight
# somewhere assigns that spotlights "camera" attribute
# to be the camera associated with that scene.
if self.camera_mob is None:
return OUT
else:
[phi, theta, r] = self.camera_mob.get_center()
v = np.array([np.sin(phi) * np.cos(theta),
np.sin(phi) * np.sin(theta), np.cos(phi)])
return v # /get_norm(v)
def project(self, point):
v = self.projection_direction()
w = project_along_vector(point, v)
return w
def get_source_point(self):
return self.source_point.get_location()
def init_points(self):
self.set_submobjects([])
self.add(self.source_point)
if self.screen is not None:
# look for the screen and create annular sectors
lower_angle, upper_angle = self.viewing_angles(self.screen)
self.radius = float(self.radius)
dr = self.radius / self.num_levels
lower_ray, upper_ray = self.viewing_rays(self.screen)
for r in np.arange(0, self.radius, dr):
new_sector = self.new_sector(r, dr, lower_angle, upper_angle)
self.add(new_sector)
def new_sector(self, r, dr, lower_angle, upper_angle):
alpha = self.max_opacity * self.opacity_function(r)
annular_sector = AnnularSector(
inner_radius=r,
outer_radius=r + dr,
color=self.color,
fill_opacity=alpha,
start_angle=lower_angle,
angle=upper_angle - lower_angle
)
# rotate (not project) it into the viewing plane
rotation_matrix = z_to_vector(self.projection_direction())
annular_sector.apply_matrix(rotation_matrix)
# now rotate it inside that plane
rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T)
projected_RIGHT = self.project(RIGHT)
omega = angle_between_vectors(rotated_RIGHT, projected_RIGHT)
annular_sector.rotate(omega, axis=self.projection_direction())
annular_sector.move_arc_center_to(self.get_source_point())
return annular_sector
def viewing_angle_of_point(self, point):
# as measured from the positive x-axis
v1 = self.project(RIGHT)
v2 = self.project(np.array(point) - self.get_source_point())
absolute_angle = angle_between_vectors(v1, v2)
# determine the angle's sign depending on their plane's
# choice of orientation. That choice is set by the camera
# position, i. e. projection direction
if np.dot(self.projection_direction(), np.cross(v1, v2)) > 0:
return absolute_angle
else:
return -absolute_angle
def viewing_angles(self, screen):
screen_points = screen.get_anchors()
projected_screen_points = list(map(self.project, screen_points))
viewing_angles = np.array(list(map(self.viewing_angle_of_point,
projected_screen_points)))
lower_angle = upper_angle = 0
if len(viewing_angles) != 0:
lower_angle = np.min(viewing_angles)
upper_angle = np.max(viewing_angles)
if upper_angle - lower_angle > TAU / 2:
lower_angle, upper_angle = upper_angle, lower_angle + TAU
return lower_angle, upper_angle
def viewing_rays(self, screen):
lower_angle, upper_angle = self.viewing_angles(screen)
projected_RIGHT = self.project(
RIGHT) / get_norm(self.project(RIGHT))
lower_ray = rotate_vector(
projected_RIGHT, lower_angle, axis=self.projection_direction())
upper_ray = rotate_vector(
projected_RIGHT, upper_angle, axis=self.projection_direction())
return lower_ray, upper_ray
def opening_angle(self):
l, u = self.viewing_angles(self.screen)
return u - l
def start_angle(self):
l, u = self.viewing_angles(self.screen)
return l
def stop_angle(self):
l, u = self.viewing_angles(self.screen)
return u
def move_source_to(self, point):
self.source_point.set_location(np.array(point))
# self.source_point.move_to(np.array(point))
# self.move_to(point)
self.update_sectors()
return self
def update_sectors(self):
if self.screen is None:
return
for submob in self.submobjects:
if type(submob) == AnnularSector:
lower_angle, upper_angle = self.viewing_angles(self.screen)
# dr = submob.outer_radius - submob.inner_radius
dr = self.radius / self.num_levels
new_submob = self.new_sector(
submob.inner_radius, dr, lower_angle, upper_angle
)
# submob.points = new_submob.points
# submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
Transform(submob, new_submob).update(1)
def dimming(self, new_alpha):
old_alpha = self.max_opacity
self.max_opacity = new_alpha
for submob in self.submobjects:
# Note: Maybe it'd be best to have a Shadow class so that the
# type can be checked directly?
if type(submob) != AnnularSector:
# it's the shadow, don't dim it
continue
old_submob_alpha = submob.fill_opacity
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
submob.set_fill(opacity=new_submob_alpha)
def change_opacity_function(self, new_f):
self.opacity_function = new_f
dr = self.radius / self.num_levels
sectors = []
for submob in self.submobjects:
if type(submob) == AnnularSector:
sectors.append(submob)
for (r, submob) in zip(np.arange(0, self.radius, dr), sectors):
if type(submob) != AnnularSector:
# it's the shadow, don't dim it
continue
alpha = self.opacity_function(r)
submob.set_fill(opacity=alpha)
# Warning: This class is likely quite buggy.
class LightSource(VMobject):
# combines:
# a lighthouse
# an ambient light
# a spotlight
# and a shadow
CONFIG = {
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
"color": LIGHT_COLOR,
"num_levels": 10,
"radius": 10.0,
"screen": None,
"opacity_function": inverse_quadratic(1, 2, 1),
"max_opacity_ambient": AMBIENT_FULL,
"max_opacity_spotlight": SPOTLIGHT_FULL,
"camera_mob": None
}
def init_points(self):
self.add(self.source_point)
self.lighthouse = Lighthouse()
self.ambient_light = AmbientLight(
source_point=VectorizedPoint(location=self.get_source_point()),
color=self.color,
num_levels=self.num_levels,
radius=self.radius,
opacity_function=self.opacity_function,
max_opacity=self.max_opacity_ambient
)
if self.has_screen():
self.spotlight = Spotlight(
source_point=VectorizedPoint(location=self.get_source_point()),
color=self.color,
num_levels=self.num_levels,
radius=self.radius,
screen=self.screen,
opacity_function=self.opacity_function,
max_opacity=self.max_opacity_spotlight,
camera_mob=self.camera_mob
)
else:
self.spotlight = Spotlight()
self.shadow = VMobject(fill_color=SHADOW_COLOR,
fill_opacity=1.0, stroke_color=BLACK)
self.lighthouse.next_to(self.get_source_point(), DOWN, buff=0)
self.ambient_light.move_source_to(self.get_source_point())
if self.has_screen():
self.spotlight.move_source_to(self.get_source_point())
self.update_shadow()
self.add(self.ambient_light, self.spotlight,
self.lighthouse, self.shadow)
def has_screen(self):
if self.screen is None:
return False
elif self.screen.get_num_points() == 0:
return False
else:
return True
def dim_ambient(self):
self.set_max_opacity_ambient(AMBIENT_DIMMED)
def set_max_opacity_ambient(self, new_opacity):
self.max_opacity_ambient = new_opacity
self.ambient_light.dimming(new_opacity)
def dim_spotlight(self):
self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED)
def set_max_opacity_spotlight(self, new_opacity):
self.max_opacity_spotlight = new_opacity
self.spotlight.dimming(new_opacity)
def set_camera_mob(self, new_cam_mob):
self.camera_mob = new_cam_mob
self.spotlight.camera_mob = new_cam_mob
def set_screen(self, new_screen):
if self.has_screen():
self.spotlight.screen = new_screen
else:
# Note: See below
index = self.submobjects.index(self.spotlight)
# camera_mob = self.spotlight.camera_mob
self.remove(self.spotlight)
self.spotlight = Spotlight(
source_point=VectorizedPoint(location=self.get_source_point()),
color=self.color,
num_levels=self.num_levels,
radius=self.radius,
screen=new_screen,
camera_mob=self.camera_mob,
opacity_function=self.opacity_function,
max_opacity=self.max_opacity_spotlight,
)
self.spotlight.move_source_to(self.get_source_point())
# Note: This line will make spotlight show up at the end
# of the submojects list, which can make it show up on
# top of the shadow. To make it show up in the
# same spot, you could try the following line,
# where "index" is what I defined above:
self.submobjects.insert(index, self.spotlight)
# self.add(self.spotlight)
# in any case
self.screen = new_screen
def move_source_to(self, point):
apoint = np.array(point)
v = apoint - self.get_source_point()
# Note: As discussed, things stand to behave better if source
# point is a submobject, so that it automatically interpolates
# during an animation, and other updates can be defined wrt
# that source point's location
self.source_point.set_location(apoint)
# self.lighthouse.next_to(apoint,DOWN,buff = 0)
# self.ambient_light.move_source_to(apoint)
self.lighthouse.shift(v)
# self.ambient_light.shift(v)
self.ambient_light.move_source_to(apoint)
if self.has_screen():
self.spotlight.move_source_to(apoint)
self.update()
return self
def change_spotlight_opacity_function(self, new_of):
self.spotlight.change_opacity_function(new_of)
def set_radius(self, new_radius):
self.radius = new_radius
self.ambient_light.radius = new_radius
self.spotlight.radius = new_radius
def update(self):
self.update_lighthouse()
self.update_ambient()
self.spotlight.update_sectors()
self.update_shadow()
def update_lighthouse(self):
self.lighthouse.move_to(self.get_source_point())
# new_lh = Lighthouse()
# new_lh.move_to(ORIGIN)
# new_lh.apply_matrix(self.rotation_matrix())
# new_lh.shift(self.get_source_point())
# self.lighthouse.submobjects = new_lh.submobjects
def update_ambient(self):
new_ambient_light = AmbientLight(
source_point=VectorizedPoint(location=ORIGIN),
color=self.color,
num_levels=self.num_levels,
radius=self.radius,
opacity_function=self.opacity_function,
max_opacity=self.max_opacity_ambient
)
new_ambient_light.apply_matrix(self.rotation_matrix())
new_ambient_light.move_source_to(self.get_source_point())
self.ambient_light.set_submobjects(new_ambient_light.submobjects)
def get_source_point(self):
return self.source_point.get_location()
def rotation_matrix(self):
if self.camera_mob is None:
return np.eye(3)
phi = self.camera_mob.get_center()[0]
theta = self.camera_mob.get_center()[1]
R1 = np.array([
[1, 0, 0],
[0, np.cos(phi), -np.sin(phi)],
[0, np.sin(phi), np.cos(phi)]
])
R2 = np.array([
[np.cos(theta + TAU / 4), -np.sin(theta + TAU / 4), 0],
[np.sin(theta + TAU / 4), np.cos(theta + TAU / 4), 0],
[0, 0, 1]
])
R = np.dot(R2, R1)
return R
def update_shadow(self):
point = self.get_source_point()
projected_screen_points = []
if not self.has_screen():
return
for point in self.screen.get_anchors():
projected_screen_points.append(self.spotlight.project(point))
projected_source = project_along_vector(
self.get_source_point(), self.spotlight.projection_direction())
projected_point_cloud_3d = np.append(
projected_screen_points,
np.reshape(projected_source, (1, 3)),
axis=0
)
# z_to_vector(self.spotlight.projection_direction())
rotation_matrix = self.rotation_matrix()
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
rotated_point_cloud_3d = np.dot(
projected_point_cloud_3d, back_rotation_matrix.T)
# these points now should all have z = 0
point_cloud_2d = rotated_point_cloud_3d[:, :2]
# now we can compute the convex hull
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
hull = []
# we also need the projected source point
source_point_2d = np.dot(self.spotlight.project(
self.get_source_point()), back_rotation_matrix.T)[:2]
index = 0
for point in point_cloud_2d[hull_2d.vertices]:
if np.all(np.abs(point - source_point_2d) < 1.0e-6):
source_index = index
index += 1
continue
point_3d = np.array([point[0], point[1], 0])
hull.append(point_3d)
index += 1
hull_mobject = VMobject()
hull_mobject.set_points_as_corners(hull)
hull_mobject.apply_matrix(rotation_matrix)
anchors = hull_mobject.get_anchors()
# add two control points for the outer cone
if np.size(anchors) == 0:
self.shadow.resize_points(0)
return
ray1 = anchors[source_index - 1] - projected_source
ray1 = ray1 / get_norm(ray1) * 100
ray2 = anchors[source_index] - projected_source
ray2 = ray2 / get_norm(ray2) * 100
outpoint1 = anchors[source_index - 1] + ray1
outpoint2 = anchors[source_index] + ray2
new_anchors = anchors[:source_index]
new_anchors = np.append(new_anchors, np.array(
[outpoint1, outpoint2]), axis=0)
new_anchors = np.append(new_anchors, anchors[source_index:], axis=0)
self.shadow.set_points_as_corners(new_anchors)
# shift it closer to the camera so it is in front of the spotlight
self.shadow.mark_paths_closed = True
# Redefining what was once a ContinualAnimation class
# as a function
def ScreenTracker(light_source):
light_source.add_updater(lambda m: m.update())
return light_source

View file

@ -1,66 +0,0 @@
from manimlib.animation.transform import Transform
from manimlib.constants import *
from manimlib.mobject.mobject import Mobject
from manimlib.scene.scene import Scene
class ReconfigurableScene(Scene):
"""
Note, this seems to no longer work as intented.
"""
CONFIG = {
"allow_recursion": True,
}
def setup(self):
self.states = []
self.num_recursions = 0
def transition_to_alt_config(
self,
return_to_original_configuration=True,
transformation_kwargs=None,
**new_config
):
if transformation_kwargs is None:
transformation_kwargs = {}
original_state = self.get_state()
state_copy = original_state.copy()
self.states.append(state_copy)
if not self.allow_recursion:
return
alt_scene = self.__class__(
skip_animations=True,
allow_recursion=False,
**new_config
)
alt_state = alt_scene.states[len(self.states) - 1]
if return_to_original_configuration:
self.clear()
self.transition_between_states(
state_copy, alt_state,
**transformation_kwargs
)
self.transition_between_states(
state_copy, original_state,
**transformation_kwargs
)
self.clear()
self.add(*original_state)
else:
self.transition_between_states(
original_state, alt_state,
**transformation_kwargs
)
self.__dict__.update(new_config)
def get_state(self):
# Want to return a mobject that maintains the most
# structure. The way to do that is to extract only
# those that aren't inside another.
return Mobject(*self.get_top_level_mobjects())
def transition_between_states(self, start_state, target_state, **kwargs):
self.play(Transform(start_state, target_state, **kwargs))
self.wait()

View file

@ -1,107 +0,0 @@
from copy import deepcopy
import itertools as it
from manimlib.constants import *
from manimlib.mobject.mobject import Mobject
from manimlib.utils.iterables import adjacent_pairs
# Warning: This is all now pretty deprecated, and should not be expected to work
class Region(Mobject):
CONFIG = {
"display_mode": "region"
}
def __init__(self, condition=(lambda x, y: True), **kwargs):
"""
Condition must be a function which takes in two real
arrays (representing x and y values of space respectively)
and return a boolean array. This can essentially look like
a function from R^2 to {True, False}, but & and | must be
used in place of "and" and "or"
"""
Mobject.__init__(self, **kwargs)
self.condition = condition
def _combine(self, region, op):
self.condition = lambda x, y: op(
self.condition(x, y),
region.condition(x, y)
)
def union(self, region):
self._combine(region, lambda bg1, bg2: bg1 | bg2)
return self
def intersect(self, region):
self._combine(region, lambda bg1, bg2: bg1 & bg2)
return self
def complement(self):
self.bool_grid = ~self.bool_grid
return self
class HalfPlane(Region):
def __init__(self, point_pair, upper_left=True, *args, **kwargs):
"""
point_pair of the form [(x_0, y_0,...), (x_1, y_1,...)]
Pf upper_left is True, the side of the region will be
everything on the upper left side of the line through
the point pair
"""
if not upper_left:
point_pair = list(point_pair)
point_pair.reverse()
(x0, y0), (x1, y1) = point_pair[0][:2], point_pair[1][:2]
def condition(x, y):
return (x1 - x0) * (y - y0) > (y1 - y0) * (x - x0)
Region.__init__(self, condition, *args, **kwargs)
def region_from_line_boundary(*lines, **kwargs):
reg = Region(**kwargs)
for line in lines:
reg.intersect(HalfPlane(line, **kwargs))
return reg
def region_from_polygon_vertices(*vertices, **kwargs):
return region_from_line_boundary(*adjacent_pairs(vertices), **kwargs)
def plane_partition(*lines, **kwargs):
"""
A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)]
Returns the list of regions of the plane cut out by
these lines
"""
result = []
half_planes = [HalfPlane(line, **kwargs) for line in lines]
complements = [deepcopy(hp).complement() for hp in half_planes]
num_lines = len(lines)
for bool_list in it.product(*[[True, False]] * num_lines):
reg = Region(**kwargs)
for i in range(num_lines):
if bool_list[i]:
reg.intersect(half_planes[i])
else:
reg.intersect(complements[i])
if reg.bool_grid.any():
result.append(reg)
return result
def plane_partition_from_points(*points, **kwargs):
"""
Returns list of regions cut out by the complete graph
with points from the argument as vertices.
Each point comes in the form (x, y)
"""
lines = [[p1, p2] for (p1, p2) in it.combinations(points, 2)]
return plane_partition(*lines, **kwargs)