* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  height: 100%;
  width: 100%;
}

body {
  background: radial-gradient(circle at center, #878889, #2f353f);
  display: grid;
  grid-template-rows: 1fr min-content;
  place-items: center;
}

.ui {
  padding: 1rem;
}

button {
  background: hsl(38, 1%, 95%);
  border: none;
  border-radius: 0.2rem;
  color: #2f353f;
  padding: 0.5rem;
}
View Compiled
const zinc = "#EEEBEB";
const paynes = "#29313E";
const cobaltTeal = "#38B0C1";
const peachFuzz = "#FFBE98";

const weeklyColors = [
  cobaltTeal,
  peachFuzz
];

const dailyColors = [
  "#D95179",
  "#79244E",
  "#AB9E3A",
  "#803B0C",
  "#E98065"
];

let colors = weeklyColors.concat(dailyColors);
colors = colors.map((color) => tinycolor(color).toHsl());
colors = colors.filter(color => {
  return color.h > 0 && color.s > 0.07
});
colors = colors.sort((a, b) => {
  return b.l - a.l;
});
colors = colors.map((color) => tinycolor(color).toHslString());

const modulateColor = (baseColor, hRange = 8, sRange = 8, lRange = 8) => {
  const random = (min, max) => Math.random() * (max - min) + min;
  baseColor = tinycolor(baseColor).toHsl();
  
  const h = Math.floor(baseColor.h + random(-hRange, hRange));
  const s = Math.floor(baseColor.s * 100 + random(-sRange, sRange));
  const l = Math.floor(baseColor.l * 100 + random(-lRange, lRange));

  return `hsl(${h}, ${s}%, ${l}%)`;
};

P5Capture.setDefaultOptions({
  disableUi: true,
  duration: 300,
  format: "gif",
  framerate: 60,
});

let scaleOutput = 1;
let dimensions = {
  x: 800,
  y: 800
}
let frame;
let output, canvas;
let seed;

const phi = 1.618;
const fibb = [3, 5, 8, 13, 21];

let crystalSize, gridSize, margin, numColumns, numRows, gridBox;
let rows = [];

function setup () {
  // print or save size
  output = createGraphics(dimensions.x, dimensions.y);
  canvas = createCanvas(dimensions.x, dimensions.y);
  colorMode(HSB, 360, 100, 100, 1);
  rectMode(CENTER);
  imageMode(CENTER);
  pixelDensity(1);
  noLoop();
  frame = 72 / 2;
  
  numColumns = 3;
  numRows = numColumns * 2;
  gridBox = width / numColumns;
  crystalSize = gridBox / phi;
  
  resetSketch();
  
  let div = createDiv('');
  div.class('ui');
  let resetButton = createButton("Reset");
  resetButton.parent(div);
  resetButton.mousePressed(resetSketch);
  
  // scale canvas to fit on screen
  const canvasElement = document.querySelector(".p5Canvas");
  const canvasRatio = canvasElement.height / canvasElement.width;
  const windowRatio = window.innerHeight / window.innerWidth;
  let cssHeight;
  let cssWidth;
  
  if (windowRatio < canvasRatio) {
    cssHeight = window.innerHeight;
    cssWidth = cssHeight / canvasRatio;
  } else {
    cssWidth = window.innerWidth;
    cssHeight = cssWidth * canvasRatio;
  }
  
  canvasElement.style.width = `${cssWidth * 0.85}px`;
  canvasElement.style.height = `${cssHeight * 0.85}px`;
}

const createCrystal = (oCrystal, index, gridRow) => {
  const crystal = oCrystal;
  crystal.layerConstructors = [
    {
      name: "outlineShape",
      function: (oCrystal) => outlineShape(oCrystal),
      weight: 0.3
    },
    {
      name: "steppedHexagons",
      function: (oCrystal) => steppedHexagons(oCrystal),
      weight: 0.75
    },
    {
      name: "circles",
      function: (oCrystal) => circles(oCrystal),
      weight: 0.5
    },
    {
      name: "simpleLines",
      function: (oCrystal) => simpleLines(oCrystal),
      weight: 0.3
    },
    {
      name: "centeredShape",
      function: (oCrystal) => centeredShape(oCrystal),
      weight: 0.3
    },
    {
      name: "dottedLines",
      function: (oCrystal) => dottedLines(oCrystal),
      weight: 0.3
    },
  ]

  if (crystal.x < width && crystal.y < height) {
    crystal.sides = random([6, 8, 10, 12]);
    crystal.angle = TWO_PI / crystal.sides;
    crystal.numSteps = random(fibb);
    crystal.singleStep = (crystalSize / 2) / crystal.numSteps;
    crystal.palette = [modulateColor(random(colors)), modulateColor(random(colors))];
    crystal.shapePicker = floor(random(0, 4));
    crystal.layers = [];
    
    crystal.layerConstructors.forEach(layer => {
      const picker = random();
      if (picker > layer.weight) {
        crystal.layers.push(layer);
      }
    });
  } else if (crystal.x === width && crystal.y < height) {
    const reference = gridRow[index - numColumns];
    
    crystal.sides = reference.sides;
    crystal.angle = reference.angle;
    crystal.numSteps = reference.numSteps;
    crystal.singleStep = reference.singleStep;
    crystal.palette = reference.palette;
    crystal.shapePicker = reference.shapePicker;
    crystal.layers = reference.layers;
    
  } else {
    const reference = rows[0][index];
    
    crystal.sides = reference.sides;
    crystal.angle = reference.angle;
    crystal.numSteps = reference.numSteps;
    crystal.singleStep = reference.singleStep;
    crystal.palette = reference.palette;
    crystal.shapePicker = reference.shapePicker;
    crystal.layers = reference.layers;
    
  }
  
  crystal.layers.forEach(layer => {
    layer.function(crystal);
  });
  // testLines(crystal);
}

const outlineShape = (oCrystal) => {
  const hexagonTrue = oCrystal.shapePicker < 3? true : false;
  
  noFill();
  if(oCrystal.shapePicker === 0 || oCrystal.shapePicker === 2) {
    strokeWeight(2);
  }
  stroke(oCrystal.palette[0]);
  push();
    translate(oCrystal.x, oCrystal.y);
    if(hexagonTrue) {
      hexagon(0, 0, crystalSize / 2);
    } else {
      circle(0, 0, crystalSize);
    }
  pop();
}

const steppedHexagons = (oCrystal) => {
  const centerOffset = (crystalSize / 2) * ((phi - 1) * (phi - 1));
  const singleStep = ((crystalSize / 2) - centerOffset) / oCrystal.numSteps;
  
  noFill();
  stroke(oCrystal.palette[1]);
  strokeWeight(1);
  push();
    translate(oCrystal.x, oCrystal.y);
    for (let i = 0; i < oCrystal.numSteps; i++) {
      hexagon(0, 0, centerOffset + (i * singleStep));
    }
  pop();
}

const circles = (oCrystal) => {
  const shapeSize = crystalSize / 2;
  const position = crystalSize / 2 - shapeSize / 2;
  
  noFill();
  strokeWeight(1);
  stroke(oCrystal.palette[0]);
  push();
    translate(oCrystal.x, oCrystal.y);
    for(let i = 0; i <= 6; i++) {
      circle(position, 0, shapeSize);
      rotate(PI / 3);
    }
  pop();
}

const simpleLines = (oCrystal) => {
  const start = floor(oCrystal.numSteps / 2);
  const stop = start + oCrystal.shapePicker;
  
  noFill();
  if (oCrystal.shapePicker < 3) {
    strokeWeight(2);
  } else {
    strokeWeight(1);
  }
  stroke(oCrystal.palette[1]);
  if(stop > start) {
    push();
      translate(oCrystal.x, oCrystal.y);
      for (let i = 0; i < oCrystal.sides; i++) {
        line(start * oCrystal.singleStep, 0, min(stop * oCrystal.singleStep, crystalSize / 2), 0);
        rotate(oCrystal.angle);
      }
    pop();
  }
}

const centeredShape = (oCrystal) => {
  const shapeSize = (oCrystal.singleStep * oCrystal.numSteps + oCrystal.singleStep * oCrystal.shapePicker) / phi;
  
  noStroke();
  fill(oCrystal.palette[0]);
  push();
  translate(oCrystal.x, oCrystal.y);
  switch (oCrystal.shapePicker) {
    case 0:
      square(0, 0, shapeSize);
      break;
    case 1:
      circle(0, 0, shapeSize);
      break;
    case 2:
      hexagon(0, 0, shapeSize / phi);
      break;
    case 3:
      push();
        rotate(PI / 6);
        hexagon(0, 0, shapeSize / phi);
      pop();
      break;
    default:
      hexagon(0, 0, shapeSize / phi);
  }
  pop();
}

const dottedLines = (oCrystal) => {
  noFill();
  strokeWeight(floor(2 * phi));
  stroke(oCrystal.palette[1]);
  push();
    translate(oCrystal.x, oCrystal.y);
    for(let i = 0; i <= oCrystal.sides; i++) {
      for(let x = oCrystal.singleStep; x < crystalSize / 2; x += oCrystal.singleStep) {
        point(x, 0);
      }
      rotate(oCrystal.angle);
    }
  pop();
}

const testLines = (oCrystal) => {
  noFill();
  stroke(0, 0, 70);
  strokeWeight(1);
  push();
    translate(oCrystal.x, oCrystal.y);
    circle(0, 0, crystalSize);
  for(let i = 0; i < oCrystal.sides; i++) {
    line(0, 0, 0, crystalSize / 2);
    rotate(oCrystal.angle);
  }
  pop();
}

const createRow = (y, posY) => {
  const row = [];
  let posX;
  
  if(y % 2 === 0) {
    for (let x = 0; x <= numColumns; x++) {
      posX = x * gridBox;
      
      let crystal = {};
      crystal.x = posX;
      crystal.y = posY;
      row.push(crystal);
    }
  } else {
    for (let x = 0; x <= numColumns - 1; x++) {
      posX = x * gridBox + (gridBox / 2);
      
      crystal = {};
      crystal.x = posX;
      crystal.y = posY;
      row.push(crystal);
    }
  }
  
  rows.push(row);
}

const createGrid = () => {
  for (let y = 0; y <= numRows; y++) {
    let yPos = y * (gridBox / 2);
    createRow(y, yPos);
  }
  
  rows.forEach(row => {
    row.forEach((location, index, row) => {
      createCrystal(location, index, row);
      // console.log(location);
    });
  });
}

const resetSketch = () => {
  background(zinc);
  
  seed = floor(random(99999));
  randomSeed(seed);
  console.log(seed);
  
  rows = [];
  
  createGrid();
}

function draw () {
  output.clear()
  
  output.push();
  output.scale(scaleOutput);
  
  output.pop();
  
  image(output, 0, 0);
}

function keyPressed() {
  if(key === 's') {
    const capture = P5Capture.getInstance();
    capture.start();
  }
}

// Utility functions
const randomSelectTwo = () => {
  const rand = random();
  if( rand > 0.5 ) {
    return true;
  } else {
    return false;
  }
}

const pointOnCircle = (posX, posY, radius, angle) => {
  const x = posX + radius * cos(angle);
  const y = posY + radius * sin(angle);
  return createVector(x, y);
}

const hexagon = (posX, posY, radius) => {
  const angle = TWO_PI / 6;
  beginShape();
  for(let i = 0; i < 6; i++) {
    const thisVertex = pointOnCircle(posX, posY, radius, i * angle);
    vertex(thisVertex.x, thisVertex.y);
  }
  endShape(CLOSE);
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.5.2/tinycolor.min.js
  3. https://cdn.jsdelivr.net/npm/p5.capture@1.4.1/dist/p5.capture.umd.min.js