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