ChatGPTとあそぼ-花火ARをつくる-

# ChatGPT、使ってみる？
気にはなってた、ChatGPT。
とうとう第一歩を踏み出したら便利すぎて感動したので、思い出を綴ります。笑

# いざ登録
ChatGPTの登録方法は、こちらのページを参考に。
二段階認証が個人的には面倒でしたが、すぐに登録完了。

https://qiita.com/makaishi2/items/c90018d743b63c5d114e

# やりたいこと
今回、ChatGPTの力を借りて実現したいのは、
**花火ARを作る！！**
カメラ映像の上に、花火が上がって一緒に写真が撮れるよ！的な。
イメージはこんな感じ↓↓↓

http://www.digitalehonaward.net/sakuhin/8th/5152/

アプリなしで、webブラウザから使えるのが理想。
マーカーレスで、URLへアクセスすれば使えるwebアプリにしたい。
事前に調べたところ、花火もビデオもjavascriptで何とかなりそう。

https://qiita.com/Junya-kobayashi/items/05d88ebcffc45815275d

https://qiita.com/kinoleaf/items/f73b465c0d20a50b8cf7

# いざ質問！
技術的にも問題なさそうなので、本題のChatGPTさんにお知恵をお借りしてみましょう。

ざっくり聞くと、手段を教えてくれました。
### 質問1
>Q.カメラ映像の上に花火を打ち上げたい
### 回答1
>A.カメラ映像を扱うには、多数の技術とツールがあり、いくつかの可能性が考えられます。以下にいくつかのオプションを示します。
>1. OpenCVとPygameを使った方法： 
OpenCVは、画像とビデオを扱うための一般的なライブラリです。Pygameは、音楽やビデオゲームを作成するためのフリーソフトウェアで、オーディオ、ビデオ、入力などを処理するためのモジュールが含まれています。これらのツールを組み合わせることで、花火のエフェクトを生成し、カメラ映像の上に表示することができます。
>2. UnityとVuforiaを使った方法：
Unityは、3Dゲームやシミュレーションを作成するためのゲームエンジンです。Vuforiaは、AR（拡張現実）を実現するためのツールキットで、Unityで使用することができます。これらを組み合わせることで、花火のARエフェクトを生成し、カメラ映像の上に表示することができます。
>3. ProcessingとOpenCVを使った方法：
Processingは、クリエイティブ・コーディングのためのプログラミング言語で、ビジュアルアートやインタラクションデザインなどに使用されます。OpenCVは、ビデオと画像処理のためのライブラリで、Processingと組み合わせることで、花火のエフェク

回答が長すぎたのか、途中で切れてしまいました。
初心者には具体的なコードで教えてほしいなぁ・・・

先人のコードを教えてあげたら、いい感じに作ってくれるんじゃ！？
てなわけで以下のコードを組み合わせてもらうことに。

どこのサイトからコード拾ってきたか、失念しました。ごめんなさい。

### 質問2
>Q.次のコードを組み合わせて、カメラ映像の上に花火を打ち上げたい。
【コードA】↓
```
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WEBカメラの映像を表示</title>
</head>
<body>
    <div>
        <h1>WEBカメラの映像を表示</h1>
        <div>
            <video id="video"></video>
        </div>
    </div>  
    <canvas id="canvas"></canvas>
    <script>
        const video = document.getElementById("video")
        navigator.mediaDevices.getUserMedia({
            video: true,
            audio: false,
        }).then(stream => {
            video.srcObject = stream;
            video.play()
        }).catch(e => {
          console.log(e)
        })

        const canvas = document.getElementById("canvas");
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        window.addEventListener("resize", () => {
          canvas.width = window.innerWidth;
          canvas.height = window.innerHeight;
        });
    </script>  
</body>
</html>
```
【コードB】↓
```
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>花火を表示する</title>
</head>
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        const canvas = document.getElementById("canvas");
        const ctx = canvas.getContext("2d");

        function drawFirework(x, y, radius, color) {
          ctx.beginPath();
          ctx.arc(x, y, radius, 0, Math.PI * 2);
          ctx.fillStyle = color;
          ctx.fill();

          for (let i = 0; i < 30; i++) {
            const angle = Math.random() * Math.PI * 2;
            const explosionRadius = Math.random() * 20;
            const explosionX = x + Math.cos(angle) * explosionRadius;
            const explosionY = y + Math.sin(angle) * explosionRadius;
            const explosionSize = Math.random() * 3;

            ctx.beginPath();
            ctx.arc(explosionX, explosionY, explosionSize, 0, Math.PI * 2);
            ctx.fillStyle = color;
            ctx.fill();
          }
        }

        canvas.addEventListener("click", (e) => {
          const x = e.clientX - canvas.offsetLeft;
          const y = e.clientY - canvas.offsetTop;

          for (let i = 0; i < 10; i++) {
            const radius = Math.random() * 3 + 1;
            const color = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`;
            drawFirework(x, y, radius, color);
          }
        });
    </script>
</body>
</html>
```
### 回答2
>上記のコードを組み合わせることで、カメラ映像の上に花火を打ち上げることができます。以下が組み合わせたコードです。

```
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>カメラ映像と花火を表示する</title>
</head>
<body>
    <div>
        <h1>カメラ映像と花火を表示する</h1>
        <div>
            <video id="video"></video>
        </div>
    </div>  
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        const video = document.getElementById("video")
        navigator.mediaDevices.getUserMedia({
            video: true,
            audio: false,
        }).then(stream => {
            video.srcObject = stream;
            video.play()
        }).catch(e => {
          console.log(e)
        })

        const canvas = document.getElementById("canvas");
        const ctx = canvas.getContext("2d");

        function drawFirework(x, y, radius, color) {
          ctx.beginPath();
          ctx.arc(x, y, radius, 0, Math.PI * 2);
          ctx.fillStyle = color;
          ctx.fill();

          for (let i = 0; i < 30; i++) {
            const angle = Math.random() * Math.PI * 2;
            const explosionRadius = Math.random() * 20;
            const explosionX = x + Math.cos(angle) * explosionRadius;
            const explosionY = y + Math.sin(angle) * explosionRadius;
            const explosionSize = Math.random() * 3;

            ctx.beginPath();
            ctx.arc(explosionX, explosionY, explosionSize, 0, Math.PI * 2);
            ctx.fillStyle = color;
            ctx.fill();
          }
        }

        canvas.addEventListener("click", (e) => {
          const x = e.clientX - canvas.offsetLeft;
          const y = e.clientY - canvas.offsetTop;

          for (let i = 0; i < 10; i++) {
            const radius = Math.random() * 3 + 1;
            const color = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`;
            drawFirework(x, y, radius, color);
          }
        });

        function draw() {
          requestAnimationFrame(draw);
          ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        }
        draw();
    </script>  
</body>
</html>
```

>このコードでは、まずカメラの映像を取得してキャンバスに描画し、クリックされるたびに花火のエフェクトを表示します。キャンバス上で描画された映像は、定期的に更新されます。

おおっ！なんかええ感じの来た。
早速実行してみたら、花火が表示されないよ・・・

### 質問3
>Q. このコードで、花火が表示されません。

早速コードを修正してくれました。

### 回答3
>A.おそらく、花火のコードとカメラのコードが別々のHTMLファイルに書かれており、それぞれのファイルを個別に開いたために、花火が表示されないのだと思われます。
>解決策としては、2つのコードを同じHTMLファイルに組み込んで、カメラの映像の上に花火が表示されるようにすることが考えられます。以下は、2つのコードを組み合わせた例です。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WEBカメラの映像と花火を表示</title>
</head>
<body>
    <div>
        <h1>WEBカメラの映像を表示</h1>
        <div>
            <video id="video"></video>
        </div>
    </div>  
    <canvas id="canvas"></canvas>
    <script>
        const video = document.getElementById("video")
        navigator.mediaDevices.getUserMedia({
            video: true,
            audio: false,
        }).then(stream => {
            video.srcObject = stream;
            video.play()
        }).catch(e => {
          console.log(e)
        })

        const canvas = document.getElementById("canvas");
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        window.addEventListener("resize", () => {
          canvas.width = window.innerWidth;
          canvas.height = window.innerHeight;
        });

        function drawFirework(x, y, radius, color) {
          ctx.beginPath();
          ctx.arc(x, y, radius, 0, Math.PI * 2);
          ctx.fillStyle = color;
          ctx.fill();

          for (let i = 0; i < 30; i++) {
            const angle = Math.random() * Math.PI * 2;
            const explosionRadius = Math.random() * 20;
            const explosionX = x + Math.cos(angle) * explosionRadius;
            const explosionY = y + Math.sin(angle) * explosionRadius;
            const explosionSize = Math.random() * 3;

            ctx.beginPath();
            ctx.arc(explosionX, explosionY, explosionSize, 0, Math.PI * 2);
            ctx.fillStyle = color;
            ctx.fill();
          }
        }

        canvas.addEventListener("click", (e) => {
          const x = e.clientX - canvas.offsetLeft;
          const y = e.clientY - canvas.offsetTop;

          const ctx = canvas.getContext("2d");
          for (let i = 0; i < 10; i++) {
            const radius = Math.random() * 3 + 1;
            const color = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`;
            drawFirework(x, y, radius, color);
          }
        });
    </script>  
</body>
</html>
```

>このコードでは、カメラの映像を表示する`<video>`要素と、花火を表示する`<canvas>`要素が同じHTMLファイル内にあります。また、カメラの映像と花火を重ねるために、花火の描

また途中で切れちゃったけどコードは分かるし、まぁいっか。
しかし、またしても花火は上がらず。
この後も延々修正してもらったけど、うまくいかない。

そもそも、私の聞き方が悪いんじゃね・・・？

# 質問再チャレンジ！

簡潔に！分かりやすく！これでどうだ！

### 質問
>Q.カメラ映像を全画面に表示し、その上に透過した花火を表示させてください。　
### 回答
>A.以下のコードを試してみてください。これは、カメラ映像を全画面に表示し、その上に半透明の黒い背景を置いて、その上に花火を表示する方法です。花火が透けて見えるようになります。

```
<!DOCTYPE html>
<html>
  <head>
    <title>Fireworks Example</title>
    <script src="https://cdn.jsdelivr.net/npm/fireworks-js@2.x/dist/index.umd.js"></script>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      html,
      body {
        height: 100%;
      }

      #video {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
      }

      #fireworks-container {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 1;
      }

      #fireworks-background {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 2;
        background-color: rgba(0, 0, 0, 0.5);
      }
    </style>
  </head>
  <body>
    <video id="video"></video>
    <div id="fireworks-container"></div>
    <div id="fireworks-background"></div>

    <script>
      const container = document.getElementById('fireworks-container');
      const fireworks = new Fireworks.default(container);

      // get access to the user's camera
      navigator.mediaDevices
        .getUserMedia({ video: true })
        .then(function (stream) {
          // set the video element to show the user's camera
          const videoElement = document.getElementById('video');
          videoElement.srcObject = stream;
          videoElement.play();

          // start the fireworks animation
          fireworks.start({
            colors: ['#ffffff', '#ff0000', '#00ff00', '#0000ff'],
            minScaleMultiplier: 0.3,
            maxScaleMultiplier: 0.5,
            minRadiusMultiplier: 0.15,
            maxRadiusMultiplier: 0.25,
            density: 5,
            explosionChance: 0.2,
            maxAge: {
              min: 10,
              max: 30,
            },
          });
        })
        .catch(function (err) {
          console.error(err);
        });
    </script>
  </body>
</html>
``` 

>上記のコードでは、カメラ映像を全画面に表示するために `html` と `body` の高さを `100%` に設定し、`object-fit: cover` を使用して映像のサイズを調整しました。また、花火を `z-index: 1` で背景より前に、黒い背景を `z-index: 2` で背景より後ろに置いています。さらに、背景の色を半透明の黒色に設定しています。

早速動かしてみた。

![black.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063371/088cae6b-1477-5a66-927e-e963d8f074d3.png)

おおっ！！

**できてる！！！**
※画像では分かりにくいかもですが、花火上がってます。
※マスク画像はこちらからお借りしました。

https://www.linustock.com/vectors/seasonal/halloween/agl00008


このままだとなんだか画面が暗い？（黒い？）ので、透明度をあげてもらおう。
### 改善要望1
>Q.透明度を上げられませんか？
### 改善策1
>A.はい、透過度を変更することができます。以下は、カメラ映像と花火の透過度を0.5に設定した例です。CSSの`opacity`プロパティを使用して、要素の透過度を変更しています。

```
<!DOCTYPE html>
<html>
  <head>
    <title>Fireworks Example</title>
    <script src="https://cdn.jsdelivr.net/npm/fireworks-js@2.x/dist/index.umd.js"></script>
    <style>
      #video {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        opacity: 0.5;
      }
      .fireworks {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        opacity: 0.5;
      }
    </style>
  </head>
  <body>
    <video id="video"></video>
    <div class="fireworks"></div>

    <script>
      const container = document.querySelector('.fireworks')
      const fireworks = new Fireworks.default(container)

      // get access to the user's camera
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // set the video element to show the user's camera
          const videoElement = document.getElementById('video')
          videoElement.srcObject = stream
          videoElement.play()

          // start the fireworks animation
          fireworks.start({
            colors: ['#ffffff', '#ff0000', '#00ff00', '#0000ff'],
            minScaleMultiplier: 0.3,
            maxScaleMultiplier: 0.5,
            minRadiusMultiplier: 0.15,
            maxRadiusMultiplier: 0.25,
            density: 5,
            explosionChance: 0.2,
            maxAge: {
                min: 10,
                max: 30
            }
          })
        })
        .catch(function (err) {
          console.error(err)
        })
    </script>
  </body>
</html>
```

>この例では、CSSセレクタを使用して、カメラ映像と花火それぞれに異なるスタイルを適用しています。カメラ映像には、`#video`というIDセレクタを使用して、フルスクリーンの位置と透過度を設定しています。花火の要素には、`.fireworks`クラスセレクタを使用して、同様に透過度を設定しています。

![white.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063371/f085763c-dd06-1ccf-1e61-35900b1c3b68.png)
うん。透明っていうか、白くなったね。

### 改善要望2
>Q.白くなってしまいました。
### 改善策2
>A.申し訳ありません。おそらく、カメラ映像と花火の重ね合わせで、透過度が累積して白くなってしまっている可能性があります。その場合、花火の発射部分に限定して透過度を下げることができます。

>以下は、花火のパーティクルに対して `opacity` プロパティを設定して、透過度を下げる例です。

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Fireworks Example</title>
    <script src="https://cdn.jsdelivr.net/npm/fireworks-js@2.x/dist/index.umd.js"></script>
    <style>
      #video {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        z-index: 0;
      }
      .fireworks {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 1;
      }
    </style>
  </head>
  <body>
    <video id="video"></video>
    <div class="fireworks"></div>

    <script>
      const container = document.querySelector('.fireworks')
      const fireworks = new Fireworks.default(container)

      // get access to the user's camera
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // set the video element to show the user's camera
          const videoElement = document.getElementById('video')
          videoElement.srcObject = stream
          videoElement.play()

          // start the fireworks animation
          fireworks.start({
            colors: ['#ffffff', '#ff0000', '#00ff00', '#0000ff'],
            minScaleMultiplier: 0.3,
            maxScaleMultiplier: 0.5,
            minRadiusMultiplier: 0.15,
            maxRadiusMultiplier: 0.25,
            density: 5,
            explosionChance: 0.2,
            maxAge: {
                min: 10,
                max: 30
            },
            beforeParticleRender: function (particle) {
              // reduce opacity for particles
              particle.opacity = 0.5;
            }
          })
        })
        .catch(function (err) {
          console.error(err)
        })
    </script>
  </body>
</html>
```

>この例では、`.fireworks` 要素をカメラ映像の上に表示しています。また、`.fireworks` 要素に `z-index: 1` を設定して、カメラ映像よりも手前に表示されるようにしています。また、`beforeParticleRender` コールバック関数を使用して、花火のパーティクルに対して `opacity` プロパティを設定して、透過度を下げています。

![fin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063371/1a500e84-370a-7c58-e02d-171e0e53ca6c.png)
まさに！求めてたやつ！！
さすがだよChatGPT!

# ChatGPTとの付き合い方
万能ChatGPTの能力を引き出すには、やっぱり質問者の力量が大事なのね。
* 簡潔で分かりやすく質問する
* 具体例を挙げて質問する

まだまだ改善の余地はあるけれど、求めていた答えを得ることができました。
次は、スクショボタン実装したいなー。

長々と乱文、失礼いたしました。
やっとこさマークダウン記法慣れてきた感ある。

# おまけ
Githubにコードを置いてみました。

https://github.com/padhinton/fire_flower_1/tree/main

* fire_flower_black.html
⇒カメラ映像暗いやつ
* fire_flower_white.html
⇒カメラ映像白いやつ
* fire_flower_fin.html
⇒完成版