def triangle_koch(d: int = 400) -> LSystem: c = 1 p = 0.3 q = c - p h = (p * q)**0.5 FORWARD = BranchKind(1) tp = Branch(angle=86, length=0) tn = Branch(angle=-86, length=0) def forward(d: float) -> Branch: return Branch(angle=0, length=d, kind=FORWARD) system = LSystem([forward(d)], recommended_depth=4) @system.add_rule(FORWARD) def f_replace(snapshot: BranchSnapshot) -> List[Command]: x = snapshot.branch.length return [ forward(x * p), tp, forward(x * h), tn, tn, forward(x * h), tp, forward(x * q) ] return system
def handle_start(_: BranchSnapshot) -> List[Command]: out = [Branch(angle=0, length=start_length, energy=start_energy)] for _ in range(2): out.append( Branch( angle=random.randint(-10, 10), length=start_length, energy=start_energy, )) out[-1].kind = BRANCH return out
def koch_island(d: int = 10) -> LSystem: FORWARD = BranchKind(1) f = Branch(angle=0, length=d, kind=FORWARD) tp = Branch(angle=90, length=0) tn = Branch(angle=-90, length=0) system = LSystem([f, tn, f, tn, f, tn, f], recommended_depth=4) @system.add_rule(FORWARD) def f_replace(_: BranchSnapshot) -> List[Command]: return [f, tn, f, tp, f, tp, f, f, tn, f, tn, f, tp, f] return system
def bushy_tree(d: int = 10) -> LSystem: FORWARD = BranchKind(1) f = Branch(angle=0, length=d, kind=FORWARD) tp = Branch(angle=22.5, length=0) tn = Branch(angle=-22.5, length=0) push = Push() pop = Pop() system = LSystem([f], recommended_depth=4) @system.add_rule(FORWARD) def f_replace(_: BranchSnapshot) -> List[Command]: return [ f, f, tn, push, tn, f, tp, f, tp, f, pop, tp, push, tp, f, tn, f, tn, f, pop ] return system
def dragon_curve(d: int = 10) -> LSystem: T1 = BranchKind(1) T2 = BranchKind(2) f1 = Branch(angle=0, length=d, kind=T1) f2 = Branch(angle=0, length=d, kind=T2) tp = Branch(angle=90, length=0) tn = Branch(angle=-90, length=0) system = LSystem([f1], recommended_depth=10) @system.add_rule(T1) def f1_replace(_: BranchSnapshot) -> List[Command]: return [f1, tp, f2, tp] @system.add_rule(T2) def f2_replace(_: BranchSnapshot) -> List[Command]: return [tn, f1, tn, f2] return system
def flower_field(d: int = 10) -> LSystem: FORWARD = BranchKind(1) f = Branch(angle=0, length=d, kind=FORWARD) tp = Branch(angle=30, length=0) tn = Branch(angle=-30, length=0) push = Push() pop = Pop() system = LSystem([f], recommended_depth=4) @system.add_rule(FORWARD) def f_replace(_: BranchSnapshot) -> List[Command]: r = random.random() if r <= 0.33: return [f, push, tp, f, pop, f, push, tn, f, pop, f] elif r <= 0.66: return [f, push, tp, f, pop, f] else: return [f, push, tn, f, pop, f] return system
def weed_plant(start_energy: float = 100) -> LSystem: def energy_to_length(energy: float) -> int: return int(round(energy / 4)) START = BranchKind(1) BRANCH = BranchKind(2) LEAF = BranchKind(3) start_length = energy_to_length(start_energy) system = LSystem( seed=[ Branch(angle=0, length=start_length, resistance=1.0, energy=start_energy, kind=START) ], recommended_depth=20, ) @system.add_rule(START) def handle_start(_: BranchSnapshot) -> List[Command]: out = [Branch(angle=0, length=start_length, energy=start_energy)] for _ in range(2): out.append( Branch( angle=random.randint(-10, 10), length=start_length, energy=start_energy, )) out[-1].kind = BRANCH return out @system.add_rule(BRANCH) def handle_branch(snapshot: BranchSnapshot) -> List[Command]: def constrain_heading(relative_angle: float) -> float: new_global_heading = snapshot.heading + relative_angle too_large = True while too_large: if new_global_heading >= 190: new_global_heading = 190 - random.uniform(0, 10) elif new_global_heading <= -10: new_global_heading = -10 + random.uniform(0, 10) else: too_large = False new_relative_heading = new_global_heading - snapshot.heading return new_relative_heading branch = snapshot.branch energy = branch.energy if energy <= 30: return [branch.clone(kind=LEAF)] if random.random() <= 0.6: left_energy = branch.energy * random.uniform(0.85, 0.9) right_energy = branch.energy * random.uniform(0.85, 0.9) gap_energy = random.uniform( 0, branch.energy * random.uniform(0.85, 0.9)) # Branch into two left = [ Push(), Branch( angle=constrain_heading(random.randint(-30, -20)), length=energy_to_length(left_energy), energy=left_energy, kind=BRANCH, ), Pop(), ] right = [ Push(), Branch( angle=constrain_heading(random.randint(20, 30)), length=energy_to_length(right_energy), energy=right_energy, kind=BRANCH, ), Pop(), ] gap = Branch(angle=0, length=energy_to_length(gap_energy), energy=gap_energy) if random.random() <= 5: return [branch.clone(kind=DEFAULT_KIND), *left, gap, *right] else: return [branch.clone(kind=DEFAULT_KIND), *right, gap, *left] else: # Continue straight-ish new_energy = branch.energy * 0.95 return [ branch.clone(kind=DEFAULT_KIND), Branch( angle=constrain_heading(random.randint(-10, 10)), length=energy_to_length(new_energy), energy=new_energy, kind=BRANCH, ), ] @system.add_render_rule(LEAF) def render_leaf(t: TurtleWrapper, branch: Branch) -> None: t.shape("circle") t.shapesize(0.2, 1.0, 0) t.fillcolor(0.8, 1.0, 0.8) t.left(branch.angle) t.forward(branch.length // 2) t.pendown() t.stamp() t.penup() return system
def handle_branch(snapshot: BranchSnapshot) -> List[Command]: def constrain_heading(relative_angle: float) -> float: new_global_heading = snapshot.heading + relative_angle too_large = True while too_large: if new_global_heading >= 190: new_global_heading = 190 - random.uniform(0, 10) elif new_global_heading <= -10: new_global_heading = -10 + random.uniform(0, 10) else: too_large = False new_relative_heading = new_global_heading - snapshot.heading return new_relative_heading branch = snapshot.branch energy = branch.energy if energy <= 30: return [branch.clone(kind=LEAF)] if random.random() <= 0.6: left_energy = branch.energy * random.uniform(0.85, 0.9) right_energy = branch.energy * random.uniform(0.85, 0.9) gap_energy = random.uniform( 0, branch.energy * random.uniform(0.85, 0.9)) # Branch into two left = [ Push(), Branch( angle=constrain_heading(random.randint(-30, -20)), length=energy_to_length(left_energy), energy=left_energy, kind=BRANCH, ), Pop(), ] right = [ Push(), Branch( angle=constrain_heading(random.randint(20, 30)), length=energy_to_length(right_energy), energy=right_energy, kind=BRANCH, ), Pop(), ] gap = Branch(angle=0, length=energy_to_length(gap_energy), energy=gap_energy) if random.random() <= 5: return [branch.clone(kind=DEFAULT_KIND), *left, gap, *right] else: return [branch.clone(kind=DEFAULT_KIND), *right, gap, *left] else: # Continue straight-ish new_energy = branch.energy * 0.95 return [ branch.clone(kind=DEFAULT_KIND), Branch( angle=constrain_heading(random.randint(-10, 10)), length=energy_to_length(new_energy), energy=new_energy, kind=BRANCH, ), ]
def forward(d: float) -> Branch: return Branch(angle=0, length=d, kind=FORWARD)