유튜브에서본 회전 하는 3D 도넛을 구현해 보았다.
코드는 아래와 같다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3D doughnut</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<style>
canvas {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div align='center'>
<canvas id="canvas"></canvas>
</div>
<script language="JavaScript">
// Start: 2022.01.16
// Update: 2022.01.18
let cvs;
let canvas;
let bufCanvas;
let bufCtx;
let thetaX = 127;
let thetaZ = 0;
function InitCanvas() {
height = window.innerHeight;
width = window.innerWidth;
canvas = document.getElementById("canvas");
canvas.width = width;
canvas.height = height;
cvs = canvas.getContext("2d");
bufCanvas = document.createElement("canvas");
bufCanvas.width = canvas.width;
bufCanvas.height = canvas.height;
bufCtx = bufCanvas.getContext("2d");
}
function drawCircle(x, y, c, r) {
if (c <= 0 || c >= 255){
console.log(c);
}
bufCtx.beginPath();
bufCtx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
bufCtx.strokeStyle = "rgb(255, 255, 255)";
bufCtx.arc(x, y, r, 0, Math.PI * 2);
bufCtx.stroke();
bufCtx.fill();
bufCtx.closePath();
}
function _draw() {
let maxSize = canvas.height < canvas.width ? canvas.height : canvas.width;
let dimen = 8;
let ox = canvas.width / 2;
let oy = canvas.height / 2;
let R1 = maxSize/20; // 원의 반지름
let R2 = maxSize/10; // Y축으로 회전하기 위한 반경
// 카메라와 스크린의 거리를 z'라 하고, 그 투영되는 상을 x',y'
// z:x = z':x'
// z:y = z':y'
// x' = xz'/z, y' = yz'/z
// z' = z1
// x', y' = xz1/z, yz1/z
// z의 최대거리 z2 = 5000이라 하면
// x' <= (height/2 * 0.8) (화면 중앙 위치, 마진 0.1)
// x 의 최대 값은 R1 + R2
// x' = Z1(R1+R2)/z2 = height * 0.4
// Z1 = Z2 * height * 0.4 / (R1+R2)
let ZZ2 = 5000;
let ZZ1 = maxSize * ZZ2 * 0.4 / (R1 + R2);
let points = [];
for (let i = 0; i < 628; i += dimen) {
let x1 = Math.cos(i/100) * R1 + R2;
let y1 = Math.sin(i/100) * R1;
let nx1 = Math.cos(i/100);
let ny1 = Math.sin(i/100);
for (let j = 0; j < 628; j += dimen) {
// Y축 회전
let x2 = x1 * Math.cos(j/100);
let y2 = y1;
let z2 = x1 * Math.sin(j/100);
let nx2 = nx1 * Math.cos(j/100);
let ny2 = ny1;
let nz2 = nx1 * Math.sin(j/100);
// X축 회전
let x3 = x2;
let y3 = y2 * Math.cos(thetaX/100) - z2 * Math.sin(thetaX/100);
let z3 = y2 * Math.sin(thetaX/100) + z2 * Math.cos(thetaX/100);
let nx3 = nx2;
let ny3 = ny2 * Math.cos(thetaX/100) - nz2 * Math.sin(thetaX/100);
let nz3 = ny2 * Math.sin(thetaX/100) + nz2 * Math.cos(thetaX/100);
// Z축 회전
let x4 = x3 * Math.cos(thetaZ/100) - y3 * Math.sin(thetaZ/100);
let y4 = x3 * Math.sin(thetaZ/100) + y3 * Math.cos(thetaZ/100);
let z4 = ZZ2 + z3;
let nx4 = nx3 * Math.cos(thetaZ/100) - ny3 * Math.sin(thetaZ/100);
let ny4 = nx3 * Math.sin(thetaZ/100) + ny3 * Math.cos(thetaZ/100);
let nz4 = nz3;
// 법선 벡터 (nx4, ny4, nz)와 광원의 백터 내적 계산
// 광원 위치는 (0, 0, -1)
let lx = 0;
let ly = 0;
let lz = -1;
let light = nx4 * lx + ny4 * ly + nz4 * lz;
if (light > 0) {
let color = Math.round(light * 255);
// x' = xz'/z, y' = yz'/z
let xp = Math.floor(x4 * ZZ1 / z4);
let yp = Math.floor(-y4 * ZZ1 / z4);
points.push([xp + ox, yp + oy, 255-color]);
}
}
}
bufCtx.beginPath();
bufCtx.strokeStyle = "black";
bufCtx.fillStyle = "black";
bufCtx.fill();
bufCtx.fillRect(0, 0, canvas.width, canvas.height);
bufCtx.closePath();
bufCtx.stroke();
points.forEach(e => drawCircle(e[0], e[1], e[2], 1));
thetaX = (thetaX + 2) % 629;
thetaZ = (thetaZ + 2) % 629;
}
function OnDraw() {
_draw();
cvs.beginPath();
cvs.clearRect(0, 0, canvas.width, canvas.height);
cvs.drawImage(bufCanvas, 0, 0);
console.log(">OnDraw()");
}
function onLoadPage() {
InitCanvas();
OnDraw();
setInterval(OnDraw, 50);
}
window.onload = onLoadPage();
</script>
</body>
</html>
'Coding > JavsScript 삽질기' 카테고리의 다른 글
[JavaScript] 회전하는 도넛과 구 그리기 (0) | 2022.01.20 |
---|---|
[JavaScript] 회전하는 구 그리기 (0) | 2022.01.19 |
[Javascript] 좋은 글 링크 모음 (0) | 2021.10.16 |