can you do second static 2d array of vertices and edges snapable grid? for example, add the same array with vertices and edges as a grid on whole canvas, but this array of vertices and edges will be static and attached at the center of this editable cube array of vertices and edges. remove all unnecessary grid code and output full modified code with vmc menu to add new wireframe lines.: 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;
});