ローポリな海を作ってGitHub ActionsでGitHub Pagesにデプロイ

## 概要
`three.js`で，スクリーンセーバーをイメージしてローポリな海を作りました．[ソースコードはこちら](https://github.com/yif11/Ocean/)で公開しています．

https://github.com/yif11/Ocean/

また，作ったページを`GitHub Actions`で`GitHub Pages`にビルド & デプロイし，以下のページで公開しています．

https://yif11.github.io/Ocean/

![ocean.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3898648/5caacbec-3148-41ac-af19-612bfeb83029.png)


## 海を作る
### 開発環境
- Vite
    - React + TypeScript
- Three.js r174

### 一部解説

格子状に点群を設置：
```ts
const rows = 50;
const cols = 100;
const spacing = 10;

for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
        const x = (j - cols / 2) * spacing;
        const y = (i - rows / 2) * spacing;
        const z = 0;
        points.push(x, y, z);
    }
}
geometry.setAttribute("position", new THREE.Float32BufferAttribute(points, 3));

const pointCloud = new THREE.Points(geometry, material);
// pointCloud.visible = false;
scene.add(pointCloud);
```

格子状の点群を描画し，それらの座標をsin関数とcos関数を組み合わせて変化させ，海面の揺らぎを表現：
```ts
requestAnimationFrame(animate);
const positions = geometry.attributes.position.array as Float32Array;
const time = performance.now() * 0.001;

for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
        const index = (i * cols + j) * 3 + 2;
        positions[index] = (Math.sin(time + i * 0.2) * Math.cos(time * 0.3 + j * 0.2) +
            Math.sin(time * 0.5 + i * 0.4) * Math.cos(time * 0.6 + j * 0.4)) * 3;
    }
}
geometry.attributes.position.needsUpdate = true;
```

各点から隣り合う縦・横・（ひとつの）斜め方向に線をつなぎ，海面を表現：
```ts
function updateLines() {
    linePositions.length = 0;
    const positions = geometry.attributes.position.array as Float32Array;
    
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            const index = (i * cols + j) * 3;
            const x = positions[index];
            const y = positions[index + 1];
            const z = positions[index + 2];
    
            if (j < cols - 1) {
                linePositions.push(x, y, z, positions[index + 3], positions[index + 4], positions[index + 5]);
            }
            if (i < rows - 1) {
                const belowIndex = ((i + 1) * cols + j) * 3;
                linePositions.push(x, y, z, positions[belowIndex], positions[belowIndex + 1], positions[belowIndex + 2]);
            }
            if (i < rows - 1 && j < cols - 1) {
                const diagonalIndex = ((i + 1) * cols + (j + 1)) * 3;
                linePositions.push(x, y, z, positions[diagonalIndex], positions[diagonalIndex + 1], positions[diagonalIndex + 2]);
            }
        }
    }
    lineGeometry.setAttribute("position", new THREE.Float32BufferAttribute(linePositions, 3));
    lineGeometry.attributes.position.needsUpdate = true;
}

const animate = () => {
    updateLines();
};
```

点群は消すかどうか迷いましたが，光を反射しているみたいできれいなので残すようにしました．月とかを設置するようにしたい．

## ビルド & デプロイ
色々試したせいで余計なコードが含まれているかもしれないです．

手元でビルドするのではなく，リポジトリにpushしたとき，`GitHub Actions`内の仮想環境（ubuntu-latest）上でビルドするようにしました．手動でビルドするのが面倒だからこうしてみたけど，時間かかるしエラーが出るかもしれないことを考えるとあんまりよくないのかもしれない．

```yml
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build
        env:
          VITE_BASE: '/Ocean/'
          GITHUB_PAGES: true

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist
```

## おわりに
わざわざコンポーネント化したのは今後これを何かのページでインポートしたかったからです．ChatGPTに提案されたから`useEffect`を使ったけど，`useEffect`を完全に理解できていないから今後勉強する．