Created
April 30, 2025 17:26
-
-
Save stephencarr/d790b1575878b84e87bc85c04815bd58 to your computer and use it in GitHub Desktop.
Processing Maze Sketch
This file contains hidden or 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 processing.svg.*; | |
import controlP5.*; | |
ControlP5 cp5; | |
int seed = 12345; | |
int output_width = 800; | |
int output_height = 800; | |
int grid_cell_size = 20; | |
float path_wobbliness = 0.0; | |
int max_segment_length = 100; | |
int path_attempts = 1000; | |
float corner_radius = 5; | |
float curve_smoothness = 0.5; | |
float stroke_weight = 1.0; | |
boolean useRoundedCorners = true; | |
boolean useMulticolor = true; | |
boolean showGrid = false; | |
int maxColors = 10; // Default to 10 colors | |
color[] colorPalette; // Array to store the color palette | |
boolean useCustomPalette = true; // Set to true to use the custom color palette below | |
// Custom color palette definition - edit these values to change your colors | |
// Colors are defined in HSB format (Hue: 0-360, Saturation: 0-100, Brightness: 0-100) | |
// You can add or remove colors from this array | |
color[] customPalette = { | |
color(191, 0, 0), // Red | |
color(255, 90, 80), // Orange | |
color(255, 232, 0), // Yellow | |
color(79, 232, 0), // Green | |
color(79, 232, 239), // Cyan | |
color(49, 69, 239), // Blue | |
color(185, 69, 239), // Purple | |
color(253, 96, 239), // Pink | |
color(222, 60, 158), // Magenta | |
color(217, 60, 59) // Orange-red | |
}; | |
String output_filename = "maze_paths.svg"; | |
int cols, rows; | |
boolean[][] visited; | |
ArrayList<Path> paths = new ArrayList<Path>(); | |
void settings() { | |
size(output_width, output_height); | |
} | |
void setup() { | |
surface.setTitle("Generative Maze Plotter"); | |
colorMode(HSB, 360, 100, 100); | |
background(0, 0, 100); | |
strokeWeight(stroke_weight); | |
noFill(); | |
setupGUI(); | |
generatePaths(); | |
} | |
void draw() { | |
background(0, 0, 100); | |
strokeWeight(stroke_weight); | |
noFill(); | |
if (showGrid) drawGrid(); | |
for (Path p : paths) { | |
if (useRoundedCorners) { | |
drawRoundedPath(p.points, corner_radius, p.col); | |
} else { | |
drawSmoothPath(p.points, curve_smoothness, p.col); | |
} | |
} | |
drawGUIPanel(); | |
} | |
// === GUI === | |
void setupGUI() { | |
cp5 = new ControlP5(this); | |
int x = 20; | |
int y = 20; | |
int w = 200; | |
int h = 15; | |
int spacing = 30; | |
cp5.addTextlabel("label_actions") | |
.setText("▶ Actions") | |
.setPosition(x, y - 15) | |
.setColorValue(color(0)); | |
cp5.addButton("regenerate") | |
.setPosition(x, y) | |
.setSize(100, 20) | |
.setLabel("Regenerate"); | |
cp5.addButton("exportSVG") | |
.setPosition(x + 110, y) | |
.setSize(100, 20) | |
.setLabel("Export SVG"); | |
y += spacing + 10; | |
cp5.addTextlabel("label_generation") | |
.setText("⚙ Generation Settings") | |
.setPosition(x, y - 20) | |
.setColorValue(color(0)); | |
addLabeledSlider("seed", "Seed", x, y, w, h, 0, 100000, seed); y += spacing; | |
addLabeledSlider("grid_cell_size", "Grid Cell Size", x, y, w, h, 5, 100, grid_cell_size); y += spacing; | |
addLabeledSlider("path_wobbliness", "Path Wobbliness", x, y, w, h, 0, 0.5, path_wobbliness); y += spacing; | |
addLabeledSlider("max_segment_length", "Max Segment Length", x, y, w, h, 10, 500, max_segment_length); y += spacing; | |
addLabeledSlider("path_attempts", "Path Attempts", x, y, w, h, 10, 5000, path_attempts); y += spacing; | |
cp5.addTextlabel("label_appearance") | |
.setText("🎨 Appearance") | |
.setPosition(x, y - 10) | |
.setColorValue(color(0)); | |
y += 10; | |
addLabeledSlider("corner_radius", "Corner Radius", x, y, w, h, 0, 50, corner_radius); y += spacing; | |
addLabeledSlider("curve_smoothness", "Curve Smoothness", x, y, w, h, -5, 5, curve_smoothness); y += spacing; | |
addLabeledSlider("stroke_weight", "Stroke Weight", x, y, w, h, 0.1, 5, stroke_weight); y += spacing; | |
addLabeledSlider("maxColors", "Max Colors", x, y, w, h, 1, 20, maxColors); y += spacing; | |
cp5.addTextlabel("label_toggles") | |
.setText("🔘 Toggles") | |
.setPosition(x, y - 10) | |
.setColorValue(color(0)); | |
y += 10; | |
addLabeledToggle("useRoundedCorners", "Use Rounded Corners", x, y); y += spacing; | |
addLabeledToggle("useMulticolor", "Use Multicolor Paths", x, y); y += spacing; | |
addLabeledToggle("showGrid", "Show Grid", x, y); y += spacing; | |
} | |
void addLabeledSlider(String name, String label, int x, int y, int w, int h, float min, float max, float val) { | |
cp5.addSlider(name) | |
.setPosition(x, y) | |
.setSize(w, h) | |
.setRange(min, max) | |
.setValue(val) | |
.setLabel(label) | |
.getCaptionLabel() | |
.align(ControlP5.LEFT, ControlP5.TOP_OUTSIDE) | |
.setColor(color(0)); | |
} | |
void addLabeledToggle(String name, String label, int x, int y) { | |
cp5.addToggle(name) | |
.setPosition(x, y) | |
.setSize(20, 20) | |
.setValue(false) | |
.setLabel(label) | |
.getCaptionLabel() | |
.align(ControlP5.RIGHT, ControlP5.TOP_OUTSIDE) | |
.setColor(color(0)); | |
} | |
void drawGUIPanel() { | |
fill(255, 230); | |
noStroke(); | |
rect(10, 10, 240, height - 20); | |
} | |
// === GUI Callbacks === | |
void regenerate() { | |
generatePaths(); | |
} | |
void exportSVG() { | |
println("=== SVG Export Started ==="); | |
// Create a StringBuilder for building the SVG content directly | |
StringBuilder svgContent = new StringBuilder(); | |
// Add SVG header with namespace declarations | |
svgContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | |
svgContent.append("<svg xmlns=\"http://www.w3.org/2000/svg\" "); | |
svgContent.append("xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" "); | |
svgContent.append("width=\"").append(output_width).append("\" "); | |
svgContent.append("height=\"").append(output_height).append("\">\n"); | |
// Group paths by color for organizing into layers | |
HashMap<Integer, ArrayList<Path>> pathsByColor = new HashMap<Integer, ArrayList<Path>>(); | |
for (Path p : paths) { | |
if (!pathsByColor.containsKey(p.col)) { | |
pathsByColor.put(p.col, new ArrayList<Path>()); | |
} | |
pathsByColor.get(p.col).add(p); | |
} | |
println("Found " + pathsByColor.size() + " unique colors"); | |
// Now create a layer for each color | |
for (Integer colorValue : pathsByColor.keySet()) { | |
// Convert color value to RGB components - ensure correct conversion from HSB to RGB | |
colorMode(RGB, 255); // Temporarily switch to RGB mode for correct conversion | |
int r = int(red(colorValue)); | |
int g = int(green(colorValue)); | |
int b = int(blue(colorValue)); | |
colorMode(HSB, 360, 100, 100); // Switch back to HSB mode | |
String colorKey = "RGB_" + r + "_" + g + "_" + b; | |
String rgbValue = "rgb(" + r + "," + g + "," + b + ")"; | |
// Create layer | |
svgContent.append(" <g inkscape:groupmode=\"layer\" "); | |
svgContent.append("inkscape:label=\"Color_").append(colorKey).append("\" "); | |
svgContent.append("id=\"layer_").append(colorKey).append("\">\n"); | |
// Add paths for this color | |
svgContent.append(" <g style=\"stroke:").append(rgbValue).append("; "); | |
svgContent.append("stroke-width:").append(stroke_weight).append("; "); | |
svgContent.append("fill:none; stroke-linecap:round;\">\n"); | |
ArrayList<Path> colorPaths = pathsByColor.get(colorValue); | |
for (Path p : colorPaths) { | |
// For each path, create an SVG path element | |
if (p.points.size() < 2) continue; | |
svgContent.append(" <path d=\""); | |
// Generate the path data | |
if (useRoundedCorners) { | |
// Use rounded corners with quadratic curves | |
addRoundedPathToSVG(svgContent, p.points, corner_radius); | |
} else { | |
// Use smooth paths with straight lines | |
PVector firstPoint = p.points.get(0); | |
svgContent.append("M ").append(firstPoint.x).append(" ").append(firstPoint.y).append(" "); | |
for (int i = 1; i < p.points.size(); i++) { | |
PVector p1 = p.points.get(i); | |
svgContent.append("L ").append(p1.x).append(" ").append(p1.y).append(" "); | |
} | |
} | |
svgContent.append("\" />\n"); | |
} | |
// Close the group tags | |
svgContent.append(" </g>\n"); | |
svgContent.append(" </g>\n"); | |
println("Added " + colorPaths.size() + " paths to layer Color_" + colorKey); | |
} | |
// Close the SVG | |
svgContent.append("</svg>"); | |
// Write the SVG file | |
saveStrings(output_filename, new String[] { svgContent.toString() }); | |
println("SVG exported with " + pathsByColor.size() + " color layers to: " + output_filename); | |
println("=== SVG Export Complete ==="); | |
} | |
// Helper function to add a rounded path to SVG using quadratic curves | |
void addRoundedPathToSVG(StringBuilder sb, ArrayList<PVector> pts, float radius) { | |
if (pts.size() < 2) return; | |
// Start the path at the first point | |
PVector first = pts.get(0); | |
sb.append("M ").append(first.x).append(" ").append(first.y).append(" "); | |
for (int i = 1; i < pts.size(); i++) { | |
PVector p0 = pts.get(i - 1); | |
PVector p1 = pts.get(i); | |
if (i < pts.size() - 1) { | |
// For points in the middle, calculate the rounded corner | |
PVector p2 = pts.get(i + 1); | |
// Get direction vectors | |
PVector v1 = PVector.sub(p1, p0).normalize(); | |
PVector v2 = PVector.sub(p2, p1).normalize(); | |
// Calculate distance for control points | |
float dist = min(radius, PVector.dist(p1, p0) / 2, PVector.dist(p1, p2) / 2); | |
// Calculate control points | |
PVector pA = PVector.sub(p1, PVector.mult(v1, dist)); | |
PVector pB = PVector.add(p1, PVector.mult(v2, dist)); | |
// Add line to approach point | |
sb.append("L ").append(pA.x).append(" ").append(pA.y).append(" "); | |
// Add quadratic curve around corner | |
sb.append("Q ").append(p1.x).append(" ").append(p1.y).append(" ") | |
.append(pB.x).append(" ").append(pB.y).append(" "); | |
} else { | |
// For the last point, just add a straight line | |
sb.append("L ").append(p1.x).append(" ").append(p1.y).append(" "); | |
} | |
} | |
} | |
// === Path Generation === | |
void generatePaths() { | |
randomSeed((int)seed); | |
noiseSeed((int)seed); | |
paths.clear(); | |
// Generate the color palette | |
generateColorPalette(); | |
cols = output_width / grid_cell_size; | |
rows = output_height / grid_cell_size; | |
visited = new boolean[cols][rows]; | |
for (int attempt = 0; attempt < path_attempts; attempt++) { | |
int startX = int(random(cols)); | |
int startY = int(random(rows)); | |
if (!visited[startX][startY]) { | |
generatePath(startX, startY); | |
} | |
} | |
} | |
void generatePath(int startX, int startY) { | |
ArrayList<PVector> pathPoints = new ArrayList<PVector>(); | |
int x = startX; | |
int y = startY; | |
int steps = 0; | |
while (steps < max_segment_length) { | |
if (x < 0 || x >= cols || y < 0 || y >= rows) break; | |
if (visited[x][y]) break; | |
visited[x][y] = true; | |
float px = x * grid_cell_size + grid_cell_size / 2; | |
float py = y * grid_cell_size + grid_cell_size / 2; | |
float wobble = grid_cell_size * path_wobbliness; | |
px += random(-wobble, wobble); | |
py += random(-wobble, wobble); | |
pathPoints.add(new PVector(px, py)); | |
ArrayList<int[]> neighbors = new ArrayList<int[]>(); | |
if (x > 0 && !visited[x - 1][y]) neighbors.add(new int[]{x - 1, y}); | |
if (x < cols - 1 && !visited[x + 1][y]) neighbors.add(new int[]{x + 1, y}); | |
if (y > 0 && !visited[x][y - 1]) neighbors.add(new int[]{x, y - 1}); | |
if (y < rows - 1 && !visited[x][y + 1]) neighbors.add(new int[]{x, y + 1}); | |
if (neighbors.size() == 0) break; | |
int[] next = neighbors.get(int(random(neighbors.size()))); | |
x = next[0]; | |
y = next[1]; | |
steps++; | |
} | |
if (pathPoints.size() >= 2) { | |
color c; | |
if (useMulticolor) { | |
// Use a color from our limited palette | |
c = colorPalette[int(random(maxColors))]; | |
} else { | |
c = color(0, 0, 0); // Black if not multicolor | |
} | |
paths.add(new Path(pathPoints, c)); | |
} | |
} | |
// === Drawing Functions === | |
void drawRoundedPath(ArrayList<PVector> pts, float radius, color c) { | |
drawRoundedPath(pts, radius, (PGraphics)g, c); | |
} | |
void drawRoundedPath(ArrayList<PVector> pts, float radius, PGraphics g, color c) { | |
if (pts.size() < 2) return; | |
g.stroke(c); | |
g.noFill(); | |
g.beginShape(); | |
for (int i = 0; i < pts.size(); i++) { | |
PVector p0 = (i > 0) ? pts.get(i - 1) : null; | |
PVector p1 = pts.get(i); | |
PVector p2 = (i < pts.size() - 1) ? pts.get(i + 1) : null; | |
if (p0 == null || p2 == null) { | |
g.vertex(p1.x, p1.y); | |
} else { | |
PVector v1 = PVector.sub(p1, p0).normalize(); | |
PVector v2 = PVector.sub(p2, p1).normalize(); | |
float dist = min(radius, PVector.dist(p1, p0) / 2, PVector.dist(p1, p2) / 2); | |
PVector pA = PVector.sub(p1, PVector.mult(v1, dist)); | |
PVector pB = PVector.add(p1, PVector.mult(v2, dist)); | |
if (i == 1) g.vertex(pA.x, pA.y); | |
else g.vertex(pA.x, pA.y); | |
g.quadraticVertex(p1.x, p1.y, pB.x, pB.y); | |
} | |
} | |
g.endShape(); | |
} | |
void drawSmoothPath(ArrayList<PVector> pts, float tightness, color c) { | |
drawSmoothPath(pts, tightness, (PGraphics)g, c); | |
} | |
void drawSmoothPath(ArrayList<PVector> pts, float tightness, PGraphics g, color c) { | |
if (pts.size() < 2) return; | |
g.stroke(c); | |
g.noFill(); | |
g.curveTightness(tightness); | |
g.beginShape(); | |
PVector first = pts.get(0); | |
PVector last = pts.get(pts.size() - 1); | |
g.curveVertex(first.x, first.y); | |
for (PVector p : pts) { | |
g.curveVertex(p.x, p.y); | |
} | |
g.curveVertex(last.x, last.y); | |
g.endShape(); | |
} | |
// === Grid Overlay === | |
void drawGrid() { | |
stroke(0, 0, 80); | |
strokeWeight(1); | |
for (int i = 0; i <= cols; i++) { | |
float x = i * grid_cell_size; | |
line(x, 0, x, height); | |
} | |
for (int j = 0; j <= rows; j++) { | |
float y = j * grid_cell_size; | |
line(0, y, width, y); | |
} | |
} | |
// === Path Class === | |
class Path { | |
ArrayList<PVector> points; | |
color col; | |
Path(ArrayList<PVector> pts, color c) { | |
points = pts; | |
col = c; | |
} | |
} | |
// === Color Palette Generation === | |
void generateColorPalette() { | |
colorPalette = new color[maxColors]; | |
if (useCustomPalette) { | |
// Use the custom defined palette | |
int customColors = min(maxColors, customPalette.length); | |
// Copy colors from the custom palette | |
for (int i = 0; i < customColors; i++) { | |
colorPalette[i] = customPalette[i]; | |
} | |
// If we need more colors than defined in the custom palette, generate additional ones | |
if (maxColors > customPalette.length) { | |
for (int i = customPalette.length; i < maxColors; i++) { | |
float hue = map(i, 0, maxColors, 0, 360); | |
colorPalette[i] = color(hue, 80, 80); | |
} | |
println("Note: Using " + customPalette.length + " custom colors plus " + | |
(maxColors - customPalette.length) + " auto-generated colors"); | |
} else { | |
println("Using " + customColors + " colors from custom palette"); | |
} | |
} else { | |
// Generate evenly spaced colors around the HSB color wheel | |
for (int i = 0; i < maxColors; i++) { | |
float hue = map(i, 0, maxColors, 0, 360); | |
colorPalette[i] = color(hue, 80, 80); | |
} | |
println("Generated automatic palette with " + maxColors + " colors"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment