Typescriptとcanvas APIでクリスマスツリーを描く

# 環境準備
Typescriptとcanvas APIでクリスマスツリーを描きましょう。

Typescriptがなかったらnpmでインストールしましょう：
```bash
npm install -g npm
npm install -g typescript
```

三つのファイルを作成しましょう
```
index.html
tree.ts
tsconfig.json
```

ファイル内容は以下の通りです。

**`index.html`**
```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <canvas id="canvas" width="200" height="200"></canvas>

    <script src="./tree.js"></script>
  </body>
</html>
```

**`tsconfig.json`**

```json
{
    "compilerOptions": {
        "target": "es5",
        "sourceMap": true,
        "lib": [
            "es5",
            "dom"
        ],
        "noUnusedLocals": true,
        "module": "commonjs"
    }
}
```
**`tree.js`**

```
const canvas = document.getElementById("canvas")  as HTMLCanvasElement | null;;
const ctx = canvas?.getContext("2d");
```
<sup>javascriptと比較して、型を定義する必要があることに注意してください。</sup>


同じディレクトリにあれば```tsc```コマンドでコンパイルできるかどうか試せます。

# 松の木を作成
さて、準備が完了したところで、さっそく木を描いていきましょう。
まず木とはどんなものでしょう？
基本的には緑の三角形が重なっているものです。

そして、枝もあります
```typescript
interface Branch {
    height: number;
    centerPosition: number;
    offset: number; 
    color: string;
}

interface Trunk {
    height: number;
    width: number;
    color: string;
}
```

Treeクラスを作成する
```typescript
class Tree {
    branch: Branch;
    trunk: Trunk;

    constructor() {
        this.branch = { 
            height: 50 * Math.cos(Math.PI / 6), 
            centerPosition: 100, 
            offset: 0,
            color: "green"
        }
        this.trunk = {
            color: "brown",
            width: 20,
            height: 20
        }
    }
}

```

三角形を作るには、ちょっとした計算が必要です。
二等辺三角形を作る必要があります。この計算は少し複雑ですが、2次元空間に3つの点が必要です。
ハードコード値として100を使用するので、木の幅は200pxになります。
```typescript
draw_branch(ctx: CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.moveTo(this.branch.centerPosition - 100 + this.branch.offset, this.branch.centerPosition + 50 - this.branch.offset);
        ctx.lineTo(this.branch.centerPosition + 100 - this.branch.offset, this.branch.centerPosition + 50 - this.branch.offset);
        ctx.lineTo(this.branch.centerPosition, this.branch.centerPosition + 50 - this.branch.height - this.branch.offset);
        ctx.closePath();
        ctx.fillStyle = this.branch.color;
        ctx.fill();
    }
```
メソッドを呼び出して、その結果を見ることができます。
```typescript
const tree = new Tree();

tree.draw_branch(ctx);
```
![Screen Shot 2022-12-06 at 16.13.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989047/131eff1d-e8d8-18fa-bf2e-3e9ccf7e42fc.png)

次に木は複数の枝を持つので、20ピクセルずつ5つのブランチを作成します。
```typescript
draw_tree(ctx: CanvasRenderingContext2D) {
        for ( let n = 0; n < 5; n++ ) {
            this.branch.offset = n * 20;
            this.draw_branch(ctx);
        }
    }
```
木っぽくなってきましたね。
![Screen Shot 2022-12-06 at 16.16.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989047/15a95fb7-63e2-7395-b0f8-8a089135e71e.png)

おっと、幹を書くのを忘れました。
```typescript
draw_trunk(ctx: CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.rect(90, 150, this.trunk.width, this.trunk.height);
        ctx.closePath();
        ctx.fillStyle = this.trunk.color;
        ctx.fill();
    }
```

![Screen Shot 2022-12-08 at 17.54.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989047/aa442f00-4687-fe99-bae8-c0e3d0909425.png)


# クリスマス・イルミネーションを作る

今はただの松の木ですが、クリスマスなのでクリスマスツリーにしていきましょう。
まずは、明かりを付けてみます。
光は基本的に小さな5pxの円です。
```typescript
class Light {
    color: string;
    posX: number;
    posY: number;

    draw(ctx: CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.arc(this.posX, this.posY, 5, 0, 2 * Math.PI);
        ctx.closePath();
        ctx.fillStyle = this.color;
        ctx.fill();
    }

}
```

この円は、キャンバスの上にランダムに作ろうと思っています。背景色が木であることを確認してから、ランダムな色で光を表示する予定です。
forループで光量を制御することができます。現在、キャンバス全体に対して400個のランダムに生成されたライトを選択しています。
色名「```green```」は、htmlコードの「```#008000```」（```rgb(0, 128, 0)```）に対応します。
そして、```getImageData```メソッドを使って、現在のピクセルの色をチェックします。

```typescript
const colors = ["red", "yellow", "cyan"];
function blink(){
    for(let i = 0; i < 400; i++){
        const randX = Math.floor(Math.random() * canvas.width); 
        const randY = Math.floor(Math.random() * canvas.height);
        const c = ctx.getImageData(randX, randY, 1, 1).data;

        if (c[0] === 0 && c[1] === 128 && c[2] === 0) {
            const light = new Light();
            light.color = colors[Math.floor(Math.random()*colors.length)];
            light.posX = randX;
            light.posY = randY;
            light.draw(ctx);
        }
    }
}
```
木にランダムに光を当てる
![Screen Shot 2022-12-08 at 18.02.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989047/dd4a69eb-73bb-790c-db53-3d4b9b7ababd.png)

クリスマスツリーっぽくなりましたね。
最後にアニメーションをつけてみよう！
一番簡単な方法は、キャンバスをクリアして、もう一度すべてを描くことです。
キラキラさせるためにタイムアウトを100に設定する。

```typescript
function mainLoop(): void {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    tree.draw_tree(ctx);
    window.setTimeout(mainLoop, 100);
    blink();
}

mainLoop();
```

![ezgif-2-e809fb3b2b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989047/01793544-0340-02ac-3221-ec5667425ed3.gif)

以下がすべてのコードになります。
```typescript

interface Branch {
    height: number;
    centerPosition: number;
    offset: number; 
    color: string;
}

interface Trunk {
    height: number;
    width: number;
    color: string;
}

class Tree {
    branch: Branch;
    trunk: Trunk;

    constructor() {
        this.branch = { 
            height: 50 * Math.cos(Math.PI / 6), 
            centerPosition: 100, 
            offset: 0,
            color: "green"
        }
        this.trunk = {
            color: "brown",
            width: 20,
            height: 20
        }
    }

    draw_branch(ctx: CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.moveTo(this.branch.centerPosition - 100 + this.branch.offset, this.branch.centerPosition + 50 - this.branch.offset);
        ctx.lineTo(this.branch.centerPosition + 100 - this.branch.offset, this.branch.centerPosition + 50 - this.branch.offset);
        ctx.lineTo(this.branch.centerPosition, this.branch.centerPosition + 50 - this.branch.height - this.branch.offset);
        ctx.closePath();
        ctx.fillStyle = this.branch.color;
        ctx.fill();
    }

    draw_trunk(ctx: CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.rect(90, 150, this.trunk.width, this.trunk.height);
        ctx.closePath();
        ctx.fillStyle = this.trunk.color;
        ctx.fill();
    }

    draw_tree(ctx: CanvasRenderingContext2D) {
        for ( let n = 0; n < 5; n++ ) {
            this.branch.offset = n * 20;
            this.draw_branch(ctx);
        }
        this.draw_trunk(ctx);
    }

}

class Light {
    color: string;
    posX: number;
    posY: number;

    draw(ctx: CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.arc(this.posX, this.posY, 5, 0, 2 * Math.PI);
        ctx.closePath();
        ctx.fillStyle = this.color;
        ctx.fill();
    }

}

const canvas = document.getElementById("canvas")  as HTMLCanvasElement | null;
const ctx = canvas?.getContext("2d");
const colors = ["red", "yellow", "cyan"];
const tree = new Tree();

tree.draw_tree(ctx);

function blink(){
    for(let i = 0; i < 400; i++){
        const randX = Math.floor(Math.random() * canvas.width); 
        const randY = Math.floor(Math.random() * canvas.height);
        const c = ctx.getImageData(randX, randY, 1, 1).data;

        if (c[0] === 0 && c[1] === 128 && c[2] === 0) {
            const light = new Light();
            light.color = colors[Math.floor(Math.random()*colors.length)];
            light.posX = randX;
            light.posY = randY;
            light.draw(ctx);
        }
    }
}


function mainLoop(): void {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    tree.draw_tree(ctx);
    window.setTimeout(mainLoop, 100);
    blink();
}

mainLoop();

```

# まとめ
クリスマスツリーを作る方法はいくつかありますが、今回紹介した方法は最も簡単で早い方法の一つだと思います。
興味があったら是非試してみてください。

それでは、メリークリスマス !