1. 시작하기

   1.1. 테트리스 게임 소개

테트리스는 누구나 한 번쯤 들어 본 게임이기에, 설명은 생략 합니다.

테트리스에 대한 자세한 사항은 아래 링크를 참고 하시기 바랍니다.

 

   1.2. 구현 할 테트리스 게임의 요구사항 정의

우리가 만들 테트리스 게임은 아래 사이트에서 플레이 할 수 있습니다.

 

Chobosho's tetris

Tetris!

chobocho.com

 

안드로이드로 포팅한 버전은 아래 사이트에서 플레이 할 수 있습니다.

 

Classic Block Game V2 - Google Play 앱

안드로이드용 클래식 블록 게임입니다.

play.google.com

 

그리고, 전체 소스는 아래에서 다운 받을 수 있습니다.

 

이번에 구현 할 테트리스 게임은 아케이드 모드, 퍼즐 모드, 아이템 모드 총 3개의 모드로 구성이 됩니다.

 

1.2.1 아케이드 모드

테트리스의 기본 형태로, 모두가 잘 알고 있는 내용이라, 자세한 설명은 생략합니다.

1.2.2 퍼즐모드

퍼즐 모드 테트리스는 고전적인 테트리스 게임을 벗어나, 플레이어의 두뇌와 반응 속도를 동시에 시험하는 독특한 경험을 제공합니다. 이 게임에는 70가지 이상의의 다양한 퍼즐이 포함되어 있어, 게임을 진행하면서 플레이어의 전략적 사고력을 향상시키는 동시에 문제 해결 능력을 증진시키는 데 도움이 됩니다.
각 스테이지의 목표는 화면에 보이는 색상이 있는 블록을 모두 제거하는 것입니다. 이는 단순히 블록을 빠르게 쌓는 것을 넘어서, 플레이어가 어떻게 블록을 배치하고 어떤 블록을 사용할지를 신중하게 고려해야 하는 전략적인 요소를 게임에 도입합니다.

1.2.3 아이템 모드

아이템 모드는 다양한 아이템들이 포함된 전략적인 게임을 제공 합니다. 이 모드의 주요 아이템들은 Yellow Thunder, Black Thunder, Orange Thunder, Red Thunder, Blue Boom, Black Boom, Green Boom, 그리고 Red Boom입니다.

Yellow Thunder는 화면의 모든 검은 폭탄을 폭발시키며, Black Thunder는 최대 3개의 폭탄을 화면에 추가합니다. Orange Thunder는 아이콘이 바닥에 닿을 때 중심을 기준으로 3x3 영역을 지웁니다. 단, 아이콘이 포함된 줄이 꽉 차있다면 작동하지 않습니다. Red Thunder는 아이콘이 바닥에 닿으면 현재 위치한 세로 라인과 오른쪽 세로 라인을 지웁니다.

Blue Boom은 아이콘이 바닥에 닿았을 때 중심 주변 3x3 크기를 회색 블록으로 채웁니다. Black Boom은 폭탄이 포함된 줄이 블록으로 꽉 차면 위 아래 한 줄씩 총 3줄을 삭제합니다. Green Boom은 원형의 회색 블록을 추가합니다. 단, 아이콘이 포함된 줄이 꽉 차있다면 작동하지 않습니다. Orange Boom은 아이콘 중심으로 빈 3x3 사각형을 그립니다. 마찬가지로, 아이콘이 포함된 줄이 꽉 차있다면 작동하지 않습니다. Red Boom은 아이콘이 위치한 한 줄을 지웁니다.

아이템 테트리스는 이러한 독특한 아이템들을 활용해 주어진 판의 블록들을 모두 제거하면서, 전략적으로 높은 점수를 달성하는 것이 목표입니다.

플레이어들은 다양한 아이템을 사용하여 게임을 더욱 재미있게 즐길 수 있습니다.

 

Posted by chobocho
Coding/JavsScript 삽질기2023. 4. 18. 00:16

게임 플레이 해보기
http://www.chobocho.com/game/tetris/

 

Chobosho's tetris

Tetris!

chobocho.com

 

자바스크립트를 공부하면서 만든 아이템 테트리스 소스 입니다.
인터넷을 대충 뒤져 보니, 간단한 테트리스 소스는 많으나,
퍼즐 테트리스, 아이템 테트리스 소스는 없어서 공개 합니다.


 



소스코드 위치
https://github.com/chobocho/webTetris/tree/item_tetris

 

GitHub - chobocho/webTetris: Javascript tetris

Javascript tetris. Contribute to chobocho/webTetris development by creating an account on GitHub.

github.com

좋아요, 추천은 힘이 됩니다! ^^
 

Posted by chobocho
Coding/JavsScript 삽질기2022. 9. 19. 00:58
Posted by chobocho
Coding/JavsScript 삽질기2022. 8. 25. 00:03

Javascript로 만든 계산기 입니다.

Chobocho's Simple Calculator
View
MR M+ M- MC

 

Posted by chobocho
Coding/JavsScript 삽질기2022. 8. 24. 01:32

JsCalculator 는 어떠한 개인 정보도 수집하지 않습니다.

JsCalculator does not collect any personal information.

 

 

Posted by chobocho

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Mandelbrot</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.02.02
    // Update: 2022.02.02

    let cvs;
    let canvas;
    let bufCanvas;
    let bufCtx;
    let imageData;

    let scaleX = 1.0;
    let scaleY = 1.0;
    let mx = 1.0;
    let my = 1.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");

        imageData = bufCtx.createImageData(canvas.width, canvas.height);
    }

    function _draw(x1, y1, x2, y2) {
        const MAX_COUNT = 256;

        // X = -3.0 ~ 3.0
        // Y = -2.0 ~ 2.0
        // Z = X + Yi

        mx = x1 < -3.0 ? -3.0 : x1;
        my = y1 < -2.0 ? -2.0 : y1;

        x2 = x2 > 3.0 ? 3.0 : x2;
        y2 = y2 > 2.0 ? 2.0 : y2;

        let mw = x2 - x1;
        let mh = y2 - y1;

        console.log(x1, y1, x2, y2, " : ", mx, my, " : ", mw, mh);

        scaleX = mw / imageData.width;
        scaleY = mh / imageData.height;

        for (let i = 0; i < imageData.height; i++) {
            for (let j = 0; j < imageData.width; j++) {

                let c = 0;
                let x = mx + j * scaleX;
                let y = my + i * scaleY;
                let sx = x;
                let sy = y;

                for (c = 0; c < MAX_COUNT; c++) {
                    if (x**2 + y**2 > 4) {
                        break;
                    }
                    let nx = x**2 - y**2;
                    let ny = 2 * x * y;
                    x = nx + sx;
                    y = ny + sy;
                }

                let pos = 4 * (i * imageData.width + j);
                let color = 3 * c / MAX_COUNT;

                if (color < 1) {
                    imageData.data[pos+0]  = 255 * color;
                    imageData.data[pos+1] = color;
                    imageData.data[pos+2] = color;
                }
                else if (color < 2 ) {
                    imageData.data[pos+0] = 255;
                    imageData.data[pos+1] = color;
                    imageData.data[pos+2] = 0;
                } else {
                    imageData.data[pos+0] = 255;
                    imageData.data[pos+1] = 255;
                    imageData.data[pos+2] = color;
                }

                imageData.data[pos+3] = 255;
            }
        }

        bufCtx.putImageData(imageData, 0, 0);
        console.log("_OnDraw2()");
    }

    function OnDraw() {
        _draw(-3.0, -2.0, 3.0, 2.0);
        cvs.clearRect(0, 0, canvas.width, canvas.height);
        cvs.drawImage(bufCanvas, 0, 0);
    }

    function rectMouseMove(event) {
        // console.log("rectMouseMove");
        var currentPos = getMousePosition(event);
        cvs.beginPath();

        cvs.clearRect(0, 0, canvas.width, canvas.height);
        cvs.drawImage(bufCanvas, 0, 0);

        cvs.strokeStyle = pos.color;
        var box = {
            W: currentPos.X - pos.sx,
            H: currentPos.Y - pos.sy
        };

        cvs.strokeRect(pos.sx, pos.sy, box.W, box.H);

        cvs.stroke();
        cvs.closePath();
    }

    function rectMouseUp(event) {
        if (pos.isDraw) {
            console.log("rectMouseUp");
            var currentPos = getMousePosition(event);

            pos.ex = currentPos.X;
            pos.ey = currentPos.Y;

            if (pos.sx > pos.ex) {
                let t = pos.sx;
                pos.sx = pos.ex;
                pos.ex = t;
            }

            if (pos.sy > pos.ey) {
                let t = pos.sy;
                pos.sy = pos.ey;
                pos.ey = t;
            }

            let sx = mx + pos.sx * scaleX;
            let sy = my + pos.sy * scaleY;
            let ex = mx + pos.ex * scaleX;
            let ey = my + pos.ey * scaleY ;

            _draw(sx, sy, ex, ey);
            cvs.clearRect(0, 0, canvas.width, canvas.height);
            cvs.drawImage(bufCanvas, 0, 0);

            pos.isDraw = false;
        }
    }

    function rectMouseDown(event) {
        console.log("rectMouseDown");
        if (pos.isDraw) {
            return;
        }
        bufCtx.putImageData(imageData, 0, 0);
        //bufCtx.drawImage(canvas, 0, 0);
        pos.isDraw = true;
        var startPos = getMousePosition(event);
        pos.sx = startPos.X;
        pos.sy = startPos.Y;
    }

    function mouseListener(event) {
        switch (event.type) {
            case "mousedown":
                if (!pos.isDraw) {
                    pos.mouseDownAction(event);
                }
                break;
            case "mousemove":
                if (pos.isDraw) {
                    pos.mouseMoveAction(event);
                }
                break;
            case "mouseup":
            case "mouseout":
                if (pos.isDraw) {
                    pos.mouseUpAction(event);
                }
                break;
        }
    }

    function InitMouseEvent() {
        canvas.addEventListener("mousedown", mouseListener);
        canvas.addEventListener("mousemove", mouseListener);
        canvas.addEventListener("mouseout", mouseListener);
        canvas.addEventListener("mouseup", mouseListener);
    }

    function getMousePosition(event) {
        var x = event.pageX - canvas.offsetLeft;
        var y = event.pageY - canvas.offsetTop;
        return { X: x, Y: y };
    }

    var pos = {
        isDraw: false,
        mouseDownAction: rectMouseDown,
        mouseUpAction: rectMouseUp,
        mouseMoveAction: rectMouseMove,
        color: "rgb(255,211,25,255)",
        sx: 0,
        sy: 0,
        ex: 0,
        ey: 0
    }

    function onLoadPage() {
        InitCanvas();
        InitMouseEvent();
        OnDraw();
    }

    window.onload = onLoadPage();
</script>
</body>
</html>
Posted by chobocho

자바스크립트로 만델브로트 집합을 구현해 보았다.

만델브로트 집합

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Mandelbrot</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.02.02
    // Update: 2022.02.02

    let cvs;
    let canvas;
    let bufCanvas;
    let bufCtx;

    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 _draw() {
        let imageData = bufCtx.createImageData(canvas.width, canvas.height);
        const MAX_COUNT = 255;

        // X = -3.0 ~ 3.0
        // Y = -2.0 ~ 2.0
        // Z = X + Yi

        let mx = -3.0;
        let my = -2.0;
        let mw = 6.0;
        let mh = 4.0;
        let scaleX = mw / imageData.width;
        let scaleY = mh / imageData.height;

        for (let i = 0; i < imageData.height; i++) {
            for (let j = 0; j < imageData.width; j++) {

                let c = 0;
                let x = mx + j * scaleX;
                let y = my + i * scaleY;
                let sx = x;
                let sy = y;

                for (c = 0; c < MAX_COUNT; c++) {
                    if (x**2 + y**2 > 4) {
                        break;
                    }
                    let nx = x**2 - y**2;
                    let ny = 2 * x * y;
                    x = nx + sx;
                    y = ny + sy;
                }

                let pos = 4 * (i * imageData.width + j);

                imageData.data[pos+0] = c;
                imageData.data[pos+1] = c;
                imageData.data[pos+2] = c;
                imageData.data[pos+3] = 255;
            }
        }

        bufCtx.putImageData(imageData, 0, 0);
    }

    function OnDraw() {
        _draw();
        cvs.beginPath();
        cvs.clearRect(0, 0, canvas.width, canvas.height);
        cvs.drawImage(bufCanvas, 0, 0);
    }

    function onLoadPage() {
        InitCanvas();
        OnDraw();
    }

    window.onload = onLoadPage();
</script>
</body>
</html>
Posted by chobocho
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
Coding/JavsScript 삽질기2022. 1. 19. 10:25

유튜브에서본 회전 하는 3D 도넛을 구현해 보았다.

3D doughnut

코드는 아래와 같다.

<!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>

 

 

Posted by chobocho
Coding/JavsScript 삽질기2022. 1. 19. 01:08

유튜브에서 회전하는 도넛 영상을 보고, 회전하는 구를 그려 보았다.

Sphere

1. 난해하게 만든 전체 소스는 아래와 같다.

<!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.18
    // Update: 2022.01.19

    let cvs;
    let canvas;
    let bufCanvas;
    let bufCtx;
    let thetaX = 0;
    let thetaZ = 0;

    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");
    }

    function drawCircle(x, y, c, r) {
        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 ZZ1 = maxSize * 1000 * 0.4 / R1;

        let points = [];

        for (let i = 0; i < 628; i += dimen) {
            let a = COS[i];
            let b = SIN[i];

            for (let j = 0; j < 628; j += dimen) {
                let c = COS[j];
                let d = SIN[j];
                let e = COS[thetaX];
                let f = SIN[thetaX];
                let g = COS[thetaZ];
                let h = SIN[thetaZ];

                let k = b * R1 * e - a * R1 * d * f;
                let x4 = a * R1 * c * g - k * h;
                let y4 = a * R1 * c * h + k * g;
                let z4 = ZZ1 / (1000 + b * R1 * f + a * R1 * d * e);
                let light = (b * f + a * d * e);

                if (light > 0) {
                    let color = Math.round(light * 255);
                    let xp = Math.floor(x4 * z4 + ox);
                    let yp = Math.floor(-y4 * z4 + oy);
                    points.push([xp, yp, 255-color]);
                }
            }
        }

        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 OnDraw() {
        let startTime = new Date();
        _draw();

        cvs.clearRect(0, 0, canvas.width, canvas.height);
        cvs.drawImage(bufCanvas, 0, 0);

        console.log("Elapsed time: " + (new Date() - startTime));
    }


    function onLoadPage() {
        InitValue();
        InitCanvas();
        OnDraw();
        setInterval(OnDraw, 50);
    }

    window.onload = onLoadPage();
</script>
</body>
</html>

 

 


1. HTML로 구를 그리기 위한 기본적인 뼈대를 작성한다.

아래 코드에서 _drawSphere()를 채워 나갑니다.

하기 HTML 코드는 좋은 예제가 아닙니다. HTML 관련은 인터넷에 있는 좋은 코드를 참고 하시기 바랍니다.

<!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">
    let cvs;
    let canvas;
    let bufCanvas;
    let bufCtx;

    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");
    }

    function _drawSphere() {

    }

    function OnDraw() {
        let startTime = new Date();

        bufCtx.strokeStyle = "black";
        bufCtx.fillStyle = "black";
        bufCtx.fillRect(0, 0, canvas.width, canvas.height);

        _drawSphere();

        cvs.clearRect(0, 0, canvas.width, canvas.height);
        cvs.drawImage(bufCanvas, 0, 0);
        console.log("Elapsed time: " + (new Date() - startTime));
    }


    function onLoadPage() {
        InitCanvas();
        OnDraw();
        setInterval(OnDraw, 50);
    }

    window.onload = onLoadPage();
</script>
</body>
</html>

 

2. 원 그리기

원점을 기준으로 지름 r인 원은 아래 수식으로 표현 할 수 있습니다.

이때, x, y는 아래와 같이 구 할 수 있습니다.


원래 작성한 코드는 아래와 같다.

<!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;

    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");
    }

    function drawCircle(x, y, c, r) {

        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;  // 원의 반지름

        // 카메라와 스크린의 거리를 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 ZZ2 = 1000;
        let ZZ1 = maxSize * ZZ2 * 0.4 / R1;

        let points = [];

        for (let i = 0; i < 628; i += dimen) {
            let x1 = Math.cos(i/100) * R1;
            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.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 OnDraw() {
        _draw();

        cvs.clearRect(0, 0, canvas.width, canvas.height);
        cvs.drawImage(bufCanvas, 0, 0);
    }


    function onLoadPage() {
        InitCanvas();
        OnDraw();
        setInterval(OnDraw, 50);
    }

    window.onload = onLoadPage();
</script>
</body>
</html>
Posted by chobocho