something weird looking here.: TypeError: rotatedVertices[v1] is undefined . try fix all code and generally improve it with already 3d cube in that array included. output full code. the purpose is to draw a matrix by lines with ability to attach and detach lines to construct the model in 3d matrix representation array visually building the model by dragging lines and atach to other lines. output full awesome code. :const canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

const ctx = canvas.getContext('2d');

let vertices = [{
x: 0.5,
y: 0.5,
z: -1,
color: "#FF0000"
},
{
x: -0.3,
y: -0.5,
z: -1,
color: "#00FF00"
},
{
x: -0.3,
y: 0.8,
z: -1,
color: "#0000FF"
},
];

let edges = [
[0, 1], // Connect vertex 0 to 1
[0, 2], // Connect vertex 0 to 2
[1, 2], // Connect vertex 1 to 2
];

let angleX = 0;
let angleY = 0;
let angleZ = 0;

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

let indexToDrag = -1;
let lineToAttach = -1;

function hover(event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;

let closestDistance = Infinity;
let closestIndex = -1;

vertices.forEach((vertex, index) => {
let [screenX, screenY] = project(vertex.x, vertex.y, vertex.z);

let distance = Math.sqrt((x - screenX) ** 2 + (y - screenY) ** 2);
if (distance < closestDistance) {
closestDistance = distance;
closestIndex = index;
}
});

if (closestDistance < 10) {
indexToDrag = closestIndex;
} else {
indexToDrag = -1;
}

// Check for line attachment
edges.forEach((edge, index) => {
const [v1, v2] = edge;
const [screenX1, screenY1] = project(vertices[v1].x, vertices[v1].y, vertices[v1].z);
const [screenX2, screenY2] = project(vertices[v2].x, vertices[v2].y, vertices[v2].z);

const d = pointToSegmentDistance(x, y, screenX1, screenY1, screenX2, screenY2);
if (d < 10) {
lineToAttach = index;
}
});
}

function drag(event) {
if (indexToDrag !== -1 && event.which === 1) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;

vertices[indexToDrag].x = x / canvas.width;
vertices[indexToDrag].y = y / canvas.height;
}
}

function release(event) {
if (lineToAttach !== -1 && event.which === 1) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;

// Check for line detachment
const [v1, v2] = edges[lineToAttach];
const [screenX1, screenY1] = project(vertices[v1].x, vertices[v1].y, vertices[v1].z);
const [screenX2, screenY2] = project(vertices[v2].x, vertices[v2].y, vertices[v2].z);

const d = pointToSegmentDistance(x, y, screenX1, screenY1, screenX2, screenY2);
if (d > 10) {
lineToAttach = -1;
return;
}

// Attach to line
const closestPoint = closestPointOnSegment(x, y, screenX1, screenY1, screenX2, screenY2);
const [cx, cy] = unproject(closestPoint[0], closestPoint[1]);
vertices.push({
x: cx,
y: cy,
z: -1,
color: "#000000"
});
edges.push([indexToDrag, vertices.length - 1]);
lineToAttach = -1;
} else {
indexToDrag = -1;
}
}

canvas.addEventListener("mousedown", hover);
canvas.addEventListener("mousemove", drag);
canvas.addEventListener("mouseup", release);

function project(x, y, z) {
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 unproject(x, y) {
const posX = ((x - canvas.width / 2) / (zoom + vertices[0].z * scale)) + offsetX;
const posY = ((y - canvas.height / 2) / (zoom + vertices[0].z * scale)) + offsetY;

return [posX / scale, posY / scale];
}

function closestPointOnSegment(px, py, x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const t = ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy);
const x = x1 + t * dx;
const y = y1 + t * dy;

return [x, y];
}

function pointToSegmentDistance(px, py, x1, y1, x2, y2) {
const closest = closestPointOnSegment(px, py, x1, y1, x2, y2);
const dx = px - closest[0];
const dy = py - closest[1];

return Math.sqrt(dx * dx + dy * dy);
}

function render() {
// Clear screen
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, canvas.width, canvas.height);

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

let rotatedVertices = vertices.map((vertex) => {
const cx = vertex.x - offsetX;
const cy = vertex.y - offsetY;
const cz = vertex.z - offsetY;

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

return {
x: rotated[0] + offsetX,
y: rotated[1] + offsetY,
z: rotated[2] + offsetY,
color: vertex.color
};
});

// Draw lines between vertices
ctx.strokeStyle = "black";
ctx.beginPath();
edges.forEach((edge) => {
const [v1, v2] = edge;
const [screenX1, screenY1] = project(rotatedVertices[v1].x, rotatedVertices[v1].y, rotatedVertices[v1].z);
const [screenX2, screenY2] = project(rotatedVertices[v2].x, rotatedVertices[v2].y, rotatedVertices[v2].z);

ctx.moveTo(screenX1, screenY1);
ctx.lineTo(screenX2, screenY2);
});
ctx.stroke();

// Draw circles on vertices
ctx.fillStyle = "red";
ctx.beginPath();
rotatedVertices.forEach((vertex) => {
ctx.fillStyle = vertex.color;
drawCircle(ctx, project(vertex.x, vertex.y, vertex.z), 5);
ctx.fill();
});

requestAnimationFrame(render);
}

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 transform(point, matrix) {
const [x, y, z] = point;
const [rowX, rowY, rowZ] = matrix;
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 drawCircle(ctx, [x, y], r) {
ctx.moveTo(x + r, y);
ctx.arc(x, y, r, 0, 2 * Math.PI);
}

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