소스는 아래와 같다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sphere</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 = 0;
let thetaZ = 0;
let maxSize = 0;
let dimen = 12;
let ox = 0;
let oy = 0;
let R1 = 1; // 원의 반지름
let R2 = 1; // 회전 중심과의 거리
let R3 = 1;
let ZZ2 = 5000;
let SIN = [];
let COS = [];
function InitValue() {
for (let i = 0; i < 628; i++) {
SIN[i] = Math.sin(i / 100);
COS[i] = Math.cos(i / 100);
}
}
function InitCanvas() {
height = window.innerHeight;
width = window.innerWidth;
console.log(">Width:", width);
console.log(">Heigth:", height);
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");
maxSize = canvas.height < canvas.width ? canvas.height : canvas.width;
ox = canvas.width / 2;
oy = canvas.height / 2;
R1 = maxSize/20; // 원의 반지름
R2 = maxSize/10; // 회전 중심과의 거리
R3 = maxSize/10;
}
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() {
if (maxSize == 0) {
return;
}
// 카메라와 스크린의 거리를 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 = 1000이라 하면
// 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 points = [];
_drawDoughnut(points);
_drawSphere(points);
bufCtx.strokeStyle = "black";
bufCtx.fillStyle = "black";
bufCtx.fillRect(0, 0, canvas.width, canvas.height);
points.forEach(e => drawCircle(e[0], e[1], e[2], 1));
thetaX = (thetaX + 2) % 629;
thetaZ = (thetaZ + 2) % 629;
}
function _drawDoughnut(points) {
// 카메라와 스크린의 거리를 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 ZZ1 = maxSize * ZZ2 * 0.4 / (R1 + R2);
for (let i = 0; i < 628; i += dimen) {
let x1 = COS[i] * R1 + R2;
let y1 = SIN[i] * R1;
let nx1 = COS[i];
let ny1 = SIN[i];
for (let j = 0; j < 628; j += dimen) {
// Y축 회전
let x2 = x1 * COS[j];
let y2 = y1;
let z2 = x1 * SIN[j];
let nx2 = nx1 * COS[j];
let ny2 = ny1;
let nz2 = nx1 * SIN[j];
// X축 회전
let x3 = x2;
let y3 = y2 * COS[thetaX] - z2 * SIN[thetaX];
let z3 = y2 * SIN[thetaX] + z2 * COS[thetaX];
let nx3 = nx2;
let ny3 = ny2 * COS[thetaX] - nz2 * SIN[thetaX];
let nz3 = ny2 * SIN[thetaX] + nz2 * COS[thetaX];
// Z축 회전
let x4 = x3 * COS[thetaZ] - y3 * SIN[thetaZ];
let y4 = x3 * SIN[thetaZ] + y3 * COS[thetaZ];
let z4 = ZZ2 * 2 + z3;
let nx4 = nx3 * COS[thetaZ] - ny3 * SIN[thetaZ];
let ny4 = nx3 * SIN[thetaZ] + ny3 * COS[thetaZ];
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]);
}
}
}
}
function _drawSphere(points) {
// 카메라와 스크린의 거리를 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 = 1000이라 하면
// 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)
ZZ1 = maxSize * ZZ2 * 0.4 / (R1 + R2 + R3);
for (let i = 0; i < 628; i += dimen) {
let x1 = COS[i] * R1;
let y1 = SIN[i] * R1;
let nx1 = COS[i];
let ny1 = SIN[i];
for (let j = 0; j < 628; j += dimen) {
// Y축 회전
let x2 = x1 * COS[j];
let y2 = y1;
let z2 = x1 * SIN[j];
let nx2 = nx1 * COS[j];
let ny2 = ny1;
let nz2 = nx1 * SIN[j];
// Z축 회전
let x3 = x2 * COS[thetaX] - y2 * SIN[thetaX];
let y3 = x2 * SIN[thetaX] + y2 * COS[thetaX];
let z3 = z2;
let nx3 = nx2 * COS[thetaX] - ny2 * SIN[thetaX];
let ny3 = nx2 * SIN[thetaX] + ny2 * COS[thetaX];
let nz3 = nz2;
// X축 회전
let x4 = x3 + R2 + R3;
let y4 = y3 * COS[thetaX] - z3 * SIN[thetaX];
let z4 = y3 * SIN[thetaX] + z3 * COS[thetaX];
let nx4 = nx3;
let ny4 = ny3 * COS[thetaX] - nz3 * SIN[thetaX];
let nz4 = ny3 * SIN[thetaX] + nz3 * COS[thetaX];
// Z축 시계 방향 회전
let x5 = x4 * COS[thetaZ] + y4 * SIN[thetaZ];
let y5 = -x4 * SIN[thetaZ] + y4 * COS[thetaZ];
let z5 = ZZ2 + z4;
let nx5 = nx4 * COS[thetaZ] + ny4 * SIN[thetaZ];
let ny5 = -nx4 * SIN[thetaZ] + ny4 * COS[thetaZ];
let nz5 = nz4;
// 법선 벡터 (nx4, ny4, nz)와 광원의 백터 내적 계산
// 광원 위치는 (0, 0, -1)
let lx = 0;
let ly = 0;
let lz = -1;
let light = nx5 * lx + ny5 * ly + nz5 * lz;
if (light > 0) {
let color = Math.round(light * 255);
// x' = xz'/z, y' = yz'/z
let xp = Math.floor(x5 * ZZ1 / z5);
let yp = Math.floor(-y5 * ZZ1 / z5);
points.push([xp + ox, yp + oy, 255-color]);
}
}
}
}
function OnDraw() {
_draw();
cvs.clearRect(0, 0, canvas.width, canvas.height);
cvs.drawImage(bufCanvas, 0, 0);
}
function onLoadPage() {
InitValue();
InitCanvas();
OnDraw();
setInterval(OnDraw, 50);
}
window.onload = onLoadPage();
</script>
</body>
</html>
'Coding > JavsScript 삽질기' 카테고리의 다른 글
만델브로트 집합 그리기 (0) | 2022.02.02 |
---|---|
[JavaScript] 회전하는 도넛 그리기 (0) | 2022.01.19 |
[JavaScript] 회전하는 구 그리기 (0) | 2022.01.19 |