three.js Line2 を動的に更新する方法(直線の終点だけ)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2527700/79137cc2-5d40-1353-f229-e2030720d9d7.png)

three.jsで太い線を描画したいのですが、THREE.MeshLineはメンテされていないらしい。three.js exampleのlines_fatで使われているLine2で太い線が描画できるようです。直線だと動的に更新もできるそうなので、簡単に試してみました。

## 参考

https://zenn.dev/naotaro0123/articles/7966554774dad0

https://threejs.org/examples/?q=fat#webgl_lines_fat

https://discourse.threejs.org/t/how-to-update-line2-dynamically/37913


## コード

```html:index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <title>three.js: How to dynamically update Line2</title>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
  </style>
</head>

<body>
  <script type="importmap">
    {
      "imports": {
        "three": "https://unpkg.com/three@0.164.1/build/three.module.js",
        "three/addons/misc/Timer": "https://unpkg.com/three@0.164.1/examples/jsm/misc/Timer.js",
        "three/addons/controls/OrbitControls": "https://unpkg.com/three@0.164.1/examples/jsm/controls/OrbitControls.js",
        "three/addons/lines/Line2":  "https://unpkg.com/three@0.164.1/examples/jsm/lines/Line2.js",
        "three/addons/lines/LineMaterial":  "https://unpkg.com/three@0.164.1/examples/jsm/lines/LineMaterial.js",
        "three/addons/lines/LineGeometry":  "https://unpkg.com/three@0.164.1/examples/jsm/lines/LineGeometry.js"
      }
    }
  </script>
  <script type="module">
    import * as THREE from 'three';
    import { Timer } from 'three/addons/misc/Timer';
    import { OrbitControls } from 'three/addons/controls/OrbitControls';
    import { Line2 } from 'three/addons/lines/Line2';
    import { LineMaterial } from 'three/addons/lines/LineMaterial';
    import { LineGeometry } from 'three/addons/lines/LineGeometry';

    const renderer = new THREE.WebGLRenderer();
    document.body.appendChild(renderer.domElement);
    const scene = new THREE.Scene();
    // グリッド表示
    scene.add(new THREE.GridHelper(60, 6));
    // カメラ
    const camera = new THREE.PerspectiveCamera(
      40,
      window.innerWidth / window.innerHeight,
      1,
      1000
    );
    camera.position.set(0, 200, 0);
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.minDistance = 10;
    controls.maxDistance = 500;

    // Line2: https://threejs.org/examples/#webgl_lines_fat
    // How to update Line2 dynamically?: https://discourse.threejs.org/t/how-to-update-line2-dynamically/37913
    const line = new class {
      constructor() {
        this.geometry = new LineGeometry();
        this.geometry.setPositions([0, 30, -30, 0, 0, 0]);
        this.material = new LineMaterial({
          color: 0xff0000,
          linewidth: 20,
        });
        scene.add(new Line2(this.geometry, this.material));
      }
      // Line2を動的に更新
      update(date) {
        // 現在の秒数からs終点を変更
        const theta = (date.getSeconds() + date.getMilliseconds() / 1000) / 30 * Math.PI;
        this.geometry.attributes.instanceEnd.setX(0, Math.sin(theta) * 30);
        this.geometry.attributes.instanceEnd.setZ(0, Math.cos(theta) * -30);
        this.geometry.attributes.instanceEnd.needsUpdate = true
      }
      // ラインの解像度: ラインの幅の基準になる
      setResolution(width, height) {
        this.material.resolution.set(width, height);
      }
    }();

    const timer = new Timer();
    const ms = Date.now();
    function animate(timestamp) {
      requestAnimationFrame(animate);
      timer.update(timestamp);

      line.update(new Date(ms + timer.getElapsed() * 1000));

      controls.update();
      renderer.render(scene, camera);
    }
    animate();

    function onWindowResize() {
      const width = window.innerWidth;
      const height = window.innerHeight;
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(width, height);

      line.setResolution(width, height);

      camera.aspect = width / height;
      camera.updateProjectionMatrix();
    }
    onWindowResize();
    window.addEventListener('resize', onWindowResize);
  </script>
</body>

</html>
```

## Line2

Line2の部分は以下のところです。`setPositions`で2点のxyzを指定しています。
線の幅は、20としました。この20は、materialのresolutionで指定する幅や高さから計算されますので`material.resolution.set`も設定が必要でした。line_fatのサンプルでは、animateの中でresolutionを設定していましたが、このコードでは、resizeの時に設定しています。

```javascript:Line2の設定部分
        this.geometry = new LineGeometry();
        this.geometry.setPositions([0, 30, -30, 0, 0, 0]);
        this.material = new LineMaterial({
          color: 0xff0000,
          linewidth: 20,
        });
        scene.add(new Line2(this.geometry, this.material));
```

```javascript:Line2の解像度設定
        this.material.resolution.set(width, height);
```

Line2の動的更新は、`instanceStart`や`instanceEnd`に位置を設定し、needsUpdate を呼ぶことでできました。
このコードでは、XZ面に終点が円を描くように動きます。

```javascript:Line2の動的更新部分
        const theta = (date.getSeconds() + date.getMilliseconds() / 1000) / 30 * Math.PI;
        this.geometry.attributes.instanceEnd.setX(0, Math.sin(theta) * 30);
        this.geometry.attributes.instanceEnd.setZ(0, Math.cos(theta) * -30);
        this.geometry.attributes.instanceEnd.needsUpdate = true
```

## おわりに

直線の終点だけ動かしています。以下のサンプルのようにもっといろいろ変化できるのかと思うのですが、そこまで理解ができませんでした。ちょっと力不足

https://discourse.threejs.org/t/fatlines-noise/36892