2018-06-02 08:59:26 -04:00
|
|
|
from functools import reduce
|
2015-12-13 15:42:20 -08:00
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.constants import *
|
2020-12-31 13:04:13 -08:00
|
|
|
# from manimlib.for_3b1b_videos.pi_creature import PiCreature
|
|
|
|
# from manimlib.for_3b1b_videos.pi_creature import Randolph
|
|
|
|
# from manimlib.for_3b1b_videos.pi_creature import get_all_pi_creature_modes
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.mobject.geometry import Circle
|
|
|
|
from manimlib.mobject.geometry import Polygon
|
|
|
|
from manimlib.mobject.geometry import RegularPolygon
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
|
|
|
from manimlib.utils.bezier import interpolate
|
|
|
|
from manimlib.utils.color import color_gradient
|
|
|
|
from manimlib.utils.config_ops import digest_config
|
|
|
|
from manimlib.utils.space_ops import center_of_mass
|
|
|
|
from manimlib.utils.space_ops import compass_directions
|
|
|
|
from manimlib.utils.space_ops import rotate_vector
|
|
|
|
from manimlib.utils.space_ops import rotation_matrix
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def rotate(points, angle=np.pi, axis=OUT):
|
2015-12-13 15:42:20 -08:00
|
|
|
if axis is None:
|
|
|
|
return points
|
|
|
|
matrix = rotation_matrix(angle, axis)
|
|
|
|
points = np.dot(points, np.transpose(matrix))
|
|
|
|
return points
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def fractalify(vmobject, order=3, *args, **kwargs):
|
2017-01-17 17:14:32 -08:00
|
|
|
for x in range(order):
|
|
|
|
fractalification_iteration(vmobject)
|
|
|
|
return vmobject
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-08-09 17:56:05 -07:00
|
|
|
def fractalification_iteration(vmobject, dimension=1.05, num_inserted_anchors_range=list(range(1, 4))):
|
2017-01-17 17:14:32 -08:00
|
|
|
num_points = vmobject.get_num_points()
|
|
|
|
if num_points > 0:
|
|
|
|
# original_anchors = vmobject.get_anchors()
|
|
|
|
original_anchors = [
|
|
|
|
vmobject.point_from_proportion(x)
|
2018-04-06 13:58:59 -07:00
|
|
|
for x in np.linspace(0, 1 - 1. / num_points, num_points)
|
2017-01-17 17:14:32 -08:00
|
|
|
]
|
|
|
|
new_anchors = []
|
|
|
|
for p1, p2, in zip(original_anchors, original_anchors[1:]):
|
|
|
|
num_inserts = random.choice(num_inserted_anchors_range)
|
|
|
|
inserted_points = [
|
|
|
|
interpolate(p1, p2, alpha)
|
2018-04-06 13:58:59 -07:00
|
|
|
for alpha in np.linspace(0, 1, num_inserts + 2)[1:-1]
|
2017-01-17 17:14:32 -08:00
|
|
|
]
|
2018-04-06 13:58:59 -07:00
|
|
|
mass_scaling_factor = 1. / (num_inserts + 1)
|
|
|
|
length_scaling_factor = mass_scaling_factor**(1. / dimension)
|
2018-08-15 17:30:24 -07:00
|
|
|
target_length = get_norm(p1 - p2) * length_scaling_factor
|
|
|
|
curr_length = get_norm(p1 - p2) * mass_scaling_factor
|
2018-04-06 13:58:59 -07:00
|
|
|
# offset^2 + curr_length^2 = target_length^2
|
2017-01-17 17:14:32 -08:00
|
|
|
offset_len = np.sqrt(target_length**2 - curr_length**2)
|
2018-08-15 17:30:24 -07:00
|
|
|
unit_vect = (p1 - p2) / get_norm(p1 - p2)
|
2018-04-06 13:58:59 -07:00
|
|
|
offset_unit_vect = rotate_vector(unit_vect, np.pi / 2)
|
2017-01-17 17:14:32 -08:00
|
|
|
inserted_points = [
|
2018-04-06 13:58:59 -07:00
|
|
|
point + u * offset_len * offset_unit_vect
|
2017-01-17 17:14:32 -08:00
|
|
|
for u, point in zip(it.cycle([-1, 1]), inserted_points)
|
|
|
|
]
|
|
|
|
new_anchors += [p1] + inserted_points
|
|
|
|
new_anchors.append(original_anchors[-1])
|
|
|
|
vmobject.set_points_as_corners(new_anchors)
|
2020-02-21 10:56:40 -08:00
|
|
|
vmobject.set_submobjects([
|
2018-04-06 13:58:59 -07:00
|
|
|
fractalification_iteration(
|
|
|
|
submob, dimension, num_inserted_anchors_range)
|
2017-01-17 17:14:32 -08:00
|
|
|
for submob in vmobject.submobjects
|
2020-02-21 10:56:40 -08:00
|
|
|
])
|
2017-01-17 17:14:32 -08:00
|
|
|
return vmobject
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class SelfSimilarFractal(VMobject):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"order": 5,
|
|
|
|
"num_subparts": 3,
|
|
|
|
"height": 4,
|
|
|
|
"colors": [RED, WHITE],
|
|
|
|
"stroke_width": 1,
|
|
|
|
"fill_opacity": 1,
|
2017-01-16 11:43:59 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
def init_colors(self):
|
|
|
|
VMobject.init_colors(self)
|
2018-03-30 11:59:39 -07:00
|
|
|
self.set_color_by_gradient(*self.colors)
|
2017-01-16 11:43:59 -08:00
|
|
|
|
2020-02-11 19:55:00 -08:00
|
|
|
def init_points(self):
|
2017-01-16 13:26:46 -08:00
|
|
|
order_n_self = self.get_order_n_self(self.order)
|
|
|
|
if self.order == 0:
|
2020-02-21 10:56:40 -08:00
|
|
|
self.set_submobjects([order_n_self])
|
2017-01-16 13:26:46 -08:00
|
|
|
else:
|
2020-02-21 10:56:40 -08:00
|
|
|
self.set_submobjects(order_n_self.submobjects)
|
2017-01-16 13:26:46 -08:00
|
|
|
return self
|
2017-01-16 11:43:59 -08:00
|
|
|
|
|
|
|
def get_order_n_self(self, order):
|
|
|
|
if order == 0:
|
|
|
|
result = self.get_seed_shape()
|
|
|
|
else:
|
2017-01-20 12:02:22 -08:00
|
|
|
lower_order = self.get_order_n_self(order - 1)
|
2017-01-16 11:43:59 -08:00
|
|
|
subparts = [
|
2017-01-20 12:02:22 -08:00
|
|
|
lower_order.copy()
|
2017-01-16 11:43:59 -08:00
|
|
|
for x in range(self.num_subparts)
|
|
|
|
]
|
|
|
|
self.arrange_subparts(*subparts)
|
|
|
|
result = VGroup(*subparts)
|
|
|
|
|
2018-08-08 10:30:52 -07:00
|
|
|
result.set_height(self.height)
|
2017-01-16 11:43:59 -08:00
|
|
|
result.center()
|
|
|
|
return result
|
|
|
|
|
|
|
|
def get_seed_shape(self):
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class Sierpinski(SelfSimilarFractal):
|
|
|
|
def get_seed_shape(self):
|
|
|
|
return Polygon(
|
2018-04-06 13:58:59 -07:00
|
|
|
RIGHT, np.sqrt(3) * UP, LEFT,
|
2017-01-16 11:43:59 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
tri1, tri2, tri3 = subparts
|
2018-04-06 13:58:59 -07:00
|
|
|
tri1.move_to(tri2.get_corner(DOWN + LEFT), UP)
|
|
|
|
tri3.move_to(tri2.get_corner(DOWN + RIGHT), UP)
|
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
|
|
|
|
class DiamondFractal(SelfSimilarFractal):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"num_subparts": 4,
|
|
|
|
"height": 4,
|
|
|
|
"colors": [GREEN_E, YELLOW],
|
2017-01-16 11:43:59 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
def get_seed_shape(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
return RegularPolygon(n=4)
|
2017-01-16 11:43:59 -08:00
|
|
|
|
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
# VGroup(*subparts).rotate(np.pi/4)
|
2018-04-06 13:58:59 -07:00
|
|
|
for part, vect in zip(subparts, compass_directions(start_vect=UP + RIGHT)):
|
|
|
|
part.next_to(ORIGIN, vect, buff=0)
|
|
|
|
VGroup(*subparts).rotate(np.pi / 4, about_point=ORIGIN)
|
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
class PentagonalFractal(SelfSimilarFractal):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"num_subparts": 5,
|
|
|
|
"colors": [MAROON_B, YELLOW, RED],
|
|
|
|
"height": 6,
|
2017-01-17 17:14:32 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
def get_seed_shape(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
return RegularPolygon(n=5, start_angle=np.pi / 2)
|
2017-01-17 17:14:32 -08:00
|
|
|
|
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
for x, part in enumerate(subparts):
|
2018-04-06 13:58:59 -07:00
|
|
|
part.shift(0.95 * part.get_height() * UP)
|
|
|
|
part.rotate(2 * np.pi * x / 5, about_point=ORIGIN)
|
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
|
2017-01-24 14:55:05 -08:00
|
|
|
class PentagonalPiCreatureFractal(PentagonalFractal):
|
2017-01-20 12:02:22 -08:00
|
|
|
def init_colors(self):
|
|
|
|
SelfSimilarFractal.init_colors(self)
|
|
|
|
internal_pis = [
|
|
|
|
pi
|
2018-08-21 19:15:16 -07:00
|
|
|
for pi in self.get_family()
|
2017-01-20 12:02:22 -08:00
|
|
|
if isinstance(pi, PiCreature)
|
|
|
|
]
|
|
|
|
colors = color_gradient(self.colors, len(internal_pis))
|
|
|
|
for pi, color in zip(internal_pis, colors):
|
|
|
|
pi.init_colors()
|
2018-04-06 13:58:59 -07:00
|
|
|
pi.body.set_stroke(color, width=0.5)
|
2018-03-30 11:51:31 -07:00
|
|
|
pi.set_color(color)
|
2017-01-20 12:02:22 -08:00
|
|
|
|
|
|
|
def get_seed_shape(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
return Randolph(mode="shruggie")
|
2017-01-20 12:02:22 -08:00
|
|
|
|
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
for part in subparts:
|
2018-04-06 13:58:59 -07:00
|
|
|
part.rotate(2 * np.pi / 5, about_point=ORIGIN)
|
2017-01-20 12:02:22 -08:00
|
|
|
PentagonalFractal.arrange_subparts(self, *subparts)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-24 14:55:05 -08:00
|
|
|
class PiCreatureFractal(VMobject):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"order": 7,
|
|
|
|
"scale_val": 2.5,
|
|
|
|
"start_mode": "hooray",
|
|
|
|
"height": 6,
|
|
|
|
"colors": [
|
2017-01-24 14:55:05 -08:00
|
|
|
BLUE_D, BLUE_B, MAROON_B, MAROON_D, GREY,
|
|
|
|
YELLOW, RED, GREY_BROWN, RED, RED_E,
|
|
|
|
],
|
2018-04-06 13:58:59 -07:00
|
|
|
"random_seed": 0,
|
|
|
|
"stroke_width": 0,
|
2017-01-24 14:55:05 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-24 14:55:05 -08:00
|
|
|
def init_colors(self):
|
|
|
|
VMobject.init_colors(self)
|
|
|
|
internal_pis = [
|
|
|
|
pi
|
2018-08-21 19:15:16 -07:00
|
|
|
for pi in self.get_family()
|
2017-01-24 14:55:05 -08:00
|
|
|
if isinstance(pi, PiCreature)
|
|
|
|
]
|
|
|
|
random.seed(self.random_seed)
|
|
|
|
for pi in reversed(internal_pis):
|
|
|
|
color = random.choice(self.colors)
|
2018-03-30 11:51:31 -07:00
|
|
|
pi.set_color(color)
|
2018-04-06 13:58:59 -07:00
|
|
|
pi.set_stroke(color, width=0)
|
2017-01-24 14:55:05 -08:00
|
|
|
|
2020-02-11 19:55:00 -08:00
|
|
|
def init_points(self):
|
2017-01-24 14:55:05 -08:00
|
|
|
random.seed(self.random_seed)
|
|
|
|
modes = get_all_pi_creature_modes()
|
2018-04-06 13:58:59 -07:00
|
|
|
seed = PiCreature(mode=self.start_mode)
|
2018-08-08 10:30:52 -07:00
|
|
|
seed.set_height(self.height)
|
2017-01-24 14:55:05 -08:00
|
|
|
seed.to_edge(DOWN)
|
|
|
|
creatures = [seed]
|
2017-01-25 12:59:46 -08:00
|
|
|
self.add(VGroup(seed))
|
2017-01-24 14:55:05 -08:00
|
|
|
for x in range(self.order):
|
|
|
|
new_creatures = []
|
|
|
|
for creature in creatures:
|
2017-01-25 12:59:46 -08:00
|
|
|
for eye, vect in zip(creature.eyes, [LEFT, RIGHT]):
|
2017-01-24 14:55:05 -08:00
|
|
|
new_creature = PiCreature(
|
2018-04-06 13:58:59 -07:00
|
|
|
mode=random.choice(modes)
|
2017-01-24 14:55:05 -08:00
|
|
|
)
|
2018-08-08 10:30:52 -07:00
|
|
|
new_creature.set_height(
|
2018-04-06 13:58:59 -07:00
|
|
|
self.scale_val * eye.get_height()
|
2017-01-25 12:59:46 -08:00
|
|
|
)
|
|
|
|
new_creature.next_to(
|
2018-04-06 13:58:59 -07:00
|
|
|
eye, vect,
|
|
|
|
buff=0,
|
|
|
|
aligned_edge=DOWN
|
2017-01-24 14:55:05 -08:00
|
|
|
)
|
|
|
|
new_creatures.append(new_creature)
|
2017-01-25 12:59:46 -08:00
|
|
|
creature.look_at(random.choice(new_creatures))
|
2017-01-24 14:55:05 -08:00
|
|
|
self.add_to_back(VGroup(*new_creatures))
|
|
|
|
creatures = new_creatures
|
|
|
|
|
|
|
|
# def init_colors(self):
|
|
|
|
# VMobject.init_colors(self)
|
2018-03-30 11:59:39 -07:00
|
|
|
# self.set_color_by_gradient(*self.colors)
|
2017-01-24 14:55:05 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-24 14:55:05 -08:00
|
|
|
class WonkyHexagonFractal(SelfSimilarFractal):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"num_subparts": 7
|
2017-01-24 14:55:05 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-24 14:55:05 -08:00
|
|
|
def get_seed_shape(self):
|
|
|
|
return RegularPolygon(n=6)
|
|
|
|
|
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
for i, piece in enumerate(subparts):
|
2018-04-06 13:58:59 -07:00
|
|
|
piece.rotate(i * np.pi / 12, about_point=ORIGIN)
|
2017-01-24 14:55:05 -08:00
|
|
|
p1, p2, p3, p4, p5, p6, p7 = subparts
|
|
|
|
center_row = VGroup(p1, p4, p7)
|
2019-02-04 14:54:25 -08:00
|
|
|
center_row.arrange(RIGHT, buff=0)
|
2017-01-24 14:55:05 -08:00
|
|
|
for p in p2, p3, p5, p6:
|
2018-08-08 10:30:52 -07:00
|
|
|
p.set_width(p1.get_width())
|
2018-04-06 13:58:59 -07:00
|
|
|
p2.move_to(p1.get_top(), DOWN + LEFT)
|
|
|
|
p3.move_to(p1.get_bottom(), UP + LEFT)
|
|
|
|
p5.move_to(p4.get_top(), DOWN + LEFT)
|
|
|
|
p6.move_to(p4.get_bottom(), UP + LEFT)
|
|
|
|
|
2017-01-24 14:55:05 -08:00
|
|
|
|
2017-01-25 12:59:46 -08:00
|
|
|
class CircularFractal(SelfSimilarFractal):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"num_subparts": 3,
|
|
|
|
"colors": [GREEN, BLUE, GREY]
|
2017-01-25 12:59:46 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-25 12:59:46 -08:00
|
|
|
def get_seed_shape(self):
|
|
|
|
return Circle()
|
2017-01-24 14:55:05 -08:00
|
|
|
|
2017-01-25 12:59:46 -08:00
|
|
|
def arrange_subparts(self, *subparts):
|
|
|
|
if not hasattr(self, "been_here"):
|
2018-04-06 13:58:59 -07:00
|
|
|
self.num_subparts = 3 + self.order
|
2017-01-25 12:59:46 -08:00
|
|
|
self.been_here = True
|
|
|
|
for i, part in enumerate(subparts):
|
2018-04-06 13:58:59 -07:00
|
|
|
theta = np.pi / self.num_subparts
|
2017-01-25 12:59:46 -08:00
|
|
|
part.next_to(
|
|
|
|
ORIGIN, UP,
|
2018-04-06 13:58:59 -07:00
|
|
|
buff=self.height / (2 * np.tan(theta))
|
2017-01-25 12:59:46 -08:00
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
part.rotate(i * 2 * np.pi / self.num_subparts, about_point=ORIGIN)
|
2017-01-25 12:59:46 -08:00
|
|
|
self.num_subparts -= 1
|
2017-01-24 14:55:05 -08:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
######## Space filling curves ############
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-25 12:59:46 -08:00
|
|
|
class JaggedCurvePiece(VMobject):
|
2019-02-05 11:02:15 -08:00
|
|
|
def insert_n_curves(self, n):
|
|
|
|
if self.get_num_curves() == 0:
|
|
|
|
self.set_points(np.zeros((1, 3)))
|
2017-01-25 12:59:46 -08:00
|
|
|
anchors = self.get_anchors()
|
2019-02-05 11:02:15 -08:00
|
|
|
indices = np.linspace(
|
|
|
|
0, len(anchors) - 1, n + len(anchors)
|
|
|
|
).astype('int')
|
2017-01-25 12:59:46 -08:00
|
|
|
self.set_points_as_corners(anchors[indices])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
class FractalCurve(VMobject):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 3,
|
|
|
|
"order": 5,
|
|
|
|
"colors": [RED, GREEN],
|
|
|
|
"num_submobjects": 20,
|
|
|
|
"monochromatic": False,
|
|
|
|
"order_to_stroke_width_map": {
|
|
|
|
3: 3,
|
|
|
|
4: 2,
|
|
|
|
5: 1,
|
2017-01-25 12:59:46 -08:00
|
|
|
},
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
|
|
|
|
2020-02-11 19:55:00 -08:00
|
|
|
def init_points(self):
|
2015-12-13 15:42:20 -08:00
|
|
|
points = self.get_anchor_points()
|
2017-01-25 12:59:46 -08:00
|
|
|
self.set_points_as_corners(points)
|
|
|
|
if not self.monochromatic:
|
|
|
|
alphas = np.linspace(0, 1, self.num_submobjects)
|
|
|
|
for alpha_pair in zip(alphas, alphas[1:]):
|
|
|
|
submobject = JaggedCurvePiece()
|
|
|
|
submobject.pointwise_become_partial(
|
|
|
|
self, *alpha_pair
|
|
|
|
)
|
|
|
|
self.add(submobject)
|
2021-01-12 07:27:32 -10:00
|
|
|
self.set_points(np.zeros((0, 3)))
|
2017-01-17 17:14:32 -08:00
|
|
|
|
|
|
|
def init_colors(self):
|
2017-01-19 08:58:11 -08:00
|
|
|
VMobject.init_colors(self)
|
2018-03-30 11:59:39 -07:00
|
|
|
self.set_color_by_gradient(*self.colors)
|
2017-01-25 12:59:46 -08:00
|
|
|
for order in sorted(self.order_to_stroke_width_map.keys()):
|
|
|
|
if self.order >= order:
|
2018-04-06 13:58:59 -07:00
|
|
|
self.set_stroke(width=self.order_to_stroke_width_map[order])
|
2015-12-13 15:42:20 -08:00
|
|
|
|
|
|
|
def get_anchor_points(self):
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
class LindenmayerCurve(FractalCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"axiom": "A",
|
|
|
|
"rule": {},
|
|
|
|
"scale_factor": 2,
|
|
|
|
"radius": 3,
|
|
|
|
"start_step": RIGHT,
|
|
|
|
"angle": np.pi / 2,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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):
|
2018-04-06 13:58:59 -07:00
|
|
|
step = float(self.radius) * self.start_step
|
2015-12-13 15:42:20 -08:00
|
|
|
step /= (self.scale_factor**self.order)
|
|
|
|
curr = np.zeros(3)
|
|
|
|
result = [curr]
|
|
|
|
for letter in self.get_command_string():
|
2019-04-30 21:52:34 -07:00
|
|
|
if letter == "+":
|
2015-12-13 15:42:20 -08:00
|
|
|
step = rotate(step, self.angle)
|
2019-04-30 21:52:34 -07:00
|
|
|
elif letter == "-":
|
2015-12-13 15:42:20 -08:00
|
|
|
step = rotate(step, -self.angle)
|
|
|
|
else:
|
|
|
|
curr = curr + step
|
|
|
|
result.append(curr)
|
|
|
|
return np.array(result) - center_of_mass(result)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
class SelfSimilarSpaceFillingCurve(FractalCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"offsets": [],
|
|
|
|
# keys must awkwardly be in string form...
|
|
|
|
"offset_to_rotation_axis": {},
|
|
|
|
"scale_factor": 2,
|
|
|
|
"radius_scale_factor": 0.5,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
def transform(self, points, offset):
|
|
|
|
"""
|
|
|
|
How to transform the copy of points shifted by
|
|
|
|
offset. Generally meant to be extended in subclasses
|
|
|
|
"""
|
|
|
|
copy = np.array(points)
|
|
|
|
if str(offset) in self.offset_to_rotation_axis:
|
|
|
|
copy = rotate(
|
2018-04-06 13:58:59 -07:00
|
|
|
copy,
|
|
|
|
axis=self.offset_to_rotation_axis[str(offset)]
|
2015-12-13 15:42:20 -08:00
|
|
|
)
|
|
|
|
copy /= self.scale_factor,
|
2018-04-06 13:58:59 -07:00
|
|
|
copy += offset * self.radius * self.radius_scale_factor
|
2015-12-13 15:42:20 -08:00
|
|
|
return copy
|
|
|
|
|
|
|
|
def refine_into_subparts(self, points):
|
|
|
|
transformed_copies = [
|
|
|
|
self.transform(points, offset)
|
|
|
|
for offset in self.offsets
|
|
|
|
]
|
|
|
|
return reduce(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda a, b: np.append(a, b, axis=0),
|
2015-12-13 15:42:20 -08:00
|
|
|
transformed_copies
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_anchor_points(self):
|
|
|
|
points = np.zeros((1, 3))
|
|
|
|
for count in range(self.order):
|
|
|
|
points = self.refine_into_subparts(points)
|
|
|
|
return points
|
|
|
|
|
2015-12-19 13:06:09 -08:00
|
|
|
def generate_grid(self):
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"offsets": [
|
|
|
|
LEFT + DOWN,
|
|
|
|
LEFT + UP,
|
|
|
|
RIGHT + UP,
|
|
|
|
RIGHT + DOWN,
|
2015-12-13 15:42:20 -08:00
|
|
|
],
|
2018-04-06 13:58:59 -07:00
|
|
|
"offset_to_rotation_axis": {
|
|
|
|
str(LEFT + DOWN): RIGHT + UP,
|
|
|
|
str(RIGHT + DOWN): RIGHT + DOWN,
|
2015-12-13 15:42:20 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
}
|
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
|
|
|
|
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"offsets": [
|
|
|
|
RIGHT + DOWN + IN,
|
|
|
|
LEFT + DOWN + IN,
|
|
|
|
LEFT + DOWN + OUT,
|
|
|
|
RIGHT + DOWN + OUT,
|
|
|
|
RIGHT + UP + OUT,
|
|
|
|
LEFT + UP + OUT,
|
|
|
|
LEFT + UP + IN,
|
|
|
|
RIGHT + UP + IN,
|
2015-12-13 15:42:20 -08:00
|
|
|
],
|
2018-04-06 13:58:59 -07:00
|
|
|
"offset_to_rotation_axis_and_angle": {
|
|
|
|
str(RIGHT + DOWN + IN): (LEFT + UP + OUT, 2 * np.pi / 3),
|
|
|
|
str(LEFT + DOWN + IN): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
|
|
|
str(LEFT + DOWN + OUT): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
|
|
|
str(RIGHT + DOWN + OUT): (UP, np.pi),
|
|
|
|
str(RIGHT + UP + OUT): (UP, np.pi),
|
|
|
|
str(LEFT + UP + OUT): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
|
|
|
str(LEFT + UP + IN): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
|
|
|
str(RIGHT + UP + IN): (RIGHT + UP + IN, 2 * np.pi / 3),
|
2017-12-20 20:31:02 +08:00
|
|
|
},
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
2017-12-20 20:31:02 +08:00
|
|
|
# Rewrote transform method to include the rotation angle
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-12-20 20:31:02 +08:00
|
|
|
def transform(self, points, offset):
|
|
|
|
copy = np.array(points)
|
|
|
|
copy = rotate(
|
2018-04-06 13:58:59 -07:00
|
|
|
copy,
|
|
|
|
axis=self.offset_to_rotation_axis_and_angle[str(offset)][0],
|
|
|
|
angle=self.offset_to_rotation_axis_and_angle[str(offset)][1],
|
2017-12-20 20:31:02 +08:00
|
|
|
)
|
|
|
|
copy /= self.scale_factor,
|
2018-04-06 13:58:59 -07:00
|
|
|
copy += offset * self.radius * self.radius_scale_factor
|
2017-12-20 20:31:02 +08:00
|
|
|
return copy
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
class PeanoCurve(SelfSimilarSpaceFillingCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [PURPLE, TEAL],
|
|
|
|
"offsets": [
|
|
|
|
LEFT + DOWN,
|
2015-12-13 15:42:20 -08:00
|
|
|
LEFT,
|
2018-04-06 13:58:59 -07:00
|
|
|
LEFT + UP,
|
2015-12-13 15:42:20 -08:00
|
|
|
UP,
|
|
|
|
ORIGIN,
|
|
|
|
DOWN,
|
2018-04-06 13:58:59 -07:00
|
|
|
RIGHT + DOWN,
|
2015-12-13 15:42:20 -08:00
|
|
|
RIGHT,
|
2018-04-06 13:58:59 -07:00
|
|
|
RIGHT + UP,
|
2015-12-13 15:42:20 -08:00
|
|
|
],
|
2018-04-06 13:58:59 -07:00
|
|
|
"offset_to_rotation_axis": {
|
|
|
|
str(LEFT): UP,
|
|
|
|
str(UP): RIGHT,
|
|
|
|
str(ORIGIN): LEFT + UP,
|
|
|
|
str(DOWN): RIGHT,
|
|
|
|
str(RIGHT): UP,
|
2015-12-13 15:42:20 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"scale_factor": 3,
|
|
|
|
"radius_scale_factor": 2.0 / 3,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [MAROON, YELLOW],
|
|
|
|
"offsets": [
|
|
|
|
LEFT / 4. + DOWN / 6.,
|
2015-12-13 15:42:20 -08:00
|
|
|
ORIGIN,
|
2018-04-06 13:58:59 -07:00
|
|
|
RIGHT / 4. + DOWN / 6.,
|
|
|
|
UP / 3.,
|
2015-12-13 15:42:20 -08:00
|
|
|
],
|
2018-04-06 13:58:59 -07:00
|
|
|
"offset_to_rotation_axis": {
|
2015-12-13 15:42:20 -08:00
|
|
|
str(ORIGIN): RIGHT,
|
2018-04-06 13:58:59 -07:00
|
|
|
str(UP / 3.): UP,
|
2015-12-13 15:42:20 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"scale_factor": 2,
|
|
|
|
"radius_scale_factor": 1.5,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
# class HexagonFillingCurve(SelfSimilarSpaceFillingCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
# CONFIG = {
|
2015-12-13 15:42:20 -08:00
|
|
|
# "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),
|
2018-04-06 13:58:59 -07:00
|
|
|
# (np.sqrt(3)*UP+RIGHT, ORIGIN),
|
2015-12-13 15:42:20 -08:00
|
|
|
# ((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)
|
|
|
|
# )
|
|
|
|
|
|
|
|
|
|
|
|
class UtahFillingCurve(SelfSimilarSpaceFillingCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [WHITE, BLUE_D],
|
|
|
|
"axis_offset_pairs": [
|
2015-12-13 15:42:20 -08:00
|
|
|
|
|
|
|
],
|
2018-04-06 13:58:59 -07:00
|
|
|
"scale_factor": 3,
|
|
|
|
"radius_scale_factor": 2 / (3 * np.sqrt(3)),
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
class FlowSnake(LindenmayerCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [YELLOW, GREEN],
|
|
|
|
"axiom": "A",
|
|
|
|
"rule": {
|
|
|
|
"A": "A-B--B+A++AA+B-",
|
|
|
|
"B": "+A-BB--B-A++A+B",
|
2015-12-13 15:42:20 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 6, # TODO, this is innaccurate
|
|
|
|
"scale_factor": np.sqrt(7),
|
|
|
|
"start_step": RIGHT,
|
|
|
|
"angle": -np.pi / 3,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
LindenmayerCurve.__init__(self, **kwargs)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.rotate(-self.order * np.pi / 9, about_point=ORIGIN)
|
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class SierpinskiCurve(LindenmayerCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [RED, WHITE],
|
|
|
|
"axiom": "B",
|
|
|
|
"rule": {
|
|
|
|
"A": "+B-A-B+",
|
|
|
|
"B": "-A+B+A-",
|
2015-12-13 15:42:20 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 6, # TODO, this is innaccurate
|
|
|
|
"scale_factor": 2,
|
|
|
|
"start_step": RIGHT,
|
|
|
|
"angle": -np.pi / 3,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class KochSnowFlake(LindenmayerCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [BLUE_D, WHITE, BLUE_D],
|
|
|
|
"axiom": "A--A--A--",
|
|
|
|
"rule": {
|
|
|
|
"A": "A+A--A+A"
|
2016-01-15 11:46:45 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 4,
|
|
|
|
"scale_factor": 3,
|
|
|
|
"start_step": RIGHT,
|
|
|
|
"angle": np.pi / 3,
|
|
|
|
"order_to_stroke_width_map": {
|
|
|
|
3: 3,
|
|
|
|
5: 2,
|
|
|
|
6: 1,
|
2017-01-25 12:59:46 -08:00
|
|
|
},
|
2016-01-15 11:46:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.scale_factor = 2 * (1 + np.cos(self.angle))
|
2016-01-15 11:46:45 -08:00
|
|
|
LindenmayerCurve.__init__(self, **kwargs)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class KochCurve(KochSnowFlake):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"axiom": "A--"
|
2017-01-16 11:43:59 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-20 09:26:14 -08:00
|
|
|
class QuadraticKoch(LindenmayerCurve):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"colors": [YELLOW, WHITE, MAROON_B],
|
|
|
|
"axiom": "A",
|
|
|
|
"rule": {
|
|
|
|
"A": "A+A-A-AA+A+A-A"
|
2017-01-20 09:26:14 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 4,
|
|
|
|
"scale_factor": 4,
|
|
|
|
"start_step": RIGHT,
|
|
|
|
"angle": np.pi / 2
|
2017-01-20 09:26:14 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-27 13:23:17 -08:00
|
|
|
class QuadraticKochIsland(QuadraticKoch):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"axiom": "A+A+A+A"
|
2017-01-27 13:23:17 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-31 09:27:48 -08:00
|
|
|
class StellarCurve(LindenmayerCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"start_color": RED,
|
|
|
|
"end_color": BLUE_E,
|
|
|
|
"rule": {
|
|
|
|
"A": "+B-A-B+A-B+",
|
|
|
|
"B": "-A+B+A-B+A-",
|
2015-12-31 09:27:48 -08:00
|
|
|
},
|
2018-04-06 13:58:59 -07:00
|
|
|
"scale_factor": 3,
|
|
|
|
"angle": 2 * np.pi / 5,
|
2015-12-31 09:27:48 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-17 17:14:32 -08:00
|
|
|
class SnakeCurve(FractalCurve):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"start_color": BLUE,
|
|
|
|
"end_color": YELLOW,
|
2015-12-13 15:42:20 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
def get_anchor_points(self):
|
|
|
|
result = []
|
|
|
|
resolution = 2**self.order
|
2018-04-06 13:58:59 -07:00
|
|
|
step = 2.0 * self.radius / resolution
|
2015-12-13 15:42:20 -08:00
|
|
|
lower_left = ORIGIN + \
|
2018-04-06 13:58:59 -07:00
|
|
|
LEFT * (self.radius - step / 2) + \
|
|
|
|
DOWN * (self.radius - step / 2)
|
2015-12-21 22:50:53 -08:00
|
|
|
|
2015-12-13 15:42:20 -08:00
|
|
|
for y in range(resolution):
|
2018-08-09 17:56:05 -07:00
|
|
|
x_range = list(range(resolution))
|
2018-04-06 13:58:59 -07:00
|
|
|
if y % 2 == 0:
|
2015-12-13 15:42:20 -08:00
|
|
|
x_range.reverse()
|
|
|
|
for x in x_range:
|
|
|
|
result.append(
|
2018-04-06 13:58:59 -07:00
|
|
|
lower_left + x * step * RIGHT + y * step * UP
|
2015-12-13 15:42:20 -08:00
|
|
|
)
|
|
|
|
return result
|