magic - performance up 50x
parent
611fb2f9ae
commit
9148985caa
@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import cairo
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import colorsys
|
||||||
|
from utils import circle_fill
|
||||||
|
from utils import circle_clip_fill
|
||||||
|
from utils import random_color
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
WIDTH, HEIGHT = 1024, 1024
|
||||||
|
ANGLE_RANDOM_MIN = -0.6
|
||||||
|
ANGLE_RANDOM_MAX = 0.6
|
||||||
|
# how much to shrink each consecutive circle
|
||||||
|
SHRINK = 0.00004
|
||||||
|
start_r = 0.01
|
||||||
|
|
||||||
|
NODE_ARRAY = None
|
||||||
|
|
||||||
|
start_hue = 0
|
||||||
|
|
||||||
|
# should be ~ 1 px
|
||||||
|
MIN_RADIUS = ((1/WIDTH) + (1/HEIGHT)) / 2
|
||||||
|
|
||||||
|
def chunker(seq, size):
|
||||||
|
return (seq[pos:pos + size] for pos in range(0, len(seq), size))
|
||||||
|
|
||||||
|
class Branch():
|
||||||
|
def __init__(self, idx, ctx, x, y, r, ang, mother=None):
|
||||||
|
#ctx.set_source_rgb(255, 0, 0)
|
||||||
|
self.nodes = [Node(ctx, x, y, r, ang, dry=True)]
|
||||||
|
#ctx.set_source_rgb(0,0, 0)
|
||||||
|
self.idx = idx
|
||||||
|
self.ctx = ctx
|
||||||
|
self.ended = False
|
||||||
|
self.ignores = []
|
||||||
|
self.first = True
|
||||||
|
self.mother = mother
|
||||||
|
|
||||||
|
def _last_node(self):
|
||||||
|
return self.nodes[-1]
|
||||||
|
def set_ignores(self, ignores):
|
||||||
|
self.ignores = ignores
|
||||||
|
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
|
||||||
|
# too small?
|
||||||
|
if next_r < MIN_RADIUS:
|
||||||
|
self.ended = True
|
||||||
|
return False
|
||||||
|
# did we hit canvas edge?
|
||||||
|
# if next_x + next_r > 1 or next_x - next_r < 0:
|
||||||
|
if (math.pow(next_x - 0.5, 2) + math.pow(next_y - 0.5, 2)) > math.pow(0.4, 2):
|
||||||
|
self.ended = True
|
||||||
|
return False
|
||||||
|
if next_y + next_r > 1 or next_y - next_r < 0:
|
||||||
|
self.ended = True
|
||||||
|
return False
|
||||||
|
last_nodes = self.nodes[-2:]
|
||||||
|
# did we hit another circle?
|
||||||
|
# search radius is radius of biggest possible circle + our radius
|
||||||
|
search_radius = next_r + start_r
|
||||||
|
search_box_x1 = next_x - search_radius
|
||||||
|
search_box_x2 = next_x + search_radius
|
||||||
|
search_box_y1 = next_y - search_radius
|
||||||
|
search_box_y2 = next_y + search_radius
|
||||||
|
filtered = NODE_ARRAY[NODE_ARRAY['x'] > search_box_x1]
|
||||||
|
filtered = filtered[filtered['x'] < search_box_x2]
|
||||||
|
filtered = filtered[filtered['y'] > search_box_y1]
|
||||||
|
filtered = filtered[filtered['y'] < search_box_y2]
|
||||||
|
# remove previous nodes
|
||||||
|
for ln in last_nodes:
|
||||||
|
filtered = filtered[ (filtered['x'] != ln.x) & (filtered['y'] != ln.y) ]
|
||||||
|
for possible_hit in filtered:
|
||||||
|
if circles_intersect(*possible_hit, next_x, next_y, next_r):
|
||||||
|
self.ended = True
|
||||||
|
return False
|
||||||
|
|
||||||
|
next_ang = last.ang + (random.uniform(ANGLE_RANDOM_MIN, ANGLE_RANDOM_MAX) * (1 - 40*last.r))
|
||||||
|
self.nodes.append(Node(self.ctx, next_x, next_y, next_r, next_ang))
|
||||||
|
if self.first:
|
||||||
|
self.first = False
|
||||||
|
first = self.nodes[0]
|
||||||
|
if self.mother:
|
||||||
|
self.ctx.set_operator(cairo.Operator.DEST_OVER)
|
||||||
|
circle_fill(self.ctx, first.x, first.y, first.r)
|
||||||
|
self.ctx.set_operator(cairo.Operator.OVER)
|
||||||
|
else:
|
||||||
|
circle_fill(self.ctx, first.x, first.y, first.r)
|
||||||
|
return True
|
||||||
|
|
||||||
|
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
|
||||||
|
return distance < combined_r
|
||||||
|
|
||||||
|
|
||||||
|
class Node():
|
||||||
|
def __init__(self, ctx, x, y, r, ang, dry=False):
|
||||||
|
global NODE_ARRAY
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.r = r
|
||||||
|
self.ang = ang
|
||||||
|
if not dry:
|
||||||
|
NODE_ARRAY = numpy.append(NODE_ARRAY,
|
||||||
|
numpy.array([(x, y, r)],
|
||||||
|
dtype=[('x', 'float'), ('y', 'float'), ('r', 'float')])
|
||||||
|
)
|
||||||
|
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 grow_sub(ctx, branch, branches, new_subs):
|
||||||
|
# create a sub branch based on length
|
||||||
|
sub_branches = len(branch.nodes) // 7 * 3
|
||||||
|
if sub_branches == 0: return
|
||||||
|
#sub_branches = 4
|
||||||
|
#print(f'creating {sub_branches} subs')
|
||||||
|
for i in range(sub_branches):
|
||||||
|
for n in range(20): # attempts at growing branches
|
||||||
|
found_ang = False
|
||||||
|
start_node = random.choice(branch.nodes)
|
||||||
|
for a_try in range(10):
|
||||||
|
# start perpendicular to our last angle
|
||||||
|
start_angle = start_node.ang + random.choice([90, -90]) + random.uniform(-30, 30)
|
||||||
|
start_x = (start_node.r * 1.2) * math.sin(start_angle) + start_node.x
|
||||||
|
start_y = (start_node.r * 1.2) * math.cos(start_angle) + start_node.y
|
||||||
|
start_r = start_node.r * 0.8
|
||||||
|
new_branch = Branch(0, ctx, start_x, start_y, start_r, start_angle, mother=start_node)
|
||||||
|
if new_branch.place_next(branches):
|
||||||
|
found_ang = True
|
||||||
|
break
|
||||||
|
if found_ang:
|
||||||
|
break
|
||||||
|
|
||||||
|
new_branch.set_ignores(branch.nodes)
|
||||||
|
branches.append(new_branch)
|
||||||
|
new_subs.append(new_branch)
|
||||||
|
|
||||||
|
def grow_branch_until_ended(branch, branches):
|
||||||
|
while not branch.ended:
|
||||||
|
branch.place_next([b for b in branches if b != branch])
|
||||||
|
|
||||||
|
def grow_subs(ctx, subs, branches):
|
||||||
|
source = ctx.get_source()
|
||||||
|
new_subs = []
|
||||||
|
for branch in subs:
|
||||||
|
grow_sub(ctx, branch, branches, new_subs)
|
||||||
|
for branch in new_subs:
|
||||||
|
grow_branch_until_ended(branch, branches)
|
||||||
|
return new_subs
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global start_hue
|
||||||
|
global NODE_ARRAY
|
||||||
|
for run in range(32):
|
||||||
|
random.seed()
|
||||||
|
start_hue = random.uniform(0, 1)
|
||||||
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
|
||||||
|
ctx = cairo.Context(surface)
|
||||||
|
|
||||||
|
ctx.scale(WIDTH, HEIGHT) # Normalizing the canvas
|
||||||
|
|
||||||
|
|
||||||
|
# place seeds
|
||||||
|
branches = []
|
||||||
|
r, g, b = colorsys.hsv_to_rgb(start_hue, 1.0, 1.0)
|
||||||
|
#r, g, b = random_color()
|
||||||
|
ctx.set_source_rgb(r, g, b)
|
||||||
|
branches.append(Branch(0, ctx, 0.5, 0.4, start_r, 180))
|
||||||
|
branches.append(Branch(1, ctx, 0.4, 0.5, start_r, 270))
|
||||||
|
branches.append(Branch(2, ctx, 0.6, 0.5, start_r, 90))
|
||||||
|
branches.append(Branch(3, ctx, 0.5, 0.6, start_r, 0))
|
||||||
|
NODE_ARRAY = numpy.array([
|
||||||
|
(0.5, 0.4, start_r),
|
||||||
|
(0.4, 0.5, start_r),
|
||||||
|
(0.6, 0.5, start_r),
|
||||||
|
(0.5, 0.6, start_r)
|
||||||
|
], dtype = [('x', 'float'), ('y', 'float'), ('r', 'float')])
|
||||||
|
|
||||||
|
# grow initial branches
|
||||||
|
print('growing initial branches')
|
||||||
|
for branch in branches:
|
||||||
|
grow_branch_until_ended(branch, branches)
|
||||||
|
subs = branches
|
||||||
|
rarity = 3 + random.randint(0, 5)
|
||||||
|
print(f'rarity: {rarity}')
|
||||||
|
try:
|
||||||
|
for x in range(rarity):
|
||||||
|
if subs == None:
|
||||||
|
return
|
||||||
|
r, g, b = colorsys.hsv_to_rgb(start_hue + x * 0.05, 1.0, 1.0)
|
||||||
|
ctx.set_source_rgb(r, g, b)
|
||||||
|
subs = grow_subs(ctx, subs, branches)
|
||||||
|
|
||||||
|
print(f'iteration {x} done')
|
||||||
|
#surface.write_to_png("out/hyphae.png") # Output to PNG
|
||||||
|
finally:
|
||||||
|
surface.write_to_png(f"out/hyphae_{run}_{rarity}.png") # Output to PNG
|
||||||
|
print(f'run {run} complete')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue