mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 22:13:30 +08:00
Better space-filling curve infrastructure
This commit is contained in:
@ -55,6 +55,14 @@ def instantiate(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):
|
||||
result = local_args.copy()
|
||||
|
230
hilbert.py
230
hilbert.py
@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
from mobject import Mobject, Mobject1D
|
||||
from scene import Scene
|
||||
from animation.transform import Transform
|
||||
@ -6,27 +8,17 @@ from topics.geometry import Line, Point
|
||||
|
||||
from helpers import *
|
||||
|
||||
def flip_over_slope_1(points):
|
||||
return points[:,[1, 0 , 2]]
|
||||
|
||||
def flip_over_slope_neg_1(points):
|
||||
return -points[:,[1, 0, 2]]
|
||||
|
||||
def hilbertification(points, radius=3):
|
||||
transformed_copies = [
|
||||
flip(points/2) + offset*radius/2.0
|
||||
for flip, offset in [
|
||||
(flip_over_slope_1, (LEFT+DOWN)),
|
||||
(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
|
||||
)
|
||||
|
||||
def flip(points, axis, angle = np.pi):
|
||||
if axis is None:
|
||||
return points
|
||||
if isinstance(axis, tuple):
|
||||
axes = axis
|
||||
else:
|
||||
axes = [axis]
|
||||
for ax in axes:
|
||||
matrix = rotation_matrix(angle, ax)
|
||||
points = np.dot(points, np.transpose(matrix))
|
||||
return points
|
||||
|
||||
|
||||
class SpaceFillingCurve(Mobject1D):
|
||||
@ -38,28 +30,138 @@ class SpaceFillingCurve(Mobject1D):
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
points = self.get_anchor_points(self.order)
|
||||
points = self.get_anchor_points()
|
||||
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)
|
||||
|
||||
def get_anchor_points(self, order):
|
||||
"""
|
||||
To be filled out in subclasses
|
||||
"""
|
||||
return []
|
||||
def get_anchor_points(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
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, order):
|
||||
def get_anchor_points(self):
|
||||
points = np.zeros((1, 3))
|
||||
for count in range(order):
|
||||
points = hilbertification(points)
|
||||
for count in range(self.order):
|
||||
points = self.refine_into_subparts(
|
||||
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):
|
||||
DEFAULT_CONFIG = {
|
||||
@ -83,28 +185,36 @@ class SnakeCurve(SpaceFillingCurve):
|
||||
return result
|
||||
|
||||
|
||||
class SpaceFillingCurveScene(Scene):
|
||||
DEFAULT_CONFIG = {
|
||||
"curve_class" : None #Must be filled in in subclasses
|
||||
}
|
||||
|
||||
class SpaceFillingCurveScene(Scene):
|
||||
@staticmethod
|
||||
def args_to_string(max_order):
|
||||
return str(max_order)
|
||||
def args_to_string(CurveClass, order):
|
||||
return CurveClass.__name__ + "Order" + str(order)
|
||||
|
||||
@staticmethod
|
||||
def string_to_args(num_str):
|
||||
return int(num_str)
|
||||
def string_to_args(arg_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):
|
||||
def construct(self, max_order):
|
||||
sample = self.curve_class(order = 1)
|
||||
class TransformOverIncreasingOrders(SpaceFillingCurveScene):
|
||||
def construct(self, CurveClass, max_order):
|
||||
sample = CurveClass(order = 1)
|
||||
curve = Line(sample.radius*LEFT, sample.radius*RIGHT)
|
||||
curve.gradient_highlight(
|
||||
sample.start_color,
|
||||
sample.end_color
|
||||
)
|
||||
for order in range(1, max_order):
|
||||
new_curve = self.curve_class(order = order)
|
||||
new_curve = CurveClass(order = order)
|
||||
self.play(
|
||||
Transform(curve, new_curve),
|
||||
run_time = 3/np.sqrt(order),
|
||||
@ -112,34 +222,12 @@ class SpaceFillingCurveGrowingOrder(SpaceFillingCurveScene):
|
||||
self.dither()
|
||||
|
||||
|
||||
class HilbertCurveGrowingOrder(SpaceFillingCurveGrowingOrder):
|
||||
DEFAULT_CONFIG = {
|
||||
"curve_class" : HilbertCurve,
|
||||
}
|
||||
|
||||
class SnakeCurveGrowingOrder(SpaceFillingCurveGrowingOrder):
|
||||
DEFAULT_CONFIG = {
|
||||
"curve_class" : SnakeCurve,
|
||||
}
|
||||
|
||||
|
||||
class DrawSpaceFillingCurve(SpaceFillingCurveScene):
|
||||
def construct(self, order):
|
||||
curve = self.curve_class(order = order)
|
||||
def construct(self, CurveClass, order):
|
||||
curve = CurveClass(order = order)
|
||||
self.play(ShowCreation(curve), run_time = 10)
|
||||
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
|
||||
class Mobject1D(Mobject):
|
||||
DEFAULT_CONFIG = {
|
||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
@ -488,13 +488,17 @@ class Mobject1D(Mobject):
|
||||
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)
|
||||
epsilon = self.epsilon / max(length, min_density)
|
||||
self.add_points([
|
||||
interpolate(start, end, t)
|
||||
for t in np.arange(0, 1, epsilon)
|
||||
], color = color)
|
||||
if length == 0:
|
||||
points = [start]
|
||||
else:
|
||||
epsilon = self.epsilon/length
|
||||
points = [
|
||||
interpolate(start, end, t)
|
||||
for t in np.arange(0, 1, epsilon)
|
||||
]
|
||||
self.add_points(points, color = color)
|
||||
|
||||
class Mobject2D(Mobject):
|
||||
DEFAULT_CONFIG = {
|
||||
|
@ -49,9 +49,6 @@ class Cross(Mobject1D):
|
||||
|
||||
|
||||
class Line(Mobject1D):
|
||||
DEFAULT_CONFIG = {
|
||||
"min_density" : 0.1
|
||||
}
|
||||
def __init__(self, start, end, **kwargs):
|
||||
self.set_start_and_end(start, end)
|
||||
Mobject1D.__init__(self, **kwargs)
|
||||
@ -75,7 +72,7 @@ class Line(Mobject1D):
|
||||
]
|
||||
|
||||
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):
|
||||
return np.linalg.norm(self.start - self.end)
|
||||
|
Reference in New Issue
Block a user