Created
May 6, 2025 19:21
-
-
Save vikramrojo/bd9f3f98a3ff56286202fe012919f1ac to your computer and use it in GitHub Desktop.
Guilloche p5js Script
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
let primaryColorPicker, secondaryColorPicker, diagonalColorPicker, polygonColorPicker, polygon2ColorPicker; | |
let vertThicknessSlider, horizThicknessSlider, diagThicknessSlider, polyThicknessSlider, poly2ThicknessSlider; | |
let vertAmplitudeSlider, horizAmplitudeSlider; | |
let vertDensitySlider, horizDensitySlider, diagDensitySlider; | |
let dashLengthSlider, dashGapSlider, diagonalAngleSlider; | |
let polyCountSlider, polySidesSlider, polyRadiusSlider; | |
let poly2SidesSlider, poly2RadiusSlider; | |
let polyDuplicatesSlider, polySpacingSlider; | |
let poly2DuplicatesSlider, poly2SpacingSlider; | |
let offsetSlider; | |
let downloadButton; | |
let verticalToggle, horizontalToggle, diagonalToggle, polygonToggle, polygon2Toggle, polyRotateToggle; | |
let tileWidth = 500; | |
let tileHeight = 500; | |
function setup() { | |
createCanvas(tileWidth * 2, tileHeight * 2); | |
noFill(); | |
let yPos = 20; | |
let yStep = 35; // Reduced further to fit more controls | |
// Create toggles and color pickers without labels | |
verticalToggle = createCheckbox('', true); | |
verticalToggle.position(1020, yPos); | |
primaryColorPicker = createColorPicker('#FFA300'); | |
primaryColorPicker.position(1060, yPos); | |
yPos += yStep; | |
horizontalToggle = createCheckbox('', true); | |
horizontalToggle.position(1020, yPos); | |
secondaryColorPicker = createColorPicker('#06B6D4'); | |
secondaryColorPicker.position(1060, yPos); | |
yPos += yStep; | |
diagonalToggle = createCheckbox('', true); | |
diagonalToggle.position(1020, yPos); | |
diagonalColorPicker = createColorPicker('#B6E4C5'); | |
diagonalColorPicker.position(1060, yPos); | |
yPos += yStep; | |
polygonToggle = createCheckbox('', true); | |
polygonToggle.position(1020, yPos); | |
polygonColorPicker = createColorPicker('#FFEA85'); // Yellow | |
polygonColorPicker.position(1060, yPos); | |
yPos += yStep; | |
polygon2Toggle = createCheckbox('', true); | |
polygon2Toggle.position(1020, yPos); | |
polygon2ColorPicker = createColorPicker('#A5F3FC'); // Light cyan | |
polygon2ColorPicker.position(1060, yPos); | |
yPos += yStep; | |
// Line thickness controls | |
createDiv('Vertical Line Thickness:').position(1020, yPos); | |
vertThicknessSlider = createSlider(1, 10, 2, 1); | |
vertThicknessSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Horizontal Line Thickness:').position(1020, yPos); | |
horizThicknessSlider = createSlider(1, 10, 2, 1); | |
horizThicknessSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Diagonal Line Thickness:').position(1020, yPos); | |
diagThicknessSlider = createSlider(1, 10, 1, 1); | |
diagThicknessSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 1 Line Thickness:').position(1020, yPos); | |
polyThicknessSlider = createSlider(1, 10, 1, 1); | |
polyThicknessSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 2 Line Thickness:').position(1020, yPos); | |
poly2ThicknessSlider = createSlider(1, 10, 1, 1); | |
poly2ThicknessSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
// Wave and diagonal controls | |
createDiv('Vertical Amplitude:').position(1020, yPos); | |
vertAmplitudeSlider = createSlider(5, 50, 20, 1); | |
vertAmplitudeSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Horizontal Amplitude:').position(1020, yPos); | |
horizAmplitudeSlider = createSlider(5, 50, 20, 1); | |
horizAmplitudeSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Vertical Density:').position(1020, yPos); | |
vertDensitySlider = createSlider(1, 10, 3, 1); | |
vertDensitySlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Horizontal Density:').position(1020, yPos); | |
horizDensitySlider = createSlider(1, 10, 3, 1); | |
horizDensitySlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Diagonal Line Spacing:').position(1020, yPos); | |
diagDensitySlider = createSlider(5, 40, 20, 1); | |
diagDensitySlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Diagonal Angle (degrees):').position(1020, yPos); | |
diagonalAngleSlider = createSlider(0, 90, 45, 15); | |
diagonalAngleSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Dash Length:').position(1020, yPos); | |
dashLengthSlider = createSlider(5, 40, 15, 1); | |
dashLengthSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Dash Gap:').position(1020, yPos); | |
dashGapSlider = createSlider(5, 40, 10, 1); | |
dashGapSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
// Polygon count and type toggle | |
createDiv('Polygon Count per Tile:').position(1020, yPos); | |
polyCountSlider = createSlider(1, 10, 3, 1); | |
polyCountSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
// Moved Rotating Polygons toggle here | |
polyRotateToggle = createCheckbox('Rotating Polygons (vs Concentric)', false); | |
polyRotateToggle.position(1020, yPos); | |
yPos += yStep; | |
// Polygon controls - group 1 | |
createDiv('---- Polygon 1 Settings ----').position(1020, yPos); | |
yPos += yStep; | |
createDiv('Polygon 1 Sides:').position(1020, yPos); | |
polySidesSlider = createSlider(3, 12, 6, 1); | |
polySidesSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 1 Radius:').position(1020, yPos); | |
polyRadiusSlider = createSlider(10, 80, 40, 2); // Steps of 2 pixels | |
polyRadiusSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 1 Duplicates:').position(1020, yPos); | |
polyDuplicatesSlider = createSlider(1, 9, 3, 2); | |
polyDuplicatesSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 1 Spacing/Rotation:').position(1020, yPos); | |
polySpacingSlider = createSlider(2, 30, 8, 2); // Steps of 2 pixels/degrees | |
polySpacingSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
// Polygon controls - group 2 | |
createDiv('---- Polygon 2 Settings ----').position(1020, yPos); | |
yPos += yStep; | |
createDiv('Polygon 2 Sides:').position(1020, yPos); | |
poly2SidesSlider = createSlider(3, 12, 3, 1); | |
poly2SidesSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 2 Radius:').position(1020, yPos); | |
poly2RadiusSlider = createSlider(10, 80, 30, 2); // Steps of 2 pixels | |
poly2RadiusSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 2 Duplicates:').position(1020, yPos); | |
poly2DuplicatesSlider = createSlider(1, 9, 5, 2); | |
poly2DuplicatesSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
createDiv('Polygon 2 Spacing/Rotation:').position(1020, yPos); | |
poly2SpacingSlider = createSlider(2, 30, 6, 2); // Steps of 2 pixels/degrees | |
poly2SpacingSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
// General settings | |
createDiv('Pattern Offset:').position(1020, yPos); | |
offsetSlider = createSlider(0, 20, 4, 2); | |
offsetSlider.position(1020, yPos + 20); | |
yPos += yStep; | |
// Create download button | |
downloadButton = createButton('Download Single Tileable PNG (500×500)'); | |
downloadButton.position(1020, yPos + 20); | |
downloadButton.mousePressed(downloadSingleTile); | |
} | |
function draw() { | |
clear(); | |
// Get values from sliders | |
let primaryColor = primaryColorPicker.color(); | |
let secondaryColor = secondaryColorPicker.color(); | |
let diagonalColor = diagonalColorPicker.color(); | |
let polygonColor = polygonColorPicker.color(); | |
let polygon2Color = polygon2ColorPicker.color(); | |
let vertThickness = vertThicknessSlider.value(); | |
let horizThickness = horizThicknessSlider.value(); | |
let diagThickness = diagThicknessSlider.value(); | |
let polyThickness = polyThicknessSlider.value(); | |
let poly2Thickness = poly2ThicknessSlider.value(); | |
let vertAmplitude = vertAmplitudeSlider.value(); | |
let horizAmplitude = horizAmplitudeSlider.value(); | |
let vertDensity = Math.floor(vertDensitySlider.value()); | |
let horizDensity = Math.floor(horizDensitySlider.value()); | |
let diagDensity = Math.floor(diagDensitySlider.value()); | |
let dashLength = Math.floor(dashLengthSlider.value()); | |
let dashGap = Math.floor(dashGapSlider.value()); | |
let diagonalAngle = diagonalAngleSlider.value(); | |
// Polygon 1 settings | |
let polyCount = Math.floor(polyCountSlider.value()); | |
let polySides = Math.floor(polySidesSlider.value()); | |
let polyRadius = polyRadiusSlider.value(); | |
let polyDuplicates = Math.floor(polyDuplicatesSlider.value()); | |
let polySpacing = polySpacingSlider.value(); | |
// Polygon 2 settings | |
let poly2Sides = Math.floor(poly2SidesSlider.value()); | |
let poly2Radius = poly2RadiusSlider.value(); | |
let poly2Duplicates = Math.floor(poly2DuplicatesSlider.value()); | |
let poly2Spacing = poly2SpacingSlider.value(); | |
let offset = offsetSlider.value(); | |
// Get values from toggles | |
let showVertical = verticalToggle.checked(); | |
let showHorizontal = horizontalToggle.checked(); | |
let showDiagonal = diagonalToggle.checked(); | |
let showPolygon = polygonToggle.checked(); | |
let showSecondPolygon = polygon2Toggle.checked(); | |
let useRotatingPolygons = polyRotateToggle.checked(); | |
// Draw layers in bottom-to-top order | |
// Layer 1 (bottom): Polygons (either concentric or rotating based on toggle) | |
if (showPolygon) { | |
if (useRotatingPolygons) { | |
drawAlternatingPolygons( | |
polygonColor, polygon2Color, | |
polyThickness, poly2Thickness, | |
polyCount, | |
polySides, poly2Sides, | |
polyRadius, poly2Radius, | |
polyDuplicates, poly2Duplicates, | |
polySpacing, poly2Spacing, | |
true, // rotating | |
showSecondPolygon | |
); | |
} else { | |
drawAlternatingPolygons( | |
polygonColor, polygon2Color, | |
polyThickness, poly2Thickness, | |
polyCount, | |
polySides, poly2Sides, | |
polyRadius, poly2Radius, | |
polyDuplicates, poly2Duplicates, | |
polySpacing, poly2Spacing, | |
false, // concentric | |
showSecondPolygon | |
); | |
} | |
} | |
// Layer 2: Diagonal dashed lines | |
if (showDiagonal) { | |
drawDiagonalDashedLines(diagonalColor, diagThickness, diagDensity, dashLength, dashGap, diagonalAngle); | |
} | |
// Layer 3: Vertical wavy lines | |
if (showVertical) { | |
drawVerticalWavyLines(primaryColor, vertThickness, vertAmplitude, vertDensity, offset); | |
} | |
// Layer 4 (top): Horizontal wavy lines | |
if (showHorizontal) { | |
drawHorizontalWavyLines(secondaryColor, horizThickness, horizAmplitude, horizDensity, offset); | |
} | |
} | |
function drawVerticalWavyLines(color, thickness, amplitude, density, offset) { | |
stroke(color); | |
strokeWeight(thickness); | |
let numLines = 40; | |
for (let i = 0; i < numLines; i++) { | |
let x = (i * width) / numLines; | |
beginShape(); | |
for (let y = 0; y <= height; y++) { | |
let normalizedY = ((y % tileHeight) + tileHeight) % tileHeight / tileHeight; | |
let phase = TWO_PI * offset; | |
let lineIndex = Math.floor(i / 2); | |
let waveX = x + amplitude * sin( | |
TWO_PI * density * normalizedY + | |
phase * (lineIndex / (numLines/2)) | |
); | |
vertex(waveX, y); | |
} | |
endShape(); | |
} | |
} | |
function drawHorizontalWavyLines(color, thickness, amplitude, density, offset) { | |
stroke(color); | |
strokeWeight(thickness); | |
let numLines = 40; | |
for (let i = 0; i < numLines; i++) { | |
let y = (i * height) / numLines; | |
beginShape(); | |
for (let x = 0; x <= width; x++) { | |
let normalizedX = ((x % tileWidth) + tileWidth) % tileWidth / tileWidth; | |
let phase = TWO_PI * offset; | |
let lineIndex = Math.floor(i / 2); | |
let waveY = y + amplitude * sin( | |
TWO_PI * density * normalizedX + | |
phase * (lineIndex / (numLines/2)) | |
); | |
vertex(x, waveY); | |
} | |
endShape(); | |
} | |
} | |
function drawDiagonalDashedLines(color, thickness, spacing, dashLength, dashGap, angle) { | |
stroke(color); | |
strokeWeight(thickness); | |
let angleRad = radians(angle); | |
let dx = cos(angleRad); | |
let dy = sin(angleRad); | |
let perpDx = -dy; | |
let perpDy = dx; | |
let diagonal = sqrt(pow(width, 2) + pow(height, 2)); | |
let numLines = ceil(diagonal / spacing) * 2; | |
let patternLength = dashLength + dashGap; | |
let centerX = width / 2; | |
let centerY = height / 2; | |
for (let i = -numLines/2; i < numLines/2; i++) { | |
let perpOffset = i * spacing; | |
let startX = centerX + perpDx * perpOffset - dx * diagonal; | |
let startY = centerY + perpDy * perpOffset - dy * diagonal; | |
let lineLength = diagonal * 2; | |
let numPatterns = Math.max(1, Math.round(lineLength / patternLength)); | |
let adjustedPatternLength = lineLength / numPatterns; | |
let adjustedDashLength = (dashLength / patternLength) * adjustedPatternLength; | |
for (let j = 0; j < numPatterns; j++) { | |
let dashStartDist = j * adjustedPatternLength; | |
let dashEndDist = dashStartDist + adjustedDashLength; | |
let dashStartX = startX + dx * dashStartDist; | |
let dashStartY = startY + dy * dashStartDist; | |
let dashEndX = startX + dx * dashEndDist; | |
let dashEndY = startY + dy * dashEndDist; | |
if (isVisibleOnCanvas(dashStartX, dashStartY) || isVisibleOnCanvas(dashEndX, dashEndY)) { | |
line(dashStartX, dashStartY, dashEndX, dashEndY); | |
} | |
} | |
} | |
} | |
function drawAlternatingPolygons(color1, color2, thickness1, thickness2, count, | |
sides1, sides2, radius1, radius2, | |
duplicates1, duplicates2, spacing1, spacing2, | |
isRotating, useSecondType) { | |
// Calculate grid for placing polygon sets | |
let cols = count; | |
let rows = count; | |
let cellWidth = tileWidth / cols; | |
let cellHeight = tileHeight / rows; | |
// Draw alternating polygons in a grid across all tiles | |
for (let tileX = 0; tileX < 2; tileX++) { | |
for (let tileY = 0; tileY < 2; tileY++) { | |
// Calculate tile offset | |
let tileOffsetX = tileX * tileWidth; | |
let tileOffsetY = tileY * tileHeight; | |
// Draw polygon sets within this tile | |
for (let col = 0; col < cols; col++) { | |
for (let row = 0; row < rows; row++) { | |
// Calculate center of each polygon set | |
let centerX = tileOffsetX + cellWidth * (col + 0.5); | |
let centerY = tileOffsetY + cellHeight * (row + 0.5); | |
// Determine which polygon type to draw based on checkerboard pattern | |
let isType1 = (col + row) % 2 === 0; | |
if (isType1 || !useSecondType) { | |
// Draw first polygon type | |
stroke(color1); | |
strokeWeight(thickness1); | |
if (isRotating) { | |
drawRotatingSet(centerX, centerY, radius1, sides1, duplicates1, spacing1); | |
} else { | |
drawConcentricSet(centerX, centerY, radius1, sides1, duplicates1, spacing1); | |
} | |
} else { | |
// Draw second polygon type | |
stroke(color2); | |
strokeWeight(thickness2); | |
if (isRotating) { | |
drawRotatingSet(centerX, centerY, radius2, sides2, duplicates2, spacing2); | |
} else { | |
drawConcentricSet(centerX, centerY, radius2, sides2, duplicates2, spacing2); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
function drawConcentricSet(centerX, centerY, maxRadius, sides, duplicates, spacing) { | |
// Draw each concentric polygon, starting with the outermost | |
for (let i = 0; i < duplicates; i++) { | |
// Calculate radius for this polygon | |
// Start with the maximum radius and reduce by spacing for each inner polygon | |
let radius = maxRadius - (i * spacing); | |
// Only draw if radius is positive | |
if (radius > 0) { | |
// Draw a regular polygon with no rotation (0) | |
drawRegularPolygon(centerX, centerY, radius, sides, 0); | |
} | |
} | |
} | |
function drawRotatingSet(centerX, centerY, radius, sides, duplicates, rotationStep) { | |
// Draw each polygon with a different rotation | |
for (let i = 0; i < duplicates; i++) { | |
// Evenly distribute rotations | |
// Convert rotation step from degrees to radians | |
let stepRad = radians(rotationStep); | |
let rotation = i * stepRad; | |
// Draw a regular polygon with this rotation | |
drawRegularPolygon(centerX, centerY, radius, sides, rotation); | |
} | |
} | |
function drawRegularPolygon(centerX, centerY, radius, sides, rotation) { | |
beginShape(); | |
for (let i = 0; i < sides; i++) { | |
let angle = rotation + TWO_PI * i / sides; | |
let x = centerX + cos(angle) * radius; | |
let y = centerY + sin(angle) * radius; | |
vertex(x, y); | |
} | |
endShape(CLOSE); | |
} | |
function isVisibleOnCanvas(x, y) { | |
return x >= 0 && x <= width && y >= 0 && y <= height; | |
} | |
function downloadSingleTile() { | |
let singleTileImage = get(0, 0, tileWidth, tileHeight); | |
let timestamp = year() + nf(month(), 2) + nf(day(), 2) + "_" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); | |
let saveCanvas = createGraphics(tileWidth, tileHeight); | |
saveCanvas.image(singleTileImage, 0, 0); | |
saveCanvas.save('guilloche_tile_' + timestamp + '.png'); | |
saveCanvas.remove(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment