const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
// canvas.width = window.innerWidth;
// canvas.height = window.innerHeight;
const vertices = [
[-1, -1, -1],
[1, -1, -1],
[1, 1, -1],
[-1, 1, -1],
[-1, -1, 1],
[1, -1, 1],
[1, 1, 1],
[-1, 1, 1]
];
const faces = [
[0, 1, 2, 3], // back
[4, 5, 6, 7], // front
[0, 1, 5, 4], // bottom
[2, 3, 7, 6], // top
[0, 3, 7, 4], // left
[1, 2, 6, 5] // right
];
function multiplyMatrix (b, a) {
// 检查矩阵 a 和 b 的维度是否匹配
if (a[0].length !== b.length) {
throw new Error("矩阵 a 的列数必须等于矩阵 b 的行数");
}
// 初始化结果矩阵
const result = new Array(a.length); // 结果矩阵的行数等于 a 的行数
for (let i = 0; i < result.length; i++) {
result[i] = new Array(b[0].length).fill(0); // 结果矩阵的列数等于 b 的列数
}
// 矩阵乘法
for (let i = 0; i < a.length; i++) { // 遍历 a 的每一行
for (let j = 0; j < b[0].length; j++) { // 遍历 b 的每一列
let sum = 0;
for (let k = 0; k < a[0].length; k++) { // 遍历 a 的列和 b 的行
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
return result;
}
function rotateX (matrix, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const rotationX = [
[1, 0, 0],
[0, cos, -sin],
[0, sin, cos]
];
return multiplyMatrix(rotationX, matrix);
}
function rotateY (matrix, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const rotationY = [
[cos, 0, sin],
[0, 1, 0],
[-sin, 0, cos]
];
return multiplyMatrix(rotationY, matrix);
}
function rotateZ (matrix, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const rotationZ = [
[cos, -sin, 0],
[sin, cos, 0],
[0, 0, 1]
];
return multiplyMatrix(rotationZ, matrix);
}
function translate (matrix, x, y, z) {
const translation = [
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
return multiplyMatrix(translation, matrix);
}
function perspectiveProjection (matrix, fov, aspect, near, far) {
const f = 1.0 / Math.tan(fov / 2);
const rangeInv = 1.0 / (near - far);
const projection = [
[f / aspect, 0, 0, 0],
[0, f, 0, 0],
[0, 0, (near + far) * rangeInv, 2 * near * far * rangeInv],
[0, 0, -1, 0],
[0, 0, 0, -1]
];
return multiplyMatrix(projection, matrix);
}
function project (vertex) {
const fov = Math.PI / 4; // Field of view
const aspect = canvas.width / canvas.height;
const near = 0.1;
const far = 200;
let point = [...vertex, 1];
point = perspectiveProjection([point], fov, aspect, near, far)[0];
// Normalize
const w = point[3];
const x = (point[0] / w + 1) * canvas.width / 16 + canvas.width / 2;
const y = (-point[1] / w + 1) * canvas.height / 16 + canvas.height / 2;
return [x, y];
}
function draw () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Apply transformations
let transformedVertices = vertices.map(v => [...v]);
transformedVertices = transformedVertices.map(v => rotateX([v], angleX)[0]);
transformedVertices = transformedVertices.map(v => rotateY([v], angleY)[0]);
transformedVertices = transformedVertices.map(v => rotateZ([v], angleZ)[0]);
transformedVertices = transformedVertices.map(v => translate([[...v, 0]], 0, 0, 5)[0]);
// Project vertices to 2D
const projectedVertices = transformedVertices.map(v => project(v));
// Draw faces
faces.forEach(face => {
ctx.beginPath();
face.forEach((vertexIndex, i) => {
const [x, y] = projectedVertices[vertexIndex];
ctx.fillText(vertexIndex + '', x, y)
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.closePath();
ctx.stroke();
});
requestAnimationFrame(draw);
}
let angleX = 0;
let angleY = 0;
let angleZ = 0;
document.addEventListener('mousemove', (e) => {
angleX = (e.clientY / canvas.height - 0.5) * Math.PI;
angleY = (e.clientX / canvas.width - 0.5) * Math.PI;
});
draw();