mirror of
https://github.com/3b1b/manim.git
synced 2025-11-15 03:47:47 +00:00
Better space-filling curve infrastructure
This commit is contained in:
parent
591515133b
commit
b481eda8ac
4 changed files with 179 additions and 82 deletions
|
|
@ -55,6 +55,14 @@ def instantiate(obj):
|
||||||
"""
|
"""
|
||||||
return obj() if isinstance(obj, type) else obj
|
return obj() if isinstance(obj, type) else obj
|
||||||
|
|
||||||
|
def get_all_descendent_classes(Class):
|
||||||
|
awaiting_review = [Class]
|
||||||
|
result = []
|
||||||
|
while awaiting_review:
|
||||||
|
Child = awaiting_review.pop()
|
||||||
|
awaiting_review += Child.__subclasses__()
|
||||||
|
result.append(Child)
|
||||||
|
return result
|
||||||
|
|
||||||
def filtered_locals(local_args):
|
def filtered_locals(local_args):
|
||||||
result = local_args.copy()
|
result = local_args.copy()
|
||||||
|
|
|
||||||
230
hilbert.py
230
hilbert.py
|
|
@ -1,3 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
from mobject import Mobject, Mobject1D
|
from mobject import Mobject, Mobject1D
|
||||||
from scene import Scene
|
from scene import Scene
|
||||||
from animation.transform import Transform
|
from animation.transform import Transform
|
||||||
|
|
@ -6,27 +8,17 @@ from topics.geometry import Line, Point
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
def flip_over_slope_1(points):
|
def flip(points, axis, angle = np.pi):
|
||||||
return points[:,[1, 0 , 2]]
|
if axis is None:
|
||||||
|
return points
|
||||||
def flip_over_slope_neg_1(points):
|
if isinstance(axis, tuple):
|
||||||
return -points[:,[1, 0, 2]]
|
axes = axis
|
||||||
|
else:
|
||||||
def hilbertification(points, radius=3):
|
axes = [axis]
|
||||||
transformed_copies = [
|
for ax in axes:
|
||||||
flip(points/2) + offset*radius/2.0
|
matrix = rotation_matrix(angle, ax)
|
||||||
for flip, offset in [
|
points = np.dot(points, np.transpose(matrix))
|
||||||
(flip_over_slope_1, (LEFT+DOWN)),
|
return points
|
||||||
(lambda x : x, (LEFT+UP)),
|
|
||||||
(lambda x : x, (RIGHT+UP)),
|
|
||||||
(flip_over_slope_neg_1, (RIGHT+DOWN)),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
return reduce(
|
|
||||||
lambda a, b : np.append(a, b, axis = 0),
|
|
||||||
transformed_copies
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SpaceFillingCurve(Mobject1D):
|
class SpaceFillingCurve(Mobject1D):
|
||||||
|
|
@ -38,28 +30,138 @@ class SpaceFillingCurve(Mobject1D):
|
||||||
}
|
}
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
points = self.get_anchor_points(self.order)
|
points = self.get_anchor_points()
|
||||||
for pair in zip(points, points[1:]):
|
for pair in zip(points, points[1:]):
|
||||||
self.add_line(*pair, min_density = 0.01)
|
self.add_line(*pair)
|
||||||
self.gradient_highlight(self.start_color, self.end_color)
|
self.gradient_highlight(self.start_color, self.end_color)
|
||||||
|
|
||||||
def get_anchor_points(self, order):
|
def get_anchor_points(self):
|
||||||
"""
|
raise Exception("Not implemented")
|
||||||
To be filled out in subclasses
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
class SelfSimilarSpaceFillingCurve(SpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"axis_offset_pairs" : [],
|
||||||
|
"scale_factor" : 2,
|
||||||
|
"radius_scale_factor" : 0.5,
|
||||||
|
}
|
||||||
|
def refine_into_subparts(self, points):
|
||||||
|
transformed_copies = [
|
||||||
|
flip(points/self.scale_factor, axis) + \
|
||||||
|
offset*self.radius*self.radius_scale_factor
|
||||||
|
for axis, offset in self.axis_offset_pairs
|
||||||
|
]
|
||||||
|
return reduce(
|
||||||
|
lambda a, b : np.append(a, b, axis = 0),
|
||||||
|
transformed_copies
|
||||||
|
)
|
||||||
|
|
||||||
class HilbertCurve(SpaceFillingCurve):
|
def get_anchor_points(self):
|
||||||
def get_anchor_points(self, order):
|
|
||||||
points = np.zeros((1, 3))
|
points = np.zeros((1, 3))
|
||||||
for count in range(order):
|
for count in range(self.order):
|
||||||
points = hilbertification(points)
|
points = self.refine_into_subparts(
|
||||||
|
points
|
||||||
|
)
|
||||||
return points
|
return points
|
||||||
|
|
||||||
class HilbertCurve3D(SpaceFillingCurve):
|
|
||||||
def get_anchor_points(self, order):
|
|
||||||
pass
|
class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"axis_offset_pairs" : [
|
||||||
|
(RIGHT+UP, LEFT+DOWN ),
|
||||||
|
(None, LEFT+UP ),
|
||||||
|
(None, RIGHT+UP ),
|
||||||
|
(RIGHT+DOWN, RIGHT+DOWN),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"axis_offset_pairs" : [ #TODO
|
||||||
|
(None, LEFT+DOWN+OUT),
|
||||||
|
(None, LEFT+UP+OUT),
|
||||||
|
(None, LEFT+UP+IN),
|
||||||
|
(None, LEFT+DOWN+IN),
|
||||||
|
(None, RIGHT+DOWN+IN),
|
||||||
|
(None, RIGHT+UP+IN),
|
||||||
|
(None, RIGHT+UP+OUT),
|
||||||
|
(None, RIGHT+DOWN+OUT),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeanoCurve(SelfSimilarSpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"start_color" : PURPLE,
|
||||||
|
"end_color" : TEAL,
|
||||||
|
"axis_offset_pairs" : [
|
||||||
|
(None, LEFT+DOWN ),
|
||||||
|
(UP, LEFT ),
|
||||||
|
(None, LEFT+UP ),
|
||||||
|
(RIGHT, UP ),
|
||||||
|
(LEFT+UP, ORIGIN ),
|
||||||
|
(RIGHT, DOWN ),
|
||||||
|
(None, RIGHT+DOWN),
|
||||||
|
(UP, RIGHT ),
|
||||||
|
(None, RIGHT+UP ),
|
||||||
|
],
|
||||||
|
"scale_factor" : 3,
|
||||||
|
"radius_scale_factor" : 2.0/3,
|
||||||
|
}
|
||||||
|
|
||||||
|
class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"start_color" : MAROON,
|
||||||
|
"end_color" : YELLOW,
|
||||||
|
"axis_offset_pairs" : [
|
||||||
|
(None, LEFT/4.+DOWN/6.),
|
||||||
|
(RIGHT, ORIGIN),
|
||||||
|
(None, RIGHT/4.+DOWN/6.),
|
||||||
|
(UP, UP/3.),
|
||||||
|
],
|
||||||
|
"scale_factor" : 2,
|
||||||
|
"radius_scale_factor" : 1.5,
|
||||||
|
}
|
||||||
|
|
||||||
|
class HexagonFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"start_color" : WHITE,
|
||||||
|
"end_color" : BLUE_D,
|
||||||
|
"axis_offset_pairs" : [
|
||||||
|
(None, 1.5*DOWN + 0.5*np.sqrt(3)*LEFT),
|
||||||
|
(UP+np.sqrt(3)*RIGHT, 1.5*DOWN + 0.5*np.sqrt(3)*RIGHT),
|
||||||
|
(np.sqrt(3)*UP+RIGHT, ORIGIN),
|
||||||
|
((UP, RIGHT), np.sqrt(3)*LEFT),
|
||||||
|
(None, 1.5*UP + 0.5*np.sqrt(3)*LEFT),
|
||||||
|
(None, 1.5*UP + 0.5*np.sqrt(3)*RIGHT),
|
||||||
|
(RIGHT, np.sqrt(3)*RIGHT),
|
||||||
|
],
|
||||||
|
"scale_factor" : 3,
|
||||||
|
"radius_scale_factor" : 2/(3*np.sqrt(3)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def refine_into_subparts(self, points):
|
||||||
|
return SelfSimilarSpaceFillingCurve.refine_into_subparts(
|
||||||
|
self,
|
||||||
|
flip(points, IN, np.pi/6)
|
||||||
|
)
|
||||||
|
|
||||||
|
class UtahFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"start_color" : WHITE,
|
||||||
|
"end_color" : BLUE_D,
|
||||||
|
"axis_offset_pairs" : [
|
||||||
|
(None, 1.5*DOWN + 0.5*np.sqrt(3)*LEFT),
|
||||||
|
(UP+np.sqrt(3)*RIGHT, 1.5*DOWN + 0.5*np.sqrt(3)*RIGHT),
|
||||||
|
(np.sqrt(3)*UP+RIGHT, ORIGIN),
|
||||||
|
((UP, RIGHT), np.sqrt(3)*LEFT),
|
||||||
|
(None, 1.5*UP + 0.5*np.sqrt(3)*LEFT),
|
||||||
|
(None, 1.5*UP + 0.5*np.sqrt(3)*RIGHT),
|
||||||
|
(RIGHT, np.sqrt(3)*RIGHT),
|
||||||
|
],
|
||||||
|
"scale_factor" : 3,
|
||||||
|
"radius_scale_factor" : 2/(3*np.sqrt(3)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SnakeCurve(SpaceFillingCurve):
|
class SnakeCurve(SpaceFillingCurve):
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
|
|
@ -83,28 +185,36 @@ class SnakeCurve(SpaceFillingCurve):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class SpaceFillingCurveScene(Scene):
|
|
||||||
DEFAULT_CONFIG = {
|
class SpaceFillingCurveScene(Scene):
|
||||||
"curve_class" : None #Must be filled in in subclasses
|
|
||||||
}
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def args_to_string(max_order):
|
def args_to_string(CurveClass, order):
|
||||||
return str(max_order)
|
return CurveClass.__name__ + "Order" + str(order)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def string_to_args(num_str):
|
def string_to_args(arg_str):
|
||||||
return int(num_str)
|
curve_class_name, order_str = arg_str.split()
|
||||||
|
space_filling_curves = dict([
|
||||||
|
(Class.__name__, Class)
|
||||||
|
for Class in get_all_descendent_classes(SpaceFillingCurve)
|
||||||
|
])
|
||||||
|
if curve_class_name not in space_filling_curves:
|
||||||
|
raise Exception(
|
||||||
|
"%s is not a space filling curve"%curve_class_name
|
||||||
|
)
|
||||||
|
CurveClass = space_filling_curves[curve_class_name]
|
||||||
|
return CurveClass, int(order_str)
|
||||||
|
|
||||||
class SpaceFillingCurveGrowingOrder(SpaceFillingCurveScene):
|
class TransformOverIncreasingOrders(SpaceFillingCurveScene):
|
||||||
def construct(self, max_order):
|
def construct(self, CurveClass, max_order):
|
||||||
sample = self.curve_class(order = 1)
|
sample = CurveClass(order = 1)
|
||||||
curve = Line(sample.radius*LEFT, sample.radius*RIGHT)
|
curve = Line(sample.radius*LEFT, sample.radius*RIGHT)
|
||||||
curve.gradient_highlight(
|
curve.gradient_highlight(
|
||||||
sample.start_color,
|
sample.start_color,
|
||||||
sample.end_color
|
sample.end_color
|
||||||
)
|
)
|
||||||
for order in range(1, max_order):
|
for order in range(1, max_order):
|
||||||
new_curve = self.curve_class(order = order)
|
new_curve = CurveClass(order = order)
|
||||||
self.play(
|
self.play(
|
||||||
Transform(curve, new_curve),
|
Transform(curve, new_curve),
|
||||||
run_time = 3/np.sqrt(order),
|
run_time = 3/np.sqrt(order),
|
||||||
|
|
@ -112,34 +222,12 @@ class SpaceFillingCurveGrowingOrder(SpaceFillingCurveScene):
|
||||||
self.dither()
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
class HilbertCurveGrowingOrder(SpaceFillingCurveGrowingOrder):
|
|
||||||
DEFAULT_CONFIG = {
|
|
||||||
"curve_class" : HilbertCurve,
|
|
||||||
}
|
|
||||||
|
|
||||||
class SnakeCurveGrowingOrder(SpaceFillingCurveGrowingOrder):
|
|
||||||
DEFAULT_CONFIG = {
|
|
||||||
"curve_class" : SnakeCurve,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DrawSpaceFillingCurve(SpaceFillingCurveScene):
|
class DrawSpaceFillingCurve(SpaceFillingCurveScene):
|
||||||
def construct(self, order):
|
def construct(self, CurveClass, order):
|
||||||
curve = self.curve_class(order = order)
|
curve = CurveClass(order = order)
|
||||||
self.play(ShowCreation(curve), run_time = 10)
|
self.play(ShowCreation(curve), run_time = 10)
|
||||||
self.dither()
|
self.dither()
|
||||||
|
|
||||||
class DrawHilbertCurve(DrawSpaceFillingCurve):
|
|
||||||
DEFAULT_CONFIG = {
|
|
||||||
"curve_class" : HilbertCurve,
|
|
||||||
}
|
|
||||||
|
|
||||||
class DrawSnakeCurve(DrawSpaceFillingCurve):
|
|
||||||
DEFAULT_CONFIG = {
|
|
||||||
"curve_class" : SnakeCurve,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,7 @@ class Mobject(object):
|
||||||
#TODO, Make the two implementations bellow non-redundant
|
#TODO, Make the two implementations bellow non-redundant
|
||||||
class Mobject1D(Mobject):
|
class Mobject1D(Mobject):
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||||
}
|
}
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
|
|
@ -488,13 +488,17 @@ class Mobject1D(Mobject):
|
||||||
Mobject.__init__(self, **kwargs)
|
Mobject.__init__(self, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def add_line(self, start, end, min_density = 0.1, color = None):
|
def add_line(self, start, end, color = None):
|
||||||
length = np.linalg.norm(end - start)
|
length = np.linalg.norm(end - start)
|
||||||
epsilon = self.epsilon / max(length, min_density)
|
if length == 0:
|
||||||
self.add_points([
|
points = [start]
|
||||||
interpolate(start, end, t)
|
else:
|
||||||
for t in np.arange(0, 1, epsilon)
|
epsilon = self.epsilon/length
|
||||||
], color = color)
|
points = [
|
||||||
|
interpolate(start, end, t)
|
||||||
|
for t in np.arange(0, 1, epsilon)
|
||||||
|
]
|
||||||
|
self.add_points(points, color = color)
|
||||||
|
|
||||||
class Mobject2D(Mobject):
|
class Mobject2D(Mobject):
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,6 @@ class Cross(Mobject1D):
|
||||||
|
|
||||||
|
|
||||||
class Line(Mobject1D):
|
class Line(Mobject1D):
|
||||||
DEFAULT_CONFIG = {
|
|
||||||
"min_density" : 0.1
|
|
||||||
}
|
|
||||||
def __init__(self, start, end, **kwargs):
|
def __init__(self, start, end, **kwargs):
|
||||||
self.set_start_and_end(start, end)
|
self.set_start_and_end(start, end)
|
||||||
Mobject1D.__init__(self, **kwargs)
|
Mobject1D.__init__(self, **kwargs)
|
||||||
|
|
@ -75,7 +72,7 @@ class Line(Mobject1D):
|
||||||
]
|
]
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
self.add_line(self.start, self.end, self.min_density)
|
self.add_line(self.start, self.end)
|
||||||
|
|
||||||
def get_length(self):
|
def get_length(self):
|
||||||
return np.linalg.norm(self.start - self.end)
|
return np.linalg.norm(self.start - self.end)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue