something weird looking here.: TypeError: rotatedVertices[v1] is undefined . can you do some 3dcube figurine? : 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;
});