Skip to content

Instantly share code, notes, and snippets.

@stephencarr
Created April 30, 2025 17:26
Show Gist options
  • Save stephencarr/d790b1575878b84e87bc85c04815bd58 to your computer and use it in GitHub Desktop.
Save stephencarr/d790b1575878b84e87bc85c04815bd58 to your computer and use it in GitHub Desktop.
Processing Maze Sketch
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