//////////////////////////////////////////////////////// // // // // -~=Manoylov AC=~- // Hexagon Tiling // // // // //////////////////////////////////////////////////////// // // // Controls: // // mouse // // move mouse : rotate hovered hexagon // // mouse click: generate new hexagon variant // // // // keyboard // // 1-7: switch between different hexagon patterns // // 'c': generate different colors and size // // 'h': show contours of hexagons // // any others: regenerate hexagons // //////////////////////////////////////////////////////// // // // Contacts: // // https://codepen.io/Manoylov/ // // https://www.instagram.com/manoylov_ac/ // // https://www.openprocessing.org/user/23616/ // // https://twitter.com/ManoylovAC // // https://www.facebook.com/epistolariy // // https://manoylov.tumblr.com/ // //////////////////////////////////////////////////////// // // // inspired by hexagon patterns // //////////////////////////////////////////////////////// let radius = 70; let hexagons = []; let lineWidth = radius; let colorPalette = []; let linesWidth = []; let isShowHexagonShape = !true; let patternSchema = 1; let isAnyUserAction = false; const colorSchemas = [ { lineColors : ['#000', '#003333', '#5dd38e', '#007a6c', '#002c2c'], widthFactors: [1, .9, .85, .55, .2] }, { lineColors : ['#366f69', '#49B8A9', '#e7e6c2', '#393939'], widthFactors: [1, .8, .5, .2] }, { lineColors : ['#000000', '#e8edec', '#116979', '#39b8b6', '#0e3e4b'], widthFactors: [1, .8, .5, .2] }, { lineColors : ['#000000', '#f1f1f1'], widthFactors: [1, .95] }, { lineColors : ['#03141a', '#002c2c', '#366f69'], widthFactors: [1, .9, .5] }, { lineColors : ['#292837', '#42413E', '#879366', '#C8C37B'], widthFactors: [1, .8, .5, .2] }, { lineColors : ['#F3F2DB', '#79C3A7', '#668065', '#4B3331'], widthFactors: [1, .8, .5, .2] }, { lineColors : [ '#0F4155', '#288791', '#83c8a7'], widthFactors: [1, .8, .4] }, { lineColors : ['black', '#004b51', '#bcd381', '#3f9076', '#ecf5e5', '#86bbb3', 'black'], widthFactors: [1, .96, .85, .5, .2] }, { lineColors : ['#fff', '#856c8b', '#d4ebd0', '#a4c5c6', '#ffeb99'], widthFactors: [1, .85, .5, .2] }, { lineColors : ['#000', '#f1f2f2', '#dee0e1', '#333334', '#6d6e71'], widthFactors: [1, .96, .85, .5, .2] }, { lineColors : ['#000', '#301305', '#496b4a', '#ecefe6', '#e2e2d8', '#38424e', '#000'], widthFactors: [1, .96, .85, .5, .1] }, { lineColors : ['black', '#856c8b', '#d4ebd0', '#a4c5c6', '#ffeb99'], widthFactors: [1, .96, .85, .5, .2] }, { lineColors : ['#000', '#38424e', '#dc6600', '#ecefe6', '#496b4a', '#301305'], widthFactors: [1, .9, .8, .6, .2] } ]; function setup() { createCanvas(windowWidth, windowHeight); setColorSchemaAndLinesWeight(1); generateHexagonsArray(radius, linesWidth, colorPalette, isShowHexagonShape); document.body.oncontextmenu = () => false; } function setColorSchemaAndLinesWeight(cSchema) { const colorSchema = cSchema || ~~random(colorSchemas.length); radius = ~~random(45, 80); lineWidth = radius * random (.7, 1); if (colorSchema === 3) { radius = ~~random(30, 50); lineWidth = radius * random (.7, .9); } colorPalette = colorSchemas[colorSchema].lineColors; linesWidth = colorSchemas[colorSchema].widthFactors.map(factor => lineWidth * factor); } function setSidesConnectionsOrder(segmentType = 0) { let sideNumsOrder = []; if (segmentType === 0 || segmentType === 1) { // random sideNumsOrder = shuffle([0, 1, 2, 3, 4, 5]); } else if (segmentType === 2) { // 3 arcs sideNumsOrder = [].concat(...shuffle([[0, 1], [2, 3], [4, 5]])); } else if (segmentType === 3) { // 2 arcs 1 line sideNumsOrder = [1, 2, 4, 5, 0, 3]; } else if (segmentType === 4) { // 2 arcs 1 line sideNumsOrder = [1, 2, 0, 3, 4, 5]; } else if (segmentType === 5) { // 2 arches 1 line sideNumsOrder = [].concat(...shuffle([[0, 3], [1, 5], [2, 4]])); } else if (segmentType === 6) { // 3 strait lines sideNumsOrder = [0, 3, 1, 4, 2, 5]; } else if (segmentType === 7) { // 2 arches 1 arc sideNumsOrder = [].concat(...shuffle([[0, 2], [1, 3], [4, 5]])); } return sideNumsOrder; } function generateHexagonsArray(radius, linesWidth, colorPalette, isShowHexagonShape) { const hexagonMask = Hexagon.createHexagonMask(radius); const hexagonWidth = Math.sqrt(3)/2 * radius; let bgColor = '#fff'; hexagons = []; if (random() > .3) { bgColor = colorPalette[~~random(1, colorPalette.length)]; } for (let y = hexagonWidth ; y <= height + hexagonWidth * 2; y += hexagonWidth * 2){ for (let x = radius, col = 0; x <= width + radius*2; x += radius * 1.5, ++col){ const sideNumsOrder = setSidesConnectionsOrder(patternSchema); const startAngle = radians(60 * ~~(random(6))); const targetAngle = random() < .08 ? startAngle + radians(60 * ~~(random(6))) : startAngle; const tempHexagon = new Hexagon( { x: x, y: y + (col % 2 === 0 ? hexagonWidth : 0), radius : radius, startAngle : startAngle, targetAngle : targetAngle, hexagonMask : hexagonMask, colorPalette : colorPalette, linesWidth : linesWidth, sideNumsOrder: sideNumsOrder, bgColor : bgColor, isShowShape : isShowHexagonShape } ); // tempHexagon.targetAngle = TWO_PI / 6 * floor(random(6)); hexagons.push(tempHexagon); } } } function draw() { background(colorPalette[1]); hexagons.forEach((hexagon) => { push(); translate(hexagon.xPos, hexagon.yPos); hexagon.draw(0, 0); pop(); }); if (!isAnyUserAction && frameCount % 600 === 0) { patternSchema = ~~random(6); setColorSchemaAndLinesWeight(); generateHexagonsArray(radius, linesWidth, colorPalette, isShowHexagonShape); } } function findClosestHexagon(){ let closestHexagon = hexagons[0]; let closestDistance = Infinity; hexagons.forEach((hexagon) => { let d = dist( mouseX, mouseY, hexagon.xPos - hexagon.radius, hexagon.yPos - hexagon.radius ); if (d < closestDistance) { closestDistance = d; closestHexagon = hexagon; } }); return closestHexagon; } function mousePressed() { isAnyUserAction = true; const closestHexagon = findClosestHexagon(); if (closestHexagon) { closestHexagon.drawHexagonTile(setSidesConnectionsOrder()); } } function mouseMoved() { const closestHexagon = findClosestHexagon(); if (abs(closestHexagon.targetAngle - closestHexagon.currAngle) < .2){ closestHexagon.targetAngle += TWO_PI / 6; } } function keyPressed() { isAnyUserAction = true; if (keyCode === 67) { setColorSchemaAndLinesWeight(); } else if (['0', '1', '2', '3', '4', '5', '6', '7'].includes(key)) { patternSchema = parseInt(key); } else if (key === ' ') { patternSchema = ~~random(6); } else if (key === 'h') { isShowHexagonShape = !isShowHexagonShape; } generateHexagonsArray(radius, linesWidth, colorPalette, isShowHexagonShape); } function windowResized() { resizeCanvas(windowWidth, windowHeight); generateHexagonsArray(radius, linesWidth, colorPalette, isShowHexagonShape); } /////////////////////////////////////////////////////////// class Hexagon { constructor({x, y, radius, startAngle, hexagonMask, linesWidth, colorPalette, sideNumsOrder, bgColor, targetAngle, isShowShape}) { this.xPos = x; this.yPos = y; this.width = Math.sqrt(3)/2 * radius; this.radius = radius; this.currAngle = startAngle || 0; this.targetAngle = targetAngle || startAngle || 0; this.hexagonMask = hexagonMask; this.colorPalette = colorPalette; this.sideNumsOrder = sideNumsOrder; this.bgColor = bgColor || '#fff'; this.linesWidth = linesWidth; this.isShowShape = isShowShape || false; this.graphics = createGraphics(radius*2, radius*2); this.graphics = Hexagon.drawHexagonTile(this.graphics, radius, hexagonMask, sideNumsOrder, linesWidth, colorPalette, bgColor, isShowShape); } drawHexagonTile(sideNumsOrder = this.sideNumsOrder) { Hexagon.drawHexagonTile(this.graphics, radius, this.hexagonMask, sideNumsOrder, this.linesWidth, this.colorPalette, this.bgColor, this.isShowShape); } draw(x, y) { push(); translate(x - this.radius, y - this.radius); rotate(this.currAngle); image(this.graphics, -this.radius, -this.radius); pop(); // prevention of extra calculations for rotate if(this.currAngle !== this.targetAngle) { // rotate easing this.currAngle += (this.targetAngle - this.currAngle) * 0.12; if (Math.abs(this.currAngle - this.targetAngle) < .0001) { this.currAngle = this.targetAngle; } } } } Hexagon.drawHexagonShape = (ctx, radius, {strokeColor = 'black', strokeW = 1, isFilled = false, fillColor = 'black'} = {}) => { const diameter = radius * 2; const sidesNum = 6; const angleStep = 360 / sidesNum; const centerXpos = diameter/2; const centerYpos = diameter/2; ctx.push(); ctx.beginShape(); if (isFilled) { ctx.fill(fillColor); } else { ctx.noFill(); } ctx.stroke(strokeColor); ctx.strokeWeight(strokeW); for (let a = 0; a <= 360; a += angleStep) { let x = (radius + .5) * cos(radians(a)) + centerXpos; let y = (radius + .5) * sin(radians(a)) + centerYpos; ctx.vertex(x, y); } ctx.endShape(); ctx.pop(); }; Hexagon.createHexagonMask = (radius) => { const hexagonMask = createGraphics(radius * 2, radius * 2); Hexagon.drawHexagonShape(hexagonMask, radius, {strokeColor: 'black', strokeW: 1, isFilled: true}); return hexagonMask; }; Hexagon.drawHexagonTile = (ctx, radius, maskForCtx, sideNumsOrder, linesWidth, colorPalette, bgColor = '#fff', isShowHexagonShape = false) => { ctx.drawingContext.globalCompositeOperation = 'source-over'; const angleStep = TWO_PI / 6; ctx.noFill(); ctx.strokeWeight(lineWidth); ctx.background(bgColor); for (let i = 0; i < sideNumsOrder.length; i += 2) { let firstRndSideNum = sideNumsOrder[i]; let secondRndSideNum = sideNumsOrder[i + 1]; if (firstRndSideNum > secondRndSideNum) { // a swap of values so that the second side is after the first // just to simplify future calculations [firstRndSideNum, secondRndSideNum] = [secondRndSideNum, firstRndSideNum]; } const startDrawPoint = Hexagon._getMiddlePointBetweenVertexes(secondRndSideNum, secondRndSideNum + 1, angleStep); const endDrawPoint = Hexagon._getMiddlePointBetweenVertexes(firstRndSideNum, firstRndSideNum + 1, angleStep); const diff = Math.abs(secondRndSideNum - firstRndSideNum); switch (diff) { // neighbor sides - arc or 1/3 of circle case 5: secondRndSideNum = 6; // convert 0 pos to 6 case 1: { for (let j = 0; j < linesWidth.length; j++) { Hexagon.drawArc(ctx, radius, secondRndSideNum, angleStep, linesWidth[j], colorPalette[j]); } break; } // sides through one - curve or oval segment case 4: [firstRndSideNum, secondRndSideNum] = [secondRndSideNum, firstRndSideNum]; case 2: { for (let j = 0; j < linesWidth.length; j++) { Hexagon.drawArch(ctx, radius, firstRndSideNum, angleStep, linesWidth[j], colorPalette[j]); } break; } // opposite - strait line case 3: { for (let j = 0; j < linesWidth.length; j++) { Hexagon.drawLine(ctx, startDrawPoint, endDrawPoint, linesWidth[j], colorPalette[j]); } break; } } } if (isShowHexagonShape) { Hexagon.drawHexagonShape(ctx, radius); } ctx.drawingContext.globalCompositeOperation ="destination-in"; ctx.image(maskForCtx, 0, 0); return ctx; }; Hexagon._getVertexPos = (vertexNum, angleStep) => { const vertexAngle = vertexNum * angleStep; return { x: cos(vertexAngle) * radius + radius, y: sin(vertexAngle) * radius + radius }; }; Hexagon._getMiddlePointBetweenVertexes = (vertexNum1, vertexNum2, angleStep) => { const firstVertexPos = Hexagon._getVertexPos(vertexNum1, angleStep); const nextVertexPos = Hexagon._getVertexPos(vertexNum2, angleStep); return { x: lerp(firstVertexPos.x, nextVertexPos.x, .5), y: lerp(firstVertexPos.y, nextVertexPos.y, .5) }; }; // drawing strait line for opposite sides of hexagon Hexagon.drawLine = (ctx, startDrawPoint, endDrawPoint, lineWidth, color = 'black') => { ctx.push(); ctx.stroke(color); ctx.strokeWeight(lineWidth); ctx.line(startDrawPoint.x, startDrawPoint.y, endDrawPoint.x, endDrawPoint.y); ctx.pop(); }; // drawing arc for neighbor sides of hexagon - 1/3 of circle Hexagon.drawArc = (ctx, radius, startDrawSideNum, angleStep, lineWidth, color = 'black') => { const middleVertex = Hexagon._getVertexPos(startDrawSideNum, angleStep); const startArcAngle = radians(120); ctx.push(); ctx.translate(middleVertex.x, middleVertex.y); ctx.rotate(radians(startDrawSideNum * 60)); ctx.strokeWeight(lineWidth); ctx.stroke(color); ctx.arc(0, 0, radius, radius, startArcAngle, startArcAngle + radians(120)); ctx.pop(); }; // drawing arc between sides through one - curve or oval segment Hexagon.drawArch = (ctx, radius, firstSideNum, angleStep, lineWidth, color = 'black') => { const startArcAngle = radians(240); const ellipseRadius = radius * 3; ctx.push(); ctx.stroke(color); ctx.strokeWeight(lineWidth); ctx.translate(radius, radius); ctx.rotate(radians( firstSideNum * 60)); ctx.translate(0, radius * 1.7315); ctx.arc(0, 0, ellipseRadius, ellipseRadius , startArcAngle, startArcAngle + radians(60)); ctx.pop(); };