make some menu at top to set grid size in units (range 1-100 with default value that stays in borders of window size) and dimension in px em and all other possible units. make that grid centered to fit on whole window and update on window resize. add some guides lines with scales steps in units at left and bottom. make some global margin, to leave some space for that grid adjustment options menus.: <!DOCTYPE html>
<html lang="en"><head>
<meta charset='UTF-8'>
<title>drawacube</title>
<style>
canvas {
display: block;
margin: auto;
}
</style>
</head>
<body>
<canvas id='canvas'></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const gridSize = 50;
const gridSpacing = 25;

let grid = [];
for (let x = -gridSize; x <= gridSize; x += gridSpacing) {
for (let y = -gridSize; y <= gridSize; y += gridSpacing) {
for (let z = -gridSize; z <= gridSize; z += gridSpacing) {
grid.push({ x, y, z });
}
}
}

let lastClickedPoint = null;
const clickedPoints = [];
let dragStart = null;

let offset = {
x: gridSpacing / 25,
y: gridSpacing / 25,
z: 0
};

function rotate(point, angleX, angleY) {
const { x, y, z } = point;

let centeredX = x - offset.x;
let centeredY = y - offset.y;
let centeredZ = z - offset.z;

const cosX = Math.cos(angleX);
const sinX = Math.sin(angleX);
const cosY = Math.cos(angleY);
const sinY = Math.sin(angleY);

const newY = centeredY * cosX - centeredZ * sinX;
const newZ = centeredY * sinX + centeredZ * cosX;
const newX = centeredX * cosY - newZ * sinY;
const newZ2 = centeredX * sinY + newZ * cosY;

return {
x: newX + offset.x,
y: newY + offset.y,
z: newZ2 + offset.z
};
}


function project(point, width, height) {
const { x, y, z } = point;

const scale = 1;
const newX = x * scale + width / 2;
const newY = y * scale + height / 2;

return { x: newX, y: newY, scale };
}

function findClosestPoint(point) {
const threshold = 20;
let minDist = Infinity;
let minIndex = -1;

for (let i = 0; i < grid.length; i++) {
const projected = project(rotate(grid[i], 0, 0), canvas.width, canvas.height);
const dx = point.x - projected.x;
const dy = point.y - projected.y;
const dist = Math.sqrt(dx * dx + dy * dy);

if (dist < minDist && dist <= threshold) {
minDist = dist;
minIndex = i;
}
}

return minIndex !== -1 ? grid[minIndex] : null;
}

let mouseDown = false;
let activeLine = null;
const lines = [];

canvas.addEventListener('mousedown', (e) => {
dragStart = { x: e.clientX, y: e.clientY };
});

canvas.addEventListener('mousemove', (e) => {
if (dragStart) {
const deltaX = e.clientX - dragStart.x;
const deltaY = e.clientY - dragStart.y;
dragStart = { x: e.clientX, y: e.clientY };
}
});

canvas.addEventListener('mouseup', (e) => {
mouseDown = false;
dragStart = null;
if (activeLine !== null) {
const canvasRect = canvas.getBoundingClientRect();
const mouseX = e.clientX - canvasRect.left;
const mouseY = e.clientY - canvasRect.top;
const endPoint = findClosestPoint({ x: mouseX, y: mouseY });

if (endPoint !== activeLine[activeLine.length - 1]) {
activeLine.push(endPoint);
lines.push(activeLine);
}
activeLine = null;
}

if (lastClickedPoint !== null) {
const canvasRect = canvas.getBoundingClientRect();
const mouseX = e.clientX - canvasRect.left;
const mouseY = e.clientY - canvasRect.top;
const clickedPoint = findClosestPoint({ x: mouseX, y: mouseY });

if (clickedPoint !== null) {
if (lastClickedPoint === clickedPoint) {
lastClickedPoint = null;
} else {
lines.push([lastClickedPoint, clickedPoint]);
clickedPoints.push(clickedPoint);
lastClickedPoint = null;
}
}
} else {
const canvasRect = canvas.getBoundingClientRect();
const mouseX = e.clientX - canvasRect.left;
const mouseY = e.clientY - canvasRect.top;
const clickedPoint = findClosestPoint({ x: mouseX, y: mouseY });

if (clickedPoint !== null) {
lastClickedPoint = clickedPoint;
clickedPoints.push(clickedPoint);
}
}
});
function drawLine(line) {
ctx.beginPath();

for (let i = 0; i < line.length - 1; i++) {
const projectedStart = project(rotate(line[i], 0, 0), canvas.width, canvas.height);
const projectedEnd = project(rotate(line[i + 1], 0, 0), canvas.width, canvas.height);

ctx.moveTo(projectedStart.x, projectedStart.y);
ctx.lineTo(projectedEnd.x, projectedEnd.y);
}

ctx.strokeStyle = 'rgba(25,200,25,0.2)';
ctx.lineWidth = 2;
ctx.stroke();
}

function calculateGridCenter(grid) {
let totalX = 0;
let totalY = 0;
let totalZ = 0;
const pointsCount = grid.length;

for (const point of grid) {
totalX += point.x;
totalY += point.y;
totalZ += point.z;
}

return {
x: totalX / pointsCount,
y: totalY / pointsCount,
z: totalZ / pointsCount,
};
}

function draw() {
ctx.fillStyle = 'rgba(1,2,1,0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);

for (const point of grid) {
const projected = project(rotate(point, 0, 0), canvas.width, canvas.height);

ctx.beginPath();
if (projected.scale > 0) {
ctx.arc(projected.x, projected.y, 2 * projected.scale, 0, Math.PI * 2);
ctx.closePath();
const gradient = ctx.createRadialGradient(projected.x, projected.y, 0, projected.x, projected.y, 2 * projected.scale);
gradient.addColorStop(0, 'rgba(255,155,255,0.5)');
gradient.addColorStop(0.5, 'rgba(55,255,25,0.8)');
gradient.addColorStop(1, 'rgba(55,25,255,0.2)');
ctx.fillStyle = gradient;
ctx.fill();
}
}

// Drawing clicked points in green
ctx.fillStyle = 'rgba(255,200,50,0.5)';
for (const clickedPoint of clickedPoints) {
const projected = project(rotate(clickedPoint, 0, 0), canvas.width, canvas.height);

ctx.beginPath();
if (projected.scale > 0) {
ctx.arc(projected.x, projected.y, 4 * projected.scale, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
}

for (const line of lines) {
drawLine(line);
}

if (activeLine !== null) {
drawLine(activeLine);
}

requestAnimationFrame(draw);
}


draw();
</script>
</body>
</html>