Open3
three.jsでランダムに複数の球体を作成して動かす
はじめに
成果物は以下のページで確認できます。
GitHubとZennのリンクを貼っているだけページだったので3Dコンテンツを追加してみました。
見た目的に少しリッチになった気がします。
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のレンダリング、文字列のキャピタライズなど、コード内で使用されるヘルパー関数を定義しています。
各ヘルパー関数について
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);
};