mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
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:
parent
0ad2f18ca6
commit
e4aebaf791
6 changed files with 0 additions and 1607 deletions
|
@ -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
|
||||
)
|
|
@ -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)
|
|
@ -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()
|
||||
)
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
Loading…
Add table
Reference in a new issue