Skip to content

Instantly share code, notes, and snippets.

@PardhavMaradani
Last active August 2, 2020 16:52
Show Gist options
  • Select an option

  • Save PardhavMaradani/fc072ddf64cb7e589588fb492ff602e1 to your computer and use it in GitHub Desktop.

Select an option

Save PardhavMaradani/fc072ddf64cb7e589588fb492ff602e1 to your computer and use it in GitHub Desktop.
Blog: Auto Generated Mandalas
# https://pardhav-m.blogspot.com/2020/08/auto-generated-mandalas.html
# https://trinket.io/embed/python/8dffbb47db
# Auto Generated Mandalas
# Random Walker
import math
import turtle
from slider import Slider
from line_clip import cohensutherland
import time
import random
# global vars
g_sectors = 16
g_pensize = 1
g_mirror = 1
g_show_sectors = 1
g_color = 'black'
g_colors = ["black", "dark grey", "cornflower blue", "deep sky blue", "dark turquoise", "aquamarine", "medium sea green", "khaki", "sienna", "firebrick", "coral", "red", "crimson", "hot pink", "dark orchid", "medium slate blue"]
g_pause_for_click = False
# screen setup
g_screen = turtle.Screen()
g_wh = int(g_screen.window_height())
g_ww = int(1.5 * g_wh)
g_screen_radius = g_wh / 2
g_screen.setup(g_ww, g_wh)
g_screen.tracer(0)
g_center = [g_ww / 6, 0]
g_minx = -g_ww / 6
g_maxx = g_ww / 2
g_miny = -g_wh / 2
g_maxy = g_wh / 2
# random walker vars
g_steps_max = int(g_screen_radius)
g_magnitude_max = int(g_screen_radius * 0.08)
g_steps = int(g_steps_max / 2)
g_magnitude = int(g_magnitude_max / 2)
# internal
g_prev_ct = None
g_redraw = False
g_paused = False
# new turtle
def new_turtle():
t = turtle.Turtle()
t.ht()
t.speed(0)
return t
# sector turtle
st = new_turtle()
st.color('lightgray')
# main drawing turtle
t = new_turtle()
# draw a border
def draw_border():
t = new_turtle()
t.pu()
t.goto(-g_ww/2, g_miny)
t.pd()
t.goto(g_ww/2, g_miny)
t.goto(g_ww/2, g_maxy)
t.goto(-g_ww/2, g_maxy)
t.goto(-g_ww/2, g_miny)
# partition
t.color('gray')
t.pu()
t.goto(g_minx, g_miny)
t.pd()
t.goto(g_minx, g_maxy)
t.update()
# handle slider updates
def handle_slider_update(id, value):
global g_sectors
global g_pensize
global g_mirror
global g_show_sectors
global g_steps
global g_magnitude
global g_redraw
if id == 0:
g_sectors = value
draw_sectors()
elif id == 1:
g_pensize = value
elif id == 2:
g_mirror = value
elif id == 3:
g_show_sectors = value
draw_sectors()
elif id == 4:
g_steps = value
elif id == 5:
g_magnitude = value
g_redraw = True
# Color button class
class ColorButton(turtle.Turtle):
def __init__(self, x, y, color, click_callback = None):
turtle.Turtle.__init__(self)
self.shape('circle')
self.speed(0)
self.my_color = color
self.click_callback = click_callback
self.color(color)
self.pu()
self.goto(x, y)
self.left(90)
self.onclick(self.onclick_handler)
self.update()
# handle button click
def onclick_handler(self, x, y):
if self.click_callback:
self.click_callback(self, self.my_color)
self.shape('turtle')
self.update()
# color button click handler
def handle_color_click(ct, color):
global g_color
global g_prev_ct
global g_redraw
g_prev_ct.shape('circle')
g_prev_ct.update()
g_color = color
g_prev_ct = ct
g_redraw = True
# create UI
def create_ui():
global g_prev_ct
x = -g_ww / 2 + 20
y = g_wh / 2 - 20
normal_length = g_screen_radius * 0.55
toggle_length = g_screen_radius * 0.15
# sliders
Slider(0, x, y, normal_length, 1, 32, 1, g_sectors, 'sectors', handle_slider_update)
Slider(1, x, y - 30, normal_length, 1, 16, 1, g_pensize, 'pensize', handle_slider_update)
Slider(2, x, y - 60, toggle_length, 0, 1, 1, g_mirror, 'mirror', handle_slider_update)
Slider(3, x, y - 90, toggle_length, 0, 1, 1, g_show_sectors, 'show_sectors', handle_slider_update)
# color buttons
ci = 0
for r in range(2):
cx = x
cy = y - (150 + (30 * r))
for c in range(8):
color = g_colors[ci]
cb = ColorButton(cx + (c * 25), cy, color, handle_color_click)
if color == g_color:
cb.shape('turtle')
g_prev_ct = cb
ci += 1
Slider(4, x, y - 240, normal_length * 0.85, 1, g_steps_max, 10, g_steps, 'steps', handle_slider_update)
Slider(5, x, y - 270, normal_length * 0.85, 1, g_magnitude_max, 1, g_magnitude, 'magnitude', handle_slider_update)
# draw sectors
def draw_sectors():
st.clear()
if g_show_sectors == 0:
return
sector_angle = 360 / g_sectors
cx, cy = g_center
for i in range(g_sectors):
st.pu()
st.goto(cx, cy)
st.seth(sector_angle * (i + 1))
st.fd(g_ww)
(x, y) = st.pos()
(nx1, ny1, nx2, ny2) = cohensutherland(g_minx, g_maxy, g_maxx, g_miny, cx, cy, x, y)
if nx1 != None:
st.goto(nx1, ny1)
st.pd()
st.goto(nx2, ny2)
st.update()
# rotate point
def rotate_point(x, y, angle):
cx, cy = g_center
x -= cx
y -= cy
sin = math.sin(math.radians(angle))
cos = math.cos(math.radians(angle))
rx = cx + (x * cos - y * sin)
ry = cy + (x * sin + y * cos)
return (rx, ry)
# angle of point wrt center
def point_angle(x, y):
cx, cy = g_center
return math.degrees(math.atan2(y - cy, x - cx))
# draw line between two points
def draw_line(t, px, py, x, y, angle):
(rpx, rpy) = rotate_point(px, py, angle)
(rx, ry) = rotate_point(x, y, angle)
if rpx < g_minx or rx < g_minx:
return
t.pu()
t.goto(rpx, rpy)
t.pd()
t.goto(rx, ry)
# draw line segment
def draw_segment(t, px, py, x, y, sectors, mirror):
sector_angle = 360 / sectors
h_sector_angle = sector_angle / 2
for i in range(sectors):
draw_line(t, px, py, x, y, sector_angle * i)
if mirror == 1:
pa = point_angle(px, py) % sector_angle
pma = h_sector_angle - pa
(rpx, rpy) = rotate_point(px, py, 2 * pma)
a = point_angle(x, y) % sector_angle
ma = h_sector_angle - a
(rx, ry) = rotate_point(x, y, 2 * ma)
if abs(pma - ma) > h_sector_angle:
continue
draw_line(t, rpx, rpy, rx, ry, sector_angle * i)
# draw points
def draw_points(points):
t.clear()
t.pensize(g_pensize)
t.color(g_color)
(px, py) = points[0]
for v in points:
x, y = v
draw_segment(t, px, py, x, y, g_sectors, g_mirror)
px, py = x, y
t.update()
if g_redraw:
break
# draw
def draw():
global g_redraw
g_redraw = False
cx, cy = g_center
points = []
# px, py = 0, 0
px = random.randint(0, int(g_screen_radius * 0.5))
py = random.randint(0, int(g_screen_radius * 0.5))
for i in range(g_steps):
dx = random.randint(-g_magnitude, g_magnitude)
dy = random.randint(-g_magnitude, g_magnitude)
x = px + dx
y = py + dy
points.append((cx + x, cy + y))
px, py = x, y
draw_points(points)
# draw mandalas in loop
def draw_loop():
global g_paused
while True:
draw()
if g_pause_for_click and not g_redraw:
g_paused = True
break
if not g_redraw:
time.sleep(1)
# handle screen click
def handle_screen_click(x, y):
global g_paused
if g_pause_for_click and g_paused:
g_paused = False
draw_loop()
# setup
def setup():
draw_border()
create_ui()
draw_sectors()
g_screen.onclick(handle_screen_click)
# main
setup()
draw_loop()
# https://pardhav-m.blogspot.com/2020/08/auto-generated-mandalas.html
# https://trinket.io/embed/python/63e95daeca
# Auto Generated Mandalas
# Perlin Noise Walker
import math
import turtle
from slider import Slider
from line_clip import cohensutherland
import time
import random
from noise import SimplexNoise
# global vars
g_sectors = 16
g_pensize = 1
g_mirror = 1
g_show_sectors = 1
g_color = 'black'
g_colors = ["black", "dark grey", "cornflower blue", "deep sky blue", "dark turquoise", "aquamarine", "medium sea green", "khaki", "sienna", "firebrick", "coral", "red", "crimson", "hot pink", "dark orchid", "medium slate blue"]
g_pause_for_click = False
# screen setup
g_screen = turtle.Screen()
g_wh = int(g_screen.window_height())
g_ww = int(1.5 * g_wh)
g_screen_radius = g_wh / 2
g_screen.setup(g_ww, g_wh)
g_screen.tracer(0)
g_center = [g_ww / 6, 0]
g_minx = -g_ww / 6
g_maxx = g_ww / 2
g_miny = -g_wh / 2
g_maxy = g_wh / 2
# perlin walker vars
g_noise_object = SimplexNoise()
g_steps_max = int(g_screen_radius * 2)
g_steps = int(g_steps_max / 2)
g_magnitude = 0.8
g_speed = 2
g_show_path = 1
# internal
g_prev_ct = None
g_redraw = False
g_paused = False
# new turtle
def new_turtle():
t = turtle.Turtle()
t.ht()
t.speed(0)
return t
# sector turtle
st = new_turtle()
st.color('lightgray')
# walker turtle
wt = new_turtle()
wt.color('red')
# main drawing turtle
t = new_turtle()
# draw a border
def draw_border():
t = new_turtle()
t.pu()
t.goto(-g_ww/2, g_miny)
t.pd()
t.goto(g_ww/2, g_miny)
t.goto(g_ww/2, g_maxy)
t.goto(-g_ww/2, g_maxy)
t.goto(-g_ww/2, g_miny)
# partition
t.color('gray')
t.pu()
t.goto(g_minx, g_miny)
t.pd()
t.goto(g_minx, g_maxy)
t.update()
# handle slider updates
def handle_slider_update(id, value):
global g_sectors
global g_pensize
global g_mirror
global g_show_sectors
global g_steps
global g_magnitude
global g_speed
global g_show_path
global g_redraw
if id == 0:
g_sectors = value
draw_sectors()
elif id == 1:
g_pensize = value
elif id == 2:
g_mirror = value
elif id == 3:
g_show_sectors = value
draw_sectors()
elif id == 4:
g_steps = value
elif id == 5:
g_magnitude = value
elif id == 6:
g_speed = value
elif id == 7:
g_show_path = value
g_redraw = True
# Color button class
class ColorButton(turtle.Turtle):
def __init__(self, x, y, color, click_callback = None):
turtle.Turtle.__init__(self)
self.shape('circle')
self.speed(0)
self.my_color = color
self.click_callback = click_callback
self.color(color)
self.pu()
self.goto(x, y)
self.left(90)
self.onclick(self.onclick_handler)
self.update()
# handle button click
def onclick_handler(self, x, y):
if self.click_callback:
self.click_callback(self, self.my_color)
self.shape('turtle')
self.update()
# color button click handler
def handle_color_click(ct, color):
global g_color
global g_prev_ct
global g_redraw
g_prev_ct.shape('circle')
g_prev_ct.update()
g_color = color
g_prev_ct = ct
g_redraw = True
# create UI
def create_ui():
global g_prev_ct
x = -g_ww / 2 + 20
y = g_wh / 2 - 20
normal_length = g_screen_radius * 0.55
toggle_length = g_screen_radius * 0.15
# sliders
Slider(0, x, y, normal_length, 1, 32, 1, g_sectors, 'sectors', handle_slider_update)
Slider(1, x, y - 30, normal_length, 1, 16, 1, g_pensize, 'pensize', handle_slider_update)
Slider(2, x, y - 60, toggle_length, 0, 1, 1, g_mirror, 'mirror', handle_slider_update)
Slider(3, x, y - 90, toggle_length, 0, 1, 1, g_show_sectors, 'show_sectors', handle_slider_update)
# color buttons
ci = 0
for r in range(2):
cx = x
cy = y - (150 + (30 * r))
for c in range(8):
color = g_colors[ci]
cb = ColorButton(cx + (c * 25), cy, color, handle_color_click)
if color == g_color:
cb.shape('turtle')
g_prev_ct = cb
ci += 1
Slider(4, x, y - 240, normal_length * 0.85, 1, g_steps_max, 10, g_steps, 'steps', handle_slider_update)
Slider(5, x, y - 270, normal_length * 0.85, 0, 1, 0.05, g_magnitude, 'magnitude', handle_slider_update)
Slider(6, x, y - 300, normal_length * 0.85, 1, 5, 1, g_speed, 'speed', handle_slider_update)
Slider(7, x, y - 360, toggle_length, 0, 1, 1, g_show_path, 'show_path', handle_slider_update)
# draw sectors
def draw_sectors():
st.clear()
if g_show_sectors == 0:
return
sector_angle = 360 / g_sectors
cx, cy = g_center
for i in range(g_sectors):
st.pu()
st.goto(cx, cy)
st.seth(sector_angle * (i + 1))
st.fd(g_ww)
(x, y) = st.pos()
(nx1, ny1, nx2, ny2) = cohensutherland(g_minx, g_maxy, g_maxx, g_miny, cx, cy, x, y)
if nx1 != None:
st.goto(nx1, ny1)
st.pd()
st.goto(nx2, ny2)
st.update()
# rotate point
def rotate_point(x, y, angle):
cx, cy = g_center
x -= cx
y -= cy
sin = math.sin(math.radians(angle))
cos = math.cos(math.radians(angle))
rx = cx + (x * cos - y * sin)
ry = cy + (x * sin + y * cos)
return (rx, ry)
# angle of point wrt center
def point_angle(x, y):
cx, cy = g_center
return math.degrees(math.atan2(y - cy, x - cx))
# draw line between two points
def draw_line(t, px, py, x, y, angle):
(rpx, rpy) = rotate_point(px, py, angle)
(rx, ry) = rotate_point(x, y, angle)
if rpx < g_minx or rx < g_minx:
return
t.pu()
t.goto(rpx, rpy)
t.pd()
t.goto(rx, ry)
# draw line segment
def draw_segment(t, px, py, x, y, sectors, mirror):
sector_angle = 360 / sectors
h_sector_angle = sector_angle / 2
for i in range(sectors):
draw_line(t, px, py, x, y, sector_angle * i)
if mirror == 1:
pa = point_angle(px, py) % sector_angle
pma = h_sector_angle - pa
(rpx, rpy) = rotate_point(px, py, 2 * pma)
a = point_angle(x, y) % sector_angle
ma = h_sector_angle - a
(rx, ry) = rotate_point(x, y, 2 * ma)
if abs(pma - ma) > h_sector_angle:
continue
draw_line(t, rpx, rpy, rx, ry, sector_angle * i)
# draw points
def draw_points(points):
t.clear()
t.pensize(g_pensize)
t.color(g_color)
(px, py) = points[0]
for v in points:
x, y = v
draw_segment(t, px, py, x, y, g_sectors, g_mirror)
px, py = x, y
t.update()
if g_redraw:
break
# Perlin Walker
class PerlinWalker():
def __init__(self, x, y, steps, noise_object, magnitude, speed):
self.xv = 0
self.yv = 0
self.x = x
self.y = y
self.lx = x
self.ly = y
self.steps = steps
self.noise_object = noise_object
self.magnitude = 0.01 * magnitude
self.speed = speed
# return all points of full walk
def getPoints(self):
points = []
for i in range(self.steps):
points.append(self.update())
return points
# update each step
def update(self):
n1 = self.noise_object.noise2(self.x * self.magnitude, self.y * self.magnitude)
n2 = self.noise_object.noise2(self.x * self.magnitude, self.y * self.magnitude)
self.xv = math.cos(n1 * 2 * math.pi)
self.yv = -math.sin(n2 * 2 * math.pi)
self.x += self.xv * self.speed
self.y += self.yv * self.speed
self.lx = self.x
self.ly = self.y
return (self.x, self.y)
# draw walker path
def draw_walker_path(points):
wt.clear()
if not g_show_path:
return
wt.pensize(g_pensize + 4)
wt.pu()
for point in points:
wt.goto(point)
if not wt.isdown():
wt.pd()
wt.update()
# center points
def center_points(center, points):
cp = []
cx, cy = center
for point in points:
x, y = point
cp.append((cx + x, cy + y))
return cp
# draw
def draw():
global g_redraw
g_redraw = False
cx, cy = g_center
points = []
# sx, sy = 0, 0
sx = random.randint(0, int(g_screen_radius * 0.5))
sy = random.randint(0, int(g_screen_radius * 0.5))
g_noise_object.randomize()
walker = PerlinWalker(sx, sy, g_steps, g_noise_object, g_magnitude, g_speed)
points = walker.getPoints()
cp = center_points(g_center, points)
draw_walker_path(cp)
draw_points(cp)
# draw mandalas in loop
def draw_loop():
global g_paused
while True:
draw()
if g_pause_for_click and not g_redraw:
g_paused = True
break
if not g_redraw:
time.sleep(1)
# handle screen click
def handle_screen_click(x, y):
global g_paused
if g_pause_for_click and g_paused:
g_paused = False
draw_loop()
# setup
def setup():
draw_border()
create_ui()
draw_sectors()
g_screen.onclick(handle_screen_click)
# main
setup()
draw_loop()
# https://pardhav-m.blogspot.com/2020/08/auto-generated-mandalas.html
# https://trinket.io/embed/python/53254a5f55
# Auto Generated Mandalas
# Perlin Noise Walker - Circular
import math
import turtle
from slider import Slider
from line_clip import cohensutherland
import time
import random
from noise import SimplexNoise
# global vars
g_sectors = 16
g_pensize = 1
g_mirror = 0
g_show_sectors = 1
g_color = 'black'
g_colors = ["black", "dark grey", "cornflower blue", "deep sky blue", "dark turquoise", "aquamarine", "medium sea green", "khaki", "sienna", "firebrick", "coral", "red", "crimson", "hot pink", "dark orchid", "medium slate blue"]
g_pause_for_click = False
# screen setup
g_screen = turtle.Screen()
g_wh = int(g_screen.window_height())
g_ww = int(1.5 * g_wh)
g_screen_radius = g_wh / 2
g_screen.setup(g_ww, g_wh)
g_screen.tracer(0)
g_center = [g_ww / 6, 0]
g_minx = -g_ww / 6
g_maxx = g_ww / 2
g_miny = -g_wh / 2
g_maxy = g_wh / 2
# perlin walker - circular - vars
g_noise_object = SimplexNoise()
g_step_angle = 5
g_magnitude = 0.8
g_frequency = 0.5
g_radius_max = int(g_screen_radius / 1.5)
g_radius = int(g_radius_max / 1.25)
g_show_path = 1
# internal
g_prev_ct = None
g_redraw = False
g_paused = False
# new turtle
def new_turtle():
t = turtle.Turtle()
t.ht()
t.speed(0)
return t
# sector turtle
st = new_turtle()
st.color('lightgray')
# walker turtle
wt = new_turtle()
wt.color('red')
# main drawing turtle
t = new_turtle()
# draw a border
def draw_border():
t = new_turtle()
t.pu()
t.goto(-g_ww/2, g_miny)
t.pd()
t.goto(g_ww/2, g_miny)
t.goto(g_ww/2, g_maxy)
t.goto(-g_ww/2, g_maxy)
t.goto(-g_ww/2, g_miny)
# partition
t.color('gray')
t.pu()
t.goto(g_minx, g_miny)
t.pd()
t.goto(g_minx, g_maxy)
t.update()
# handle slider updates
def handle_slider_update(id, value):
global g_sectors
global g_pensize
global g_mirror
global g_show_sectors
global g_step_angle
global g_magnitude
global g_frequency
global g_radius
global g_show_path
global g_redraw
if id == 0:
g_sectors = value
draw_sectors()
elif id == 1:
g_pensize = value
elif id == 2:
g_mirror = value
elif id == 3:
g_show_sectors = value
draw_sectors()
elif id == 4:
g_step_angle = value
elif id == 5:
g_magnitude = value
elif id == 6:
g_frequency = value
elif id == 7:
g_radius = value
elif id == 8:
g_show_path = value
g_redraw = True
# Color button class
class ColorButton(turtle.Turtle):
def __init__(self, x, y, color, click_callback = None):
turtle.Turtle.__init__(self)
self.shape('circle')
self.speed(0)
self.my_color = color
self.click_callback = click_callback
self.color(color)
self.pu()
self.goto(x, y)
self.left(90)
self.onclick(self.onclick_handler)
self.update()
# handle button click
def onclick_handler(self, x, y):
if self.click_callback:
self.click_callback(self, self.my_color)
self.shape('turtle')
self.update()
# color button click handler
def handle_color_click(ct, color):
global g_color
global g_prev_ct
global g_redraw
g_prev_ct.shape('circle')
g_prev_ct.update()
g_color = color
g_prev_ct = ct
g_redraw = True
# create UI
def create_ui():
global g_prev_ct
x = -g_ww / 2 + 20
y = g_wh / 2 - 20
normal_length = g_screen_radius * 0.55
toggle_length = g_screen_radius * 0.15
# sliders
Slider(0, x, y, normal_length, 1, 32, 1, g_sectors, 'sectors', handle_slider_update)
Slider(1, x, y - 30, normal_length, 1, 16, 1, g_pensize, 'pensize', handle_slider_update)
Slider(2, x, y - 60, toggle_length, 0, 1, 1, g_mirror, 'mirror', handle_slider_update)
Slider(3, x, y - 90, toggle_length, 0, 1, 1, g_show_sectors, 'show_sectors', handle_slider_update)
# color buttons
ci = 0
for r in range(2):
cx = x
cy = y - (150 + (30 * r))
for c in range(8):
color = g_colors[ci]
cb = ColorButton(cx + (c * 25), cy, color, handle_color_click)
if color == g_color:
cb.shape('turtle')
g_prev_ct = cb
ci += 1
Slider(4, x, y - 240, normal_length * 0.85, 1, 20, 1, g_step_angle, 'step_angle', handle_slider_update)
Slider(5, x, y - 270, normal_length * 0.85, 0, 1, 0.05, g_magnitude, 'magnitude', handle_slider_update)
Slider(6, x, y - 300, normal_length * 0.85, 0, 1, 0.05, g_frequency, 'frequency', handle_slider_update)
Slider(7, x, y - 330, normal_length * 0.85, 10, g_radius_max, 10, g_radius, 'radius', handle_slider_update)
Slider(8, x, y - 390, toggle_length, 0, 1, 1, g_show_path, 'show_path', handle_slider_update)
# draw sectors
def draw_sectors():
st.clear()
if g_show_sectors == 0:
return
sector_angle = 360 / g_sectors
cx, cy = g_center
for i in range(g_sectors):
st.pu()
st.goto(cx, cy)
st.seth(sector_angle * (i + 1))
st.fd(g_ww)
(x, y) = st.pos()
(nx1, ny1, nx2, ny2) = cohensutherland(g_minx, g_maxy, g_maxx, g_miny, cx, cy, x, y)
if nx1 != None:
st.goto(nx1, ny1)
st.pd()
st.goto(nx2, ny2)
st.update()
# rotate point
def rotate_point(x, y, angle):
cx, cy = g_center
x -= cx
y -= cy
sin = math.sin(math.radians(angle))
cos = math.cos(math.radians(angle))
rx = cx + (x * cos - y * sin)
ry = cy + (x * sin + y * cos)
return (rx, ry)
# angle of point wrt center
def point_angle(x, y):
cx, cy = g_center
return math.degrees(math.atan2(y - cy, x - cx))
# draw line between two points
def draw_line(t, px, py, x, y, angle):
(rpx, rpy) = rotate_point(px, py, angle)
(rx, ry) = rotate_point(x, y, angle)
if rpx < g_minx or rx < g_minx:
return
t.pu()
t.goto(rpx, rpy)
t.pd()
t.goto(rx, ry)
# draw line segment
def draw_segment(t, px, py, x, y, sectors, mirror):
sector_angle = 360 / sectors
h_sector_angle = sector_angle / 2
for i in range(sectors):
draw_line(t, px, py, x, y, sector_angle * i)
if mirror == 1:
pa = point_angle(px, py) % sector_angle
pma = h_sector_angle - pa
(rpx, rpy) = rotate_point(px, py, 2 * pma)
a = point_angle(x, y) % sector_angle
ma = h_sector_angle - a
(rx, ry) = rotate_point(x, y, 2 * ma)
if abs(pma - ma) > h_sector_angle:
continue
draw_line(t, rpx, rpy, rx, ry, sector_angle * i)
# draw points
def draw_points(points):
t.clear()
t.pensize(g_pensize)
t.color(g_color)
(px, py) = points[0]
for v in points:
x, y = v
draw_segment(t, px, py, x, y, g_sectors, g_mirror)
px, py = x, y
t.update()
if g_redraw:
break
# draw walker path
def draw_walker_path(points):
wt.clear()
if not g_show_path:
return
wt.pensize(g_pensize + 4)
wt.pu()
for point in points:
wt.goto(point)
if not wt.isdown():
wt.pd()
wt.update()
# draw
def draw():
global g_redraw
g_redraw = False
cx, cy = g_center
t = 0
points = []
g_noise_object.randomize()
for a in range(0, 360 + g_step_angle, g_step_angle):
noise = g_noise_object.noise2(t, 0.1)
xradius = g_radius
yradius = g_radius * (1 - (noise * g_magnitude))
x = cx + (xradius * math.cos(math.radians(a)))
y = cy + (yradius * math.sin(math.radians(a)))
points.append((x, y))
t += 0.01 * (10 * g_frequency)
draw_walker_path(points)
draw_points(points)
# draw mandalas in loop
def draw_loop():
global g_paused
while True:
draw()
if g_pause_for_click and not g_redraw:
g_paused = True
break
if not g_redraw:
time.sleep(1)
# handle screen click
def handle_screen_click(x, y):
global g_paused
if g_pause_for_click and g_paused:
g_paused = False
draw_loop()
# setup
def setup():
draw_border()
create_ui()
draw_sectors()
g_screen.onclick(handle_screen_click)
# main
setup()
draw_loop()
import turtle
# register square thumb shape
thumb_size = 7
screen = turtle.Screen()
screen.register_shape('thumb', ((-thumb_size, -thumb_size), (thumb_size, -thumb_size), (thumb_size, thumb_size), (-thumb_size, thumb_size)))
# Slider Class
class Slider(turtle.Turtle):
def __init__(self, id, x, y, length, min, max, step, initial_value, label, callback):
turtle.Turtle.__init__(self)
self.shape('thumb')
self.speed(0)
self.id = id
self.x = x
self.y = y
self.length = length
self.min = min
self.step = step
self.label = label
self.callback = callback
self.clicked = False
self.dragging = False
self.steps = (max - min) / step
# draw slider line
self.pu()
self.goto(x, y)
self.pd()
self.fd(length)
# turtle to write label text and value
self.lt = turtle.Turtle()
self.lt.speed(0)
self.lt.pu()
self.lt.goto(self.pos())
self.lt.fd(20)
self.lt.right(90)
self.lt.fd(thumb_size/2)
self.lt.ht()
# move thumb to initial position
self.bk(length)
initial_length = length * ((initial_value - min) / (max - min))
self.fd(initial_length)
self.value = initial_value
# update label
self.update_label()
# register mouse handlers
self.onclick(self.onclick_handler)
self.onrelease(self.onrelease_handler)
self.ondrag(self.ondrag_handler)
# write label text and value
def update_label(self):
self.lt.clear()
self.lt.write(self.label + ' = ' + str(self.value), font=("Arial", 10, "normal"))
self.update()
# get value based on slider position
def get_value(self, x):
unit_value = (x - self.x) / self.length
v1 = unit_value * self.steps * self.step
v1 = int(v1 / self.step) * self.step
return self.min + v1
# onclick handler
def onclick_handler(self, x, y):
self.clicked = True
# onrelease handler
def onrelease_handler(self, x, y):
self.clicked = False
# ondrag handler
def ondrag_handler(self, x, y):
if not self.clicked:
return
if self.dragging:
return
# stop drag if mouse moves away in y direction
if abs(y - self.y) > 20:
self.clicked = False
self.dragging = False
self.callback(self.id, self.value)
return
self.dragging = True
# limit drag within the slider
if x < self.x:
x = self.x
if x > self.x + self.length:
x = self.x + self.length
# move thumb to new position
self.goto(x, self.y)
new_value = self.get_value(x)
# call the callback function if value changes
if new_value != self.value:
self.value = new_value
self.update_label()
self.callback(self.id, self.value)
self.update()
self.dragging = False
# Source: https://github.com/scivision/lineclipping-python-fortran/blob/master/pylineclip/__init__.py
'''
The MIT License (MIT)
Copyright (c) 2014 Michael Hirsch
reference: http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
* The best way to Numba JIT this would probably be in the function calling this,
to include the loop itself inside the jit decoration.
'''
def cohensutherland(xmin, ymax, xmax, ymin, x1, y1, x2, y2):
"""Clips a line to a rectangular area.
This implements the Cohen-Sutherland line clipping algorithm. xmin,
ymax, xmax and ymin denote the clipping area, into which the line
defined by x1, y1 (start point) and x2, y2 (end point) will be
clipped.
If the line does not intersect with the rectangular clipping area,
four None values will be returned as tuple. Otherwise a tuple of the
clipped line points will be returned in the form (cx1, cy1, cx2, cy2).
"""
INSIDE, LEFT, RIGHT, LOWER, UPPER = 0, 1, 2, 4, 8
def _getclip(xa, ya):
# if dbglvl>1: print('point: '),; print(xa,ya)
p = INSIDE # default is inside
# consider x
if xa < xmin:
p |= LEFT
elif xa > xmax:
p |= RIGHT
# consider y
if ya < ymin:
p |= LOWER # bitwise OR
elif ya > ymax:
p |= UPPER # bitwise OR
return p
# check for trivially outside lines
k1 = _getclip(x1, y1)
k2 = _getclip(x2, y2)
# %% examine non-trivially outside points
# bitwise OR |
while (k1 | k2) != 0: # if both points are inside box (0000) , ACCEPT trivial whole line in box
# if line trivially outside window, REJECT
if (k1 & k2) != 0: # bitwise AND &
# if dbglvl>1: print(' REJECT trivially outside box')
# return nan, nan, nan, nan
return None, None, None, None
# non-trivial case, at least one point outside window
# this is not a bitwise or, it's the word "or"
opt = k1 or k2 # take first non-zero point, short circuit logic
if opt & UPPER: # these are bitwise ANDS
x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1)
y = ymax
elif opt & LOWER:
x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1)
y = ymin
elif opt & RIGHT:
y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1)
x = xmax
elif opt & LEFT:
y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1)
x = xmin
else:
raise RuntimeError('Undefined clipping state')
if opt == k1:
x1, y1 = x, y
k1 = _getclip(x1, y1)
elif opt == k2:
x2, y2 = x, y
k2 = _getclip(x2, y2)
return x1, y1, x2, y2
# Source: https://github.com/caseman/noise/blob/master/perlin.py
# Copyright (c) 2008, Casey Duncan (casey dot duncan at gmail dot com)
# see LICENSE.txt for details
"""Perlin noise -- pure python implementation"""
__version__ = '$Id: perlin.py 521 2008-12-15 03:03:52Z casey.duncan $'
from math import floor, sqrt
from random import randint
# 3D Gradient vectors
_GRAD3 = ((1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),
(1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),
(0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1),
(1,1,0),(0,-1,1),(-1,1,0),(0,-1,-1),
)
# 4D Gradient vectors
_GRAD4 = ((0,1,1,1), (0,1,1,-1), (0,1,-1,1), (0,1,-1,-1),
(0,-1,1,1), (0,-1,1,-1), (0,-1,-1,1), (0,-1,-1,-1),
(1,0,1,1), (1,0,1,-1), (1,0,-1,1), (1,0,-1,-1),
(-1,0,1,1), (-1,0,1,-1), (-1,0,-1,1), (-1,0,-1,-1),
(1,1,0,1), (1,1,0,-1), (1,-1,0,1), (1,-1,0,-1),
(-1,1,0,1), (-1,1,0,-1), (-1,-1,0,1), (-1,-1,0,-1),
(1,1,1,0), (1,1,-1,0), (1,-1,1,0), (1,-1,-1,0),
(-1,1,1,0), (-1,1,-1,0), (-1,-1,1,0), (-1,-1,-1,0))
# A lookup table to traverse the simplex around a given point in 4D.
# Details can be found where this table is used, in the 4D noise method.
_SIMPLEX = (
(0,1,2,3),(0,1,3,2),(0,0,0,0),(0,2,3,1),(0,0,0,0),(0,0,0,0),(0,0,0,0),(1,2,3,0),
(0,2,1,3),(0,0,0,0),(0,3,1,2),(0,3,2,1),(0,0,0,0),(0,0,0,0),(0,0,0,0),(1,3,2,0),
(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),
(1,2,0,3),(0,0,0,0),(1,3,0,2),(0,0,0,0),(0,0,0,0),(0,0,0,0),(2,3,0,1),(2,3,1,0),
(1,0,2,3),(1,0,3,2),(0,0,0,0),(0,0,0,0),(0,0,0,0),(2,0,3,1),(0,0,0,0),(2,1,3,0),
(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0),
(2,0,1,3),(0,0,0,0),(0,0,0,0),(0,0,0,0),(3,0,1,2),(3,0,2,1),(0,0,0,0),(3,1,2,0),
(2,1,0,3),(0,0,0,0),(0,0,0,0),(0,0,0,0),(3,1,0,2),(0,0,0,0),(3,2,0,1),(3,2,1,0))
# Simplex skew constants
_F2 = 0.5 * (sqrt(3.0) - 1.0)
_G2 = (3.0 - sqrt(3.0)) / 6.0
_F3 = 1.0 / 3.0
_G3 = 1.0 / 6.0
class BaseNoise:
"""Noise abstract base class"""
permutation = (151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,
129,22,39,253,9,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180)
period = len(permutation)
# Double permutation array so we don't need to wrap
permutation = permutation * 2
randint_function = randint
def __init__(self, period=None, permutation_table=None, randint_function=None):
"""Initialize the noise generator. With no arguments, the default
period and permutation table are used (256). The default permutation
table generates the exact same noise pattern each time.
An integer period can be specified, to generate a random permutation
table with period elements. The period determines the (integer)
interval that the noise repeats, which is useful for creating tiled
textures. period should be a power-of-two, though this is not
enforced. Note that the speed of the noise algorithm is indpendent of
the period size, though larger periods mean a larger table, which
consume more memory.
A permutation table consisting of an iterable sequence of whole
numbers can be specified directly. This should have a power-of-two
length. Typical permutation tables are a sequnce of unique integers in
the range [0,period) in random order, though other arrangements could
prove useful, they will not be "pure" simplex noise. The largest
element in the sequence must be no larger than period-1.
period and permutation_table may not be specified together.
A substitute for the method random.randint(a, b) can be chosen. The
method must take two integer parameters a and b and return an integer N
such that a <= N <= b.
"""
if randint_function is not None: # do this before calling randomize()
if not hasattr(randint_function, '__call__'):
raise TypeError(
'randint_function has to be a function')
self.randint_function = randint_function
if period is None:
period = self.period # enforce actually calling randomize()
if period is not None and permutation_table is not None:
raise ValueError(
'Can specify either period or permutation_table, not both')
if period is not None:
self.randomize(period)
elif permutation_table is not None:
self.permutation = tuple(permutation_table) * 2
self.period = len(permutation_table)
def randomize(self, period=None):
"""Randomize the permutation table used by the noise functions. This
makes them generate a different noise pattern for the same inputs.
"""
if period is not None:
self.period = period
perm = list(range(self.period))
perm_right = self.period - 1
for i in list(perm):
#j = self.randint_function(0, perm_right)
j = randint(0, perm_right)
perm[i], perm[j] = perm[j], perm[i]
self.permutation = tuple(perm) * 2
class SimplexNoise(BaseNoise):
"""Perlin simplex noise generator
Adapted from Stefan Gustavson's Java implementation described here:
http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
To summarize:
"In 2001, Ken Perlin presented 'simplex noise', a replacement for his classic
noise algorithm. Classic 'Perlin noise' won him an academy award and has
become an ubiquitous procedural primitive for computer graphics over the
years, but in hindsight it has quite a few limitations. Ken Perlin himself
designed simplex noise specifically to overcome those limitations, and he
spent a lot of good thinking on it. Therefore, it is a better idea than his
original algorithm. A few of the more prominent advantages are:
* Simplex noise has a lower computational complexity and requires fewer
multiplications.
* Simplex noise scales to higher dimensions (4D, 5D and up) with much less
computational cost, the complexity is O(N) for N dimensions instead of
the O(2^N) of classic Noise.
* Simplex noise has no noticeable directional artifacts. Simplex noise has
a well-defined and continuous gradient everywhere that can be computed
quite cheaply.
* Simplex noise is easy to implement in hardware."
"""
def noise2(self, x, y):
"""2D Perlin simplex noise.
Return a floating point value from -1 to 1 for the given x, y coordinate.
The same value is always returned for a given x, y pair unless the
permutation table changes (see randomize above).
"""
# Skew input space to determine which simplex (triangle) we are in
s = (x + y) * _F2
i = floor(x + s)
j = floor(y + s)
t = (i + j) * _G2
x0 = x - (i - t) # "Unskewed" distances from cell origin
y0 = y - (j - t)
if x0 > y0:
i1 = 1; j1 = 0 # Lower triangle, XY order: (0,0)->(1,0)->(1,1)
else:
i1 = 0; j1 = 1 # Upper triangle, YX order: (0,0)->(0,1)->(1,1)
x1 = x0 - i1 + _G2 # Offsets for middle corner in (x,y) unskewed coords
y1 = y0 - j1 + _G2
x2 = x0 + _G2 * 2.0 - 1.0 # Offsets for last corner in (x,y) unskewed coords
y2 = y0 + _G2 * 2.0 - 1.0
# Determine hashed gradient indices of the three simplex corners
perm = self.permutation
ii = int(i) % self.period
jj = int(j) % self.period
gi0 = perm[ii + perm[jj]] % 12
gi1 = perm[ii + i1 + perm[jj + j1]] % 12
gi2 = perm[ii + 1 + perm[jj + 1]] % 12
# Calculate the contribution from the three corners
tt = 0.5 - x0**2 - y0**2
if tt > 0:
g = _GRAD3[gi0]
noise = tt**4 * (g[0] * x0 + g[1] * y0)
else:
noise = 0.0
tt = 0.5 - x1**2 - y1**2
if tt > 0:
g = _GRAD3[gi1]
noise += tt**4 * (g[0] * x1 + g[1] * y1)
tt = 0.5 - x2**2 - y2**2
if tt > 0:
g = _GRAD3[gi2]
noise += tt**4 * (g[0] * x2 + g[1] * y2)
return noise * 70.0 # scale noise to [-1, 1]
def lerp(t, a, b):
return a + t * (b - a)
def grad3(hash, x, y, z):
g = _GRAD3[hash % 16]
return x*g[0] + y*g[1] + z*g[2]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment