440 lines
14 KiB
JavaScript
440 lines
14 KiB
JavaScript
////////////////////////////////////////////////////////
|
|
// // //
|
|
// -~=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();
|
|
}; |