Better space-filling curve infrastructure

This commit is contained in:
Grant Sanderson
2015-11-25 15:28:07 -08:00
parent 591515133b
commit b481eda8ac
4 changed files with 179 additions and 82 deletions

View File

@ -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()

View File

@ -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,
}

View File

@ -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 = {

View File

@ -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)