do some template array of vertices and edges for grid. multiply that static grid array, so it can fit on whole canvas as a grid, independent of resizing and else. attach that static grid array (SGA) to the center, so it always stay at the center and only be affected by snapping or attacment functions to do new wireframe lines in that 3d matrix array, without being affected by any transformations. remove all unnecessary grid code and also, output full modified code with vmc menu to add new wireframe lines and turn that grid on/off button. also, new created lines should be counted as actual wireframe model part, not grid related, so they should always be visible.: const canvas = document.createElement(‘canvas’);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

const ctx = canvas.getContext(‘2d’);
const vmcMenu = document.getElementById(‘vmc-menu’);

const greenDotContainer = document.createElement(‘div’);
document.body.appendChild(greenDotContainer);
const vertices = [
[0, 0, 0],
[0, 1, 0],
[1, 1, 0],
[1, 0, 0],
[0, 0, 1],
[0, 1, 1],
[1, 1, 1],
[1, 0, 1],
];

const edges = [
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
];

const scale = 0.025;
const zoom = 1;
const offsetX = 0.5;
const offsetY = 0.5;

let angleX = 0;
let angleY = 0;
let angleZ = 0;
let bestIndex = -1;
let bestDistance = Infinity;
let startNewEdgeIndex = -1;
let isMouseDown = false;
let prevMousePos = null;

// Red Dot
const redDot = document.getElementById(‘red-dot’);

// Add Edge
document.getElementById(‘add-edge’).addEventListener(‘click’, () => {
if (bestIndex === -1) return;
if (startNewEdgeIndex === -1) {
startNewEdgeIndex = bestIndex;
} else {
edges.push([startNewEdgeIndex, bestIndex]);
startNewEdgeIndex = -1;
}
});

// Remove Edge
document.getElementById(‘remove-edge’).addEventListener(‘click’, () => {
if (bestIndex === -1) return;
edges.forEach((edge, index) => {
if (edge.includes(bestIndex)) {
edges.splice(index, 1);
}
});
});

function rotateX(angle) {
const c = Math.cos(angle);
const s = Math.sin(angle);
return [
[1, 0, 0],
[0, c, -s],
[0, s, c],
];
}

function rotateY(angle) {
const c = Math.cos(angle);
const s = Math.sin(angle);
return [
[c, 0, s],
[0, 1, 0],
[-s, 0, c],
];
}

function rotateZ(angle) {
const c = Math.cos(angle);
const s = Math.sin(angle);
return [
[c, -s, 0],
[s, c, 0],
[0, 0, 1],
];
}

function project(vertex, scale, offsetX, offsetY, zoom) {
const [x, y, z] = vertex;
const posX = (x - offsetX) * scale;
const posY = (y - offsetY) * scale;
const posZ = z * scale;

return [
(posX * (zoom + posZ) + canvas.width / 2),
(posY * (zoom + posZ) + canvas.height / 2),
];
}

function transform(vertex, rotationMatrix) {
const [x, y, z] = vertex;
const [rowX, rowY, rowZ] = rotationMatrix;
return [
x * rowX[0] + y * rowX[1] + z * rowX[2],
x * rowY[0] + y * rowY[1] + z * rowY[2],
x * rowZ[0] + y * rowZ[1] + z * rowZ[2],
];
}

function extraterrestrialTransformation(vertex, frequency, amplitude) {
const [x, y, z] = vertex;
const cosX = (Math.cos(x * frequency) * amplitude);
const cosY = (Math.cos(y * frequency) * amplitude);
const cosZ = (Math.cos(z * frequency) * amplitude);

return [x + cosX, y + cosY, z + cosZ];
}

function getDeviation(maxDeviation) {
const t = Date.now() / 1000;
const frequency = 100 / 50;
const amplitude = maxDeviation / 10;
const deviation = Math.sin(t * frequency) * amplitude;
return deviation.toFixed(3);
}

function render() {
ctx.fillStyle = ‘#FFF’;
ctx.fillRect(0, 0, canvas.width, canvas.height);

const rotX = rotateX(angleX);
const rotY = rotateY(angleY);
const rotZ = rotateZ(angleZ);

// Extraterrestrial transformation parameters
const frequency = 1;
const amplitude = 0.8;

const transformedVertices = vertices.map(vertex => {
const extraterrestrialVertex = extraterrestrialTransformation(vertex, frequency, amplitude);
const cx = extraterrestrialVertex[0] - offsetX;
const cy = extraterrestrialVertex[1] - offsetY;
const cz = extraterrestrialVertex[2] - offsetY;

const rotated = transform(transform(transform([cx, cy, cz], rotX), rotY), rotZ);

return [
rotated[0] + offsetX,
rotated[1] + offsetY,
rotated[2] + offsetY,
];
});

const projectedVertices = transformedVertices.map(vertex => project(vertex, canvas.height * scale, offsetX, offsetY, zoom));

ctx.lineWidth = 2;
ctx.strokeStyle = ‘hsla(’ + (angleX + offsetX + angleY + offsetY) * 55 + ‘, 100%, 30%, 0.8)’;
ctx.beginPath();

for (let edge of edges) {
const [a, b] = edge;
const [x1, y1] = projectedVertices[a];
const [x2, y2] = projectedVertices[b];

const dist = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (y2 - x1) ** 2 + (x2 - y1));
const angle = Math.atan2(y2 - y1, x2 - x1, x2 - y1, y2 - x1);

// Calculate control point for curved edge
const cpDist = 0.005 * dist;
const cpX = (x1 + x2) / 2 + cpDist * Math.cos(angle - Math.PI / 2) * getDeviation(0.2);
const cpY = (y1 + y2) / 2 + cpDist * Math.sin(angle - Math.PI / 2) * getDeviation(0.2);

ctx.moveTo(x1, y1, x2, y2);
ctx.quadraticCurveTo(cpX, cpY, x2, y2, x1, y1);
}

ctx.stroke();

canvas.addEventListener(‘mousedown’, (event) => {
isMouseDown = true;
prevMousePos = { x: event.clientX, y: event.clientY };
});

canvas.addEventListener(‘mouseup’, () => {
isMouseDown = false;
prevMousePos = null;
});

canvas.addEventListener(‘mousemove’, (event) => {
const mousePos = {
x: event.clientX - canvas.getBoundingClientRect().left,
y: event.clientY - canvas.getBoundingClientRect().top
};

bestIndex = -1;
bestDistance = Infinity;

projectedVertices.forEach((currVertex, index) => {
const distance = Math.hypot(
currVertex[0] - mousePos.x,
currVertex[1] - mousePos.y
);
if (distance < bestDistance) {
bestIndex = index;
bestDistance = distance;
}
});

if (bestDistance < 10 && bestIndex !== -1) {
vmcMenu.style.display = ‘block’;
vmcMenu.style.left = mousePos.x + ‘px’;
vmcMenu.style.top = mousePos.y + ‘px’;

document.getElementById(‘vmc-vertex-x’).value = vertices[bestIndex][0];
document.getElementById(‘vmc-vertex-y’).value = vertices[bestIndex][1];
document.getElementById(‘vmc-vertex-z’).value = vertices[bestIndex][2];

document.getElementById(‘vmc-vertex-x’).dataset.vertexIndex = bestIndex;
document.getElementById(‘vmc-vertex-y’).dataset.vertexIndex = bestIndex;
document.getElementById(‘vmc-vertex-z’).dataset.vertexIndex = bestIndex;

redDot.style.display = ‘block’;
redDot.style.left = projectedVertices[bestIndex][0] - 3 + ‘px’;
redDot.style.top = projectedVertices[bestIndex][1] - 3 + ‘px’;
} else {
vmcMenu.style.display = ‘none’;
redDot.style.display = ‘none’;
}

if (isMouseDown && prevMousePos) {
const deltaX = event.clientX - prevMousePos.x;
const deltaY = event.clientY - prevMousePos.y;

angleY += deltaX * 0.01;
angleX += deltaY * 0.01;

prevMousePos = { x: event.clientX, y: event.clientY };
}
});

function updateVertexValue(event, indexToUpdate) {
const newValue = parseFloat(event.target.value);
const vertexIndex = parseInt(event.target.dataset.vertexIndex);

if (!isNaN(newValue) && vertexIndex >= 0) {
vertices[vertexIndex][indexToUpdate] = newValue;
}
}

document.getElementById(‘vmc-vertex-x’).addEventListener(‘input’, (event) => {
updateVertexValue(event, 0);
});
document.getElementById(‘vmc-vertex-y’).addEventListener(‘input’, (event) => {
updateVertexValue(event, 1);
});
document.getElementById(‘vmc-vertex-z’).addEventListener(‘input’, (event) => {
updateVertexValue(event, 2);
});

angleX += +getDeviation(0.0005);
angleY += +getDeviation(0.0005);
angleZ += +getDeviation(0.0005);



requestAnimationFrame(render);

}


// Grid related variables
const gridSpacing = 50; // Spacing between grid lines (in px)
const gridPoints = []; // Array to store grid points
let displayGrid = true; // A flag to toggle grid display on and off

for (let i = 0; i < canvas.width; i += gridSpacing) {
for (let j = 0; j < canvas.height; j += gridSpacing) {
gridPoints.push({ x: i, y: j });
const dot = document.createElement(‘div’);
dot.className = ‘green-dot’;
dot.style.left = (i - 2) + ‘px’;
dot.style.top = (j - 2) + ‘px’;
dot.style.display = displayGrid ? ‘’ : ‘none’;
greenDotContainer.appendChild(dot);
}
}

// Renders grid lines
function renderGrid() {
if (!displayGrid) return;
ctx.strokeStyle = ‘#0F0’;
ctx.lineWidth = 1;
for (let i = 0; i <= canvas.width; i += gridSpacing) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, canvas.height);
ctx.stroke();
}
for (let i = 0; i <= canvas.height; i += gridSpacing) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(canvas.width, i);
ctx.stroke();
}
}

// Adds a button to toggle the grid display
const gridToggleButton = document.createElement(‘button’);
gridToggleButton.innerText = ‘Toggle Grid’;
vmcMenu.insertBefore(gridToggleButton, document.getElementById(‘add-edge’));

gridToggleButton.addEventListener(‘click’, () => {
displayGrid = !displayGrid;
if (displayGrid) {
greenDotContainer.style.display = ‘block’;
} else {
greenDotContainer.style.display = ‘none’;
}
});

renderGrid();
requestAnimationFrame(render);

window.addEventListener(“resize”, () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});