Coding/JavsScript 삽질기2022. 1. 20. 00:12
Sphere

 


 

소스는 아래와 같다.

<!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>
Posted by chobocho