Open3

three.jsでランダムに複数の球体を作成して動かす

0y00y0

はじめに

成果物は以下のページで確認できます。
https://s4k10503.github.io/my-profile/

GitHubとZennのリンクを貼っているだけページだったので3Dコンテンツを追加してみました。
見た目的に少しリッチになった気がします。

0y00y0

SphereComponent.jsの概要

SphereComponent.jsで3Dの描画を行っています。
以下は大まかな解説です。

インポート部分

コードの最初の部分で必要なライブラリとモジュールをインポートします。

import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { Slider, TextField, Box, Typography, Grid } from '@mui/material';

定数の定義と初期状態の設定

立方体のサイズ、境界制限、およびシーンの初期パラメータ(球体の数、半径、光の強度、カメラのZ位置)を設定しています。

const CUBE_SIZE = 30;
const BOUNDARY_LIMIT = 14.5;
const [params, setParams] = useState({
  sphereCount: 100,
  sphereRadius: 0.5,
  lightIntensity: 0.8,
  cameraZPosition: 45,
});

シーンの初期化

シーン、カメラ、レンダラー、立方体の初期化を行います。

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.z = params.cameraZPosition;
// ...
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);

球体の生成

指定された数の球体を生成し、立方体内にランダムに配置しています。

useEffect(() => {
  // ...初期化、アニメーション、クリーンアップのロジック...
}, [params]);

マウスとリサイズのイベントハンドラ

マウスの動きに基づいてカメラの位置を変更し、ウィンドウのリサイズに対応します。

const onMouseMove = createMouseMoveHandler(scene, cameraRef);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('resize', () => handleResize(camera, renderer));

アニメーションループの作成

球体の動き、球体同士の衝突、立方体の境界との衝突を処理するアニメーションループを作成し、開始します。

const animate = createAnimationLoop(spheres, params.sphereRadius, renderer, scene, camera, BOUNDARY_LIMIT);
animate();

UIの変更ハンドラ

UIの値が変更されたときに状態を更新します。

const handleChange = (name) => (e) => setParams({ ...params, [name]: e.target.value });

UIのレンダリング

3Dシーンの上に配置されるUI部分を描画します。スライダーとテキストフィールドを使用してパラメータを調整します。

return renderUI(containerRef, params, handleChange);

ヘルパー関数

球体の生成、マウス移動の処理、リサイズの処理、アニメーションループの作成、球体の更新、クリーンアップ、UIのレンダリング、文字列のキャピタライズなど、コード内で使用されるヘルパー関数を定義しています。

0y00y0

各ヘルパー関数について

createSphere

指定された半径で新しい球体を作成します。

  • ジオメトリの作成: 三次元形状を定義します。
  • マテリアルの作成: ランダムな色で、透明度や光沢などの材質特性を定義します。
  • メッシュの作成: ジオメトリとマテリアルを組み合わせて、可視化するオブジェクトを作成します。
  • 位置と速度の設定: 球体の初期位置と速度をランダムに設定します。
const createSphere = (radius, cubeSize) => {
  const geometry = new THREE.SphereGeometry(radius, 32, 32);
  const color = Math.random() * 0xffffff;
  const material = new THREE.MeshPhongMaterial({ color, emissive: color, transparent: true, opacity: 0.6 });
  const sphere = new THREE.Mesh(geometry, material);
  sphere.position.set(Math.random() * cubeSize - cubeSize / 2, Math.random() * cubeSize - cubeSize / 2, Math.random() * cubeSize - cubeSize / 2);
  sphere.velocity = new THREE.Vector3((Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1);

  return sphere;
};

createMouseMoveHandler

マウスの動きに応じてカメラの位置を変更します。

  • カーソルの位置の計算: 画面上のカーソル位置から正規化座標を計算します。
  • カメラ位置の更新: カーソルの位置に基づいてカメラの位置を変更します。
  • シーンの方向への注視: カメラをシーンの中心に向けます。
const createMouseMoveHandler = (scene, cameraRef) => (event) => {
  const x = (event.clientX / window.innerWidth) * 2 - 1;
  const y = -(event.clientY / window.innerHeight) * 2 + 1;
  cameraRef.current.position.x = x * 5;
  cameraRef.current.position.y = y * 5;
  cameraRef.current.lookAt(scene.position);
};

handleResize

ウィンドウのリサイズに応じてカメラとレンダラーのサイズを調整します。

  • 新しいサイズの取得: ウィンドウの新しい幅と高さを取得します。
  • カメラアスペクト比の更新: カメラのアスペクト比を新しいサイズに合わせて更新します。
  • プロジェクション行列の更新: カメラのプロジェクション行列を更新します。
  • レンダラーサイズの更新: レンダラーのサイズを新しいウィンドウサイズに合わせて更新します。
const handleResize = (camera, renderer) => {
  const newWidth = window.innerWidth;
  const newHeight = window.innerHeight;
  camera.aspect = newWidth / newHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(newWidth, newHeight);
};