var grid, groups, shapes;
sliderColumnWidth = createSlider(8, 80, 20, 1);
sliderColumnWidth.position(10, 10);
sliderColumnWidth.size(80);
sliderColumnWidth.input(() => {startGrid(); draw()});
sliderNumberOfTypes = createSlider(2, 10, 5, 1);
sliderNumberOfTypes.position(10, 30);
sliderNumberOfTypes.size(80);
sliderNumberOfTypes.input(() => {startGrid(); draw()});
checkboxContour = createCheckbox("Draw Contour", true);
checkboxContour.position(10, 50);
checkboxContour.input(() => {draw()});
checkboxVertices = createCheckbox("Draw Vertices");
checkboxVertices.position(10, 70);
checkboxVertices.input(() => {draw()});
checkboxDebug = createCheckbox("Draw Groups");
checkboxDebug.position(10, 90);
checkboxDebug.input(() => {draw()});
var size = min(windowWidth, windowHeight);
size -= size%(size/sliderColumnWidth.value());
createCanvas(size, size);
if(checkboxDebug.checked()) {
groups.forEach(group => {
var number = group[0].number;
fill(map(number, 0, NUMBER_OF_TYPES, 50, 150));
square(g.x, g.y, GRID_SIZE);
if(checkboxContour.checked()){
strokeWeight(map(GRID_SIZE, 10, 90, 1, 3));
shapes.forEach(shape => {
fill(random(50, 255), random(50, 255), random(50, 255), 150);
shape.forEach(coord => vertex(coord.x, coord.y))
if(checkboxVertices.checked()){
var circleSize = map(GRID_SIZE, 10, 90, 5, 10);
shapes.forEach(shape => {
circle(coord.x, coord.y, circleSize);
if(checkboxDebug.checked()) {
textAlign(CENTER, CENTER);
text(g.number, g.x + GRID_SIZE/2, g.y + GRID_SIZE/2);
GRID_SIZE = width/sliderColumnWidth.value();
NUMBER_OF_TYPES = sliderNumberOfTypes.value();
for(var y = 0; y < height-2; y += GRID_SIZE) {
for(var x = 0; x < width-2; x += GRID_SIZE) {
number: floor(random(NUMBER_OF_TYPES)),
itensPerRow = sqrt(grid.length);
grid.forEach((g, index) => {
findNeighbors(g.number, index, neighbors);
groups.forEach(group => {
edges.push({x1: g.x, y1: g.y, x2: g.x + GRID_SIZE, y2: g.y});
edges.push({x1: g.x + GRID_SIZE, y1: g.y, x2: g.x + GRID_SIZE, y2: g.y + GRID_SIZE});
edges.push({x1: g.x + GRID_SIZE, y1: g.y + GRID_SIZE, x2: g.x, y2: g.y + GRID_SIZE});
edges.push({x1: g.x, y1: g.y + GRID_SIZE, x2: g.x, y2: g.y});
for(var i = edges.length-1; i >= 0; i--){
const duplicatedIndex = edges.findIndex((edge2, i2) =>
((edge.x1 === edge2.x1 && edge.y1 === edge2.y1 && edge.x2 === edge2.x2 && edge.y2 === edge2.y2) ||
(edge.x1 === edge2.x2 && edge.y1 === edge2.y2 && edge.x2 === edge2.x1 && edge.y2 === edge2.y1))
if(duplicatedIndex >= 0){
edges.splice(Math.max(i, duplicatedIndex), 1);
edges.splice(Math.min(i, duplicatedIndex), 1);
var startEdge = edges.splice(0, 1)[0];
var shape = [{x: startEdge.x1, y: startEdge.y1}];
createShapeFromEdges(shape, edges, startEdge.x2, startEdge.y2);
shapes = shapes.map(shape => simplifyPolygon(shape));
shapes = shapes.map(shape => shrinkPolygon(shape, GRID_SIZE/3));
function simplifyPolygon(polygon) {
newPolygon.push(polygon[0]);
for (let i = 1; i < polygon.length; i++) {
const prevPoint = polygon[i - 1];
const currentPoint = polygon[i];
const nextPoint = i === polygon.length - 1 ? polygon[0] : polygon[i + 1];
const angle1 = Math.atan2(currentPoint.y - prevPoint.y, currentPoint.x - prevPoint.x);
const angle2 = Math.atan2(nextPoint.y - currentPoint.y, nextPoint.x - currentPoint.x);
newPolygon.push(currentPoint);
function shrinkPolygon(polygon, distance) {
const shrinkedPolygon = [];
for (let i = 0; i < polygon.length; i++) {
const p2 = polygon[(i + 1) % polygon.length];
const p3 = polygon[(i + 2) % polygon.length];
const length1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
const nx1 = -dy1 / length1;
const ny1 = dx1 / length1;
const length2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
const nx2 = -dy2 / length2;
const ny2 = dx2 / length2;
const miterX = p2.x + nx1 + nx2;
const miterY = p2.y + ny1 + ny2;
const miterLength = distance / Math.sqrt((nx1 + nx2) ** 2 + (ny1 + ny2) ** 2);
const offsetX = miterLength * (nx1 + nx2);
const offsetY = miterLength * (ny1 + ny2);
const newX = miterX + offsetX;
const newY = miterY + offsetY;
shrinkedPolygon.push({ x: newX, y: newY });
function createShapeFromEdges(shape, edges, x, y){
for(var i = 0; i < edges.length; i++){
if(edge.x1 === x && edge.y1 === y){
shape.push({x: edge.x1, y: edge.y1});
return createShapeFromEdges(shape, edges, edge.x2, edge.y2);
}else if(edge.x2 === x && edge.y2 === y) {
shape.push({x: edge.x2, y: edge.y2});
return createShapeFromEdges(shape, edges, edge.x1, edge.y1);
function findNeighbors(number, index, neighbors){
if(index % itensPerRow != 0)
getIndex(number, index - 1, neighbors);
if(index % itensPerRow != itensPerRow-1)
getIndex(number, index + 1, neighbors);
getIndex(number, index - itensPerRow, neighbors);
getIndex(number, index + itensPerRow, neighbors);
function getIndex(number, index, neighbors){
if(index >= 0 && index < grid.length){
if(!grid[index].used && grid[index].number == number){
neighbors.push(grid[index]);
findNeighbors(number, index, neighbors)