More flexible space filling curve class

This commit is contained in:
Grant Sanderson 2015-11-30 10:03:10 -08:00
parent b481eda8ac
commit b2c5f71155

View file

@ -8,16 +8,11 @@ from topics.geometry import Line, Point
from helpers import *
def flip(points, axis, angle = np.pi):
def rotate(points, angle = np.pi, axis = OUT):
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))
matrix = rotation_matrix(angle, axis)
points = np.dot(points, np.transpose(matrix))
return points
@ -38,72 +33,139 @@ class SpaceFillingCurve(Mobject1D):
def get_anchor_points(self):
raise Exception("Not implemented")
class LindenmayerCurve(SpaceFillingCurve):
DEFAULT_CONFIG = {
"axiom" : "A",
"rule" : {},
"scale_factor" : 2,
"radius" : 3,
"start_step" : RIGHT,
"angle" : np.pi/2,
}
def expand_command_string(self, command):
result = ""
for letter in command:
if letter in self.rule:
result += self.rule[letter]
else:
result += letter
return result
def get_command_string(self):
result = self.axiom
for x in range(self.order):
result = self.expand_command_string(result)
return result
def get_anchor_points(self):
step = float(self.radius) * self.start_step
step /= (self.scale_factor**self.order)
curr = np.zeros(3)
result = [curr]
for letter in self.get_command_string():
if letter is "+":
step = rotate(step, self.angle)
elif letter is "-":
step = rotate(step, -self.angle)
else:
curr = curr + step
result.append(curr)
return np.array(result) - center_of_mass(result)
class SelfSimilarSpaceFillingCurve(SpaceFillingCurve):
DEFAULT_CONFIG = {
"axis_offset_pairs" : [],
"offsets" : [],
"offset_index_to_rotation_axis" : {},
"scale_factor" : 2,
"radius_scale_factor" : 0.5,
}
def transform(self, points, offset):
"""
How to transform the copy of points shifted by
offset. Generally meant to be extended in subclasses
"""
if offset in self.offset_index_to_rotation_axis:
return rotate(
points,
axis = self.offset_index_to_rotation_axis[offset]
)
points /= self.scale_factor,
points += offset*self.radius*self.radius_scale_factor
return points
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
self.transform(points, offset)
for offset in self.offsets
]
return reduce(
lambda a, b : np.append(a, b, axis = 0),
lambda a, b : np.append(a, b, axis = 0),
transformed_copies
)
def get_anchor_points(self):
points = np.zeros((1, 3))
for count in range(self.order):
points = self.refine_into_subparts(
points
)
points = self.refine_into_subparts(points)
return points
class HilbertCurve(SelfSimilarSpaceFillingCurve):
DEFAULT_CONFIG = {
"axis_offset_pairs" : [
(RIGHT+UP, LEFT+DOWN ),
(None, LEFT+UP ),
(None, RIGHT+UP ),
(RIGHT+DOWN, RIGHT+DOWN),
"offsets" : [
LEFT+DOWN,
LEFT+UP,
RIGHT+UP,
RIGHT+DOWN,
],
}
"offset_index_to_rotation_axis" : {
0 : RIGHT+UP,
3 : 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),
"offsets" : [
LEFT+DOWN+OUT,
LEFT+UP+OUT,
LEFT+UP+IN,
LEFT+DOWN+IN,
RIGHT+DOWN+IN,
RIGHT+UP+IN,
RIGHT+UP+OUT,
RIGHT+DOWN+OUT,
],
"offset_index_to_rotation_axis" : {}#TODO
}
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 ),
"offsets" : [
LEFT+DOWN,
LEFT,
LEFT+UP,
UP,
ORIGIN,
DOWN,
RIGHT+DOWN,
RIGHT,
RIGHT+UP,
],
"offset_index_to_rotation_axis" : {
1 : UP,
3 : RIGHT,
4 : LEFT+UP,
5 : RIGHT,
6 : UP,
},
"scale_factor" : 3,
"radius_scale_factor" : 2.0/3,
}
@ -112,57 +174,90 @@ 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.),
"offsets" : [
LEFT/4.+DOWN/6.,
ORIGIN,
RIGHT/4.+DOWN/6.,
UP/3.,
],
"offset_index_to_rotation_axis" : {
1 : RIGHT,
3 : UP,
},
"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)),
}
# 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,
# rotate(points, np.pi/6, IN)
# )
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 FlowSnake(LindenmayerCurve):
DEFAULT_CONFIG = {
"start_color" : YELLOW,
"end_color" : GREEN,
"axiom" : "A",
"rule" : {
"A" : "A-B--B+A++AA+B-",
"B" : "+A-BB--B-A++A+B",
},
"radius" : 6, #TODO, this is innaccurate
"scale_factor" : np.sqrt(7),
"start_step" : RIGHT,
"angle" : -np.pi/3,
}
def __init__(self, **kwargs):
LindenmayerCurve.__init__(self, **kwargs)
self.rotate(-self.order*np.pi/9)
class Sierpinski(LindenmayerCurve):
DEFAULT_CONFIG = {
"start_color" : RED,
"end_color" : WHITE,
"axiom" : "A",
"rule" : {
"A" : "+B-A-B+",
"B" : "-A+B+A-",
},
"radius" : 6, #TODO, this is innaccurate
"scale_factor" : 2,
"start_step" : RIGHT,
"angle" : -np.pi/3,
}
class SnakeCurve(SpaceFillingCurve):
DEFAULT_CONFIG = {
"start_color" : BLUE,