You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
2.9 KiB
Python
94 lines
2.9 KiB
Python
3 years ago
|
#!/usr/bin/env python3
|
||
|
|
||
|
import cairo
|
||
|
import math
|
||
|
import random
|
||
|
from utils import circle_fill
|
||
|
from utils import random_color
|
||
|
|
||
|
WIDTH, HEIGHT = 1000, 1000
|
||
|
ANGLE_RANDOM_MIN = -0.8
|
||
|
ANGLE_RANDOM_MAX = 0.8
|
||
|
# how much to shrink each consecutive circle
|
||
|
SHRINK = 0.0002
|
||
|
|
||
|
class Branch():
|
||
|
def __init__(self, idx, ctx, x, y, r, ang):
|
||
|
self.nodes = [Node(ctx, x, y, r, ang)]
|
||
|
self.idx = idx
|
||
|
self.ctx = ctx
|
||
|
self.ended = False
|
||
|
def _last_node(self):
|
||
|
return self.nodes[-1]
|
||
|
def place_next(self, branches):
|
||
|
if not self.ended:
|
||
|
last = self._last_node()
|
||
|
next_x = last.r * math.sin(last.ang) + last.x
|
||
|
next_y = last.r * math.cos(last.ang) + last.y
|
||
|
next_r = last.r - SHRINK
|
||
|
# did we hit canvas edge?
|
||
|
if next_x + next_r > 1 or next_x - next_r < 0:
|
||
|
self.ended = True
|
||
|
return
|
||
|
if next_y + next_r > 1 or next_y - next_r < 0:
|
||
|
self.ended = True
|
||
|
return
|
||
|
last_nodes = self.nodes[-2:]
|
||
|
# did we hit another circle?
|
||
|
for branch in branches:
|
||
|
for node in branch.nodes:
|
||
|
if node not in last_nodes: #!= last and node != self.nodes[-2]:
|
||
|
if circles_intersect(node.x, node.y, node.r,
|
||
|
next_x, next_y, next_r):
|
||
|
print(f'intersect: {next_x},{next_y} r: {next_r} with {node}')
|
||
|
self.ended = True
|
||
|
return
|
||
|
|
||
|
next_ang = last.ang + random.uniform(ANGLE_RANDOM_MIN, ANGLE_RANDOM_MAX)
|
||
|
print(f'next circle: {next_x},{next_y} r: {next_r} a: {next_ang}')
|
||
|
self.nodes.append(Node(self.ctx, next_x, next_y, next_r, next_ang))
|
||
|
|
||
|
|
||
|
def circles_intersect(x1, y1, r1, x2, y2, r2):
|
||
|
distance = math.sqrt(math.pow(abs(x2 - x1), 2) + math.pow(abs(y2 - y1), 2))
|
||
|
combined_r = r1 + r2
|
||
|
print(f'd: {distance} r1 + r2: {combined_r}')
|
||
|
return distance < combined_r
|
||
|
|
||
|
|
||
|
class Node():
|
||
|
def __init__(self, ctx, x, y, r, ang):
|
||
|
self.x = x
|
||
|
self.y = y
|
||
|
self.r = r
|
||
|
self.ang = ang
|
||
|
circle_fill(ctx, x, y, r)
|
||
|
def __ne__(self, other):
|
||
|
return self.x != other.x or self.y != other.y or self.r != other.r or self.ang != other.ang
|
||
|
|
||
|
|
||
|
def main():
|
||
|
|
||
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
|
||
|
ctx = cairo.Context(surface)
|
||
|
|
||
|
ctx.scale(WIDTH, HEIGHT) # Normalizing the canvas
|
||
|
|
||
|
|
||
|
branches = []
|
||
|
for b in range(6):
|
||
|
start_x = random.uniform(0.3, 0.7)
|
||
|
start_y = random.uniform(0.3, 0.7)
|
||
|
start_r = 0.02
|
||
|
start_angle = random.randint(0, 360)
|
||
|
branches.append(Branch(b, ctx, start_x, start_y, start_r, start_angle))
|
||
|
for x in range(100):
|
||
|
for branch in branches:
|
||
|
branch.place_next(branches)
|
||
|
|
||
|
|
||
|
surface.write_to_png("out/hyphae.png") # Output to PNG
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|