Created
October 14, 2024 12:38
-
-
Save typoman/5f90d24b560b6685102313e822efc119 to your computer and use it in GitHub Desktop.
drawbot random circles on a line grid
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
import cmath | |
import random | |
def distance(p1: tuple, p2: tuple): | |
""" | |
Calculate the Euclidean distance between two points. | |
Args: | |
p1 (tuple): The first point as a tuple of (x, y) coordinates. | |
p2 (tuple): The second point as a tuple of (x, y) coordinates. | |
Returns: | |
float: The Euclidean distance between the two points. | |
""" | |
return math.hypot(p1[0] - p2[0], p1[1] - p2[1]) | |
def getAngle(p1, p2): | |
""" | |
Calculates the angle between two points in a 2D plane. | |
Args: | |
p1 (tuple): The coordinates of the first point as a tuple of two numbers. | |
p2 (tuple): The coordinates of the second point as a tuple of two numbers. | |
Returns: | |
float: The angle in radians between the two points. | |
Raises: | |
TypeError: If either p1 or p2 is not a tuple of two numbers. | |
""" | |
return math.atan2(p1[1] - p2[1], p1[0] - p2[0]) | |
def gaussian_weight(distance, sigma): | |
""" | |
Calculates the Gaussian weight for a given distance and standard deviation. | |
The gaussian_weight function controls how quickly a value fades away as you | |
move further from the center. As you move away from the center, the weight | |
value decreases, causing the value to fade in a smooth non linear rate. The | |
sigma value determines the rate of this decrease: a small sigma means a | |
sharp, narrow fade that drops off quickly, while a large sigma means a | |
softer, wider gradient that fades more gradually. | |
Args: | |
distance (float): The distance from the center (0 on x axis) | |
sigma (float): The standard deviation of the Gaussian distribution. | |
Returns: | |
float: The Gaussian weight. | |
Raises: | |
TypeError: If distance or sigma is not a number. | |
# Typical usage with positive distance and sigma | |
>>> gaussian_weight(1.0, 2.0) | |
0.8824969025845955 | |
# Zero distance | |
>>> gaussian_weight(0.0, 2.0) | |
1.0 | |
# Zero sigma, with the added small value | |
>>> gaussian_weight(1.0, 0.0) | |
0.0 | |
# Negative distance, which is mathematically valid | |
>>> gaussian_weight(-1.0, 2.0) | |
0.8824969025845955 | |
# Negative sigma, which is mathematically invalid | |
>>> gaussian_weight(1.0, -2.0) | |
0.0 | |
# Large distance | |
>>> gaussian_weight(10.0, 2.0) | |
3.726653172078671e-06 | |
# Large sigma | |
>>> gaussian_weight(1.0, 10.0) | |
0.9950124791926823 | |
""" | |
sigma = max(sigma, 1e-6) # Add a small value to sigma to avoid division by zero | |
return math.exp(-distance**2 / (2 * sigma**2)) | |
def draw_vector_spiral(gridSize, spacing, centers, powers): | |
""" | |
Draw vector spirals on the grid based on multiple center points. | |
Args: | |
gridSize (int): The size of the grid. | |
spacing (int): The spacing between grid points. | |
centers (list): A list of tuples representing the center points of the spirals. | |
""" | |
for x in range(gridSize): | |
for y in range(gridSize): | |
with savedState(): | |
current_point = (x * spacing, y * spacing) | |
distances = [distance(current_point, center) for center in centers] | |
angles = [getAngle(current_point, center) for center in centers] | |
# Calculate the weights using a Gaussian distribution | |
# min_distance = min(distances) | |
# sigma = min_distance * 1.5 # Adjust the sigma value to control the gaussian spread of the weights | |
# I remove the sigma that was based on distance and made it random taken from the arg powers. | |
# This makes it possible for a center to have much bigger influence on a large area, but | |
# if sigma is based on the closest center point (`min_distance`) then it would always be a | |
# smooth transition between the center points. Now some points have much bigger influence | |
# beyond the min_distance. Increase the `powers` value to see its effect. | |
weights = [gaussian_weight(d, p) for d, p in zip(distances, powers)] | |
# Normalize the weights | |
sum_weights = sum(weights) | |
weights = [weight / sum_weights for weight in weights] | |
# Convert the angles to complex numbers | |
complex_numbers = [cmath.exp(1j * angle) for angle in angles] | |
# Calculate the weighted average of the complex numbers | |
weighted_complex = sum(complex_number * weight for complex_number, weight in zip(complex_numbers, weights)) | |
# Calculate the angle of the weighted average complex number | |
weighted_angle = math.degrees(cmath.phase(weighted_complex)) | |
translate(current_point[0], current_point[1]) | |
rotate(weighted_angle + 90, (spacing * 0.4, 0)) | |
line((0, 0), (spacing * 0.8, 0)) | |
def generate_random_tuples(min_val, max_val, tuple_length, num_tuples): | |
""" | |
Generate a list of tuples containing random integer values. | |
Args: | |
- min_val (int): Minimum value for the random integers. | |
- max_val (int): Maximum value for the random integers. | |
- tuple_length (int): Length of each tuple. | |
- num_tuples (int): Number of tuples to generate. | |
Returns: | |
- list[tuple[int, ...]]: A list of tuples containing random integers. | |
""" | |
return [tuple(random.randint(min_val, max_val) for _ in range(tuple_length)) for _ in range(num_tuples)] | |
def random_floats(n, max_value=1): | |
floats = [random.random() * max_value for _ in range(n)] | |
return floats | |
size(600, 600) | |
fill(1) | |
rect(0, 0, 600, 600) | |
strokeWidth(3.5) | |
stroke(0, 0, 0) | |
translate(0, 5) | |
n_points = 100 | |
centers = generate_random_tuples(0, 600, 2, n_points) | |
powers = random_floats(n_points, 10) | |
draw_vector_spiral(60, 10, centers, powers) | |
# -----save----- | |
import os | |
from datetime import datetime | |
import subprocess | |
fn = os.path.split(os.path.realpath(__file__))[1] + datetime.now().strftime("%Y|%m|%d-%H%M%S") | |
datetime.now().strftime("%Y|%m|%d-%H%M%S") | |
saveImage(f"{fn}.png") | |
subprocess.call(['open', "%s.pdf" %fn]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
sample render:

edit:
This code made in drawbot explores the creation of a noise with spiral effect on a grid of objects in 2D space. Initially, I attempted to make all lines point towards a single center point, then added a 90-degree rotation to create a circular pattern. To create a more complex effect, I introduced multiple center points and used a Gaussian distribution to transition between their rotations. Although the result is visually interesting, it resembles a circle rather than a spiral. My goal remains to generate a spiral effect on a grid, similar to a wind map with arrows indicating direction, but with a more pronounced spiral pattern. Further experimentation is needed to achieve this desired outcome.