ReferenceError: canvasOffsets is not defined error. need to make normal snapability from 3dwireframe matrix model to grid, from grid to grid, from grid to 3dwfireframe matrix model and from 3dwireframe matrix model to 3dwireframe matrix model on mouse click to start-end snap points and making new 3dwireframe matrix lines that way. grid should not interline itself on its own, only by user mouse clicks! output without html, css and arrays to fit the chat.:
<html lang=“en”>
<head>
<meta charset=“UTF-8”>
<meta name=“viewport” content=“width=device-width, initial-scale=1.0”>
<title>Visual Matrix Constructor</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
.vmc-menu {
display: none;
position: absolute;
background-color: rgba(0,0,0,0.1);
border-radius: 5px;
padding: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index:1;
}
.vmc-menu label {
display: block;
margin-bottom: 10px;
}
.red-dot {
position: absolute;
width: 5px;
height: 5px;
background-color: red;
border-radius: 50%;
pointer-events: none;
color:red;
z-index:0;

}
.green-dot {
position: absolute;
width: 5px;
height: 5px;
background-color: lime;
border-radius: 50%;
pointer-events: none;
z-index:0;
}
</style>
</head>
<body>
<div class=“vmc-menu” id=“vmc-menu”>
<label>Vertex X: <input type=“number” id=“vmc-vertex-x” step=“0.1”></label>
<label>Vertex Y: <input type=“number” id=“vmc-vertex-y” step=“0.1”></label>
<label>Vertex Z: <input type=“number” id=“vmc-vertex-z” step=“0.1”></label>
<button id=“add-edge”>Add Edge</button>
<button id=“remove-edge”>Remove Edge</button>
<button id=“toggle-grid”>Toggle Grid</button>
</div>
<div class=“red-dot” id=“red-dot”></div>
<div class=“green-dot” id=“green-dot”></div>
<script>
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 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.5;
const offsetX = 0.3;
const offsetY = 0.3;
let edgeStart = null;
let angleX = 0;
let angleY = 0;
let angleZ = 0;
let bestIndex = -1;
let bestDistance = Infinity;
let startNewEdgeIndex = -1;
let isMouseDown = false;
let prevMousePos = null;
const gridSpacing = 40;
const gridPoints = [];
let displayGrid = true;
function roundToGrid(value) {
return Math.round(value / gridSpacing) * gridSpacing;
}

function nearestGridIntersection(x, y, offsetX, offsetY) {
const xt = (x - offsetX) / zoom;
const yt = (y - offsetY - canvas.height * scale * 0.3) / zoom;

const snapX = roundToGrid(xt) * zoom + offsetX;
const snapY = roundToGrid(yt) * zoom + offsetY + canvas.height * scale * 0.3;

return {
x: snapX,
y: snapY
};
}

const redDot = document.getElementById(‘red-dot’);
const greenDotContainer = document.getElementById(‘green-dot’);


function addEdge(newConnection = false) {
if (bestIndex === -1) return;
if (startNewEdgeIndex === -1) {
if (!newConnection) return;

const snapped = snapToGrid(event.clientX, event.clientY);
const { x, y } = reverseProject([snapped.x, snapped.y], canvas.height * scale, offsetX, offsetY, zoom);
vertices.push([x, y, 0]);
startNewEdgeIndex = vertices.length - 1;
} else {
edges.push([startNewEdgeIndex, bestIndex]);
startNewEdgeIndex = -1;
}
}
function reverseProject(coordinates, scale, offsetX, offsetY, zoom) {
const [x, y] = coordinates;
return [
(x - canvas.width / 2) / (zoom * scale) + offsetX,
(y - canvas.height / 2) / (zoom * scale) + offsetY,
];
}
function drawLine(ctx, from, to) {
ctx.beginPath();
ctx.moveTo(from.x, from.y);
ctx.lineTo(to.x, to.y);
ctx.stroke();
}
document.getElementById(‘add-edge’).addEventListener(‘click’, () => {
addEdge(newConnection = true);
});
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 = 1 / 1;
const amplitude = maxDeviation / 0.1;
const deviation = Math.sin(t * frequency) * amplitude;
return deviation.toFixed(3);
}

function render() {
ctx.fillStyle = ‘#FFF’;
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (displayGrid) {
ctx.strokeStyle = ‘#0F0’;
ctx.lineWidth = 1;
gridPoints.forEach(({ x, y }) => {
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
});
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();
}
}
if (edgeStart && redDot.style.display === ‘block’) {
const edgeEnd = { x: parseFloat(redDot.style.left) + 3, y: parseFloat(redDot.style.top) + 3 };
ctx.strokeStyle = ‘#00F’;
ctx.lineWidth = 2;
drawLine(ctx, edgeStart, edgeEnd);
}
const rotX = rotateX(angleX);
const rotY = rotateY(angleY);
const rotZ = rotateZ(angleZ);

const transformedVertices = vertices.map(vertex => {
const cx = vertex[0] - offsetX;
const cy = vertex[1] - offsetY;
const cz = vertex[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);

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 };

edgeStart = redDot.style.display === ‘block’ ? { x: parseFloat(redDot.style.left) + 3, y: parseFloat(redDot.style.top) + 3 } : null;
});

canvas.addEventListener(‘mouseup’, (event) => {
if (edgeStart && redDot.style.display === ‘block’) {
const edgeEnd = { x: parseFloat(redDot.style.left) + 3, y: parseFloat(redDot.style.top) + 3 };

const startX = (edgeStart.x - canvas.width / 2) / (canvas.height * scale) + offsetX;
const startY = (edgeStart.y - canvas.height / 2) / (canvas.height * scale) + offsetY;
const startZ = 0;

const endX = (edgeEnd.x - canvas.width / 2) / (canvas.height * scale) + offsetX;
const endY = (edgeEnd.y - canvas.height / 2) / (canvas.height * scale) + offsetY;
const endZ = 0;

vertices.push([startX.toPrecision(1), startY.toPrecision(1), startZ], [endX.toPrecision(1), endY.toPrecision(1), endZ]);
edges.push([vertices.length - 2, vertices.length - 1]);
}
});

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;
}
});

const snappedPos = nearestGridIntersection(event.clientX - canvasOffsets.left, event.clientY - canvasOffsets.top, canvasOffsets.left, canvasOffsets.top);
if (bestDistance < gridSpacing / 2 && bestIndex !== -1) {
const snappedVert = snapToGrid(projectedVertices[bestIndex][0], projectedVertices[bestIndex][1]);
projectedVertices[bestIndex][0] = snappedVert.x;
projectedVertices[bestIndex][1] = snappedVert.y;

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 = snapMousePos.x - 3 + ‘px’;
redDot.style.top = snapMousePos.y - 3 + ‘px’;
} else {
vmcMenu.style.display = ‘none’;
redDot.style.display = ‘none’;
}

greenDotContainer.style.transform = “translate(” + (snapMousePos.x - 3) + "px, " + (snapMousePos.y - 3) + “px)”;

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) {
const updatedValue = indexToUpdate === 2 ? newValue : roundToGrid((newValue - 0.5) / scale) * scale + 0.5;
vertices[vertexIndex][indexToUpdate] = updatedValue;
}
}

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);




for (let i = 0; i < canvas.width; i += gridSpacing) {
for (let j = 0; j < canvas.height; j += gridSpacing) {
gridPoints.push({ x: i, y: j });
}
}

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();
}
}

const gridToggleButton = document.getElementById(‘toggle-grid’);
gridToggleButton.innerText = ‘Toggle Grid’;

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

}



requestAnimationFrame(render);

window.addEventListener(“resize”, () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
</body>
</html>