異なる質量を持つボールが3D空間内で衝突し、反発する様子をシミュレートするゲーム。
衝突の際に、異なる質量のボールが様々な方向から衝突する場合の精密な反発シミュレーションを行うために、運動量保存の法則とエネルギー保存の法則に基づく計算を行っています。
コードをメモ帳などのテキストエディタに貼り付け、ファイルを「index.html」などの拡張子が.htmlのファイルとして保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3次元ボールシミュレーション(精密な衝突計算)</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000; /* 背景色を黒に設定 */
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<!-- Three.js ライブラリを読み込む -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// キャンバス要素を取得
const canvas = document.getElementById('gameCanvas');
const width = window.innerWidth;
const height = window.innerHeight;
// シーン、カメラ、レンダラーの作成
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(width, height); // キャンバスのサイズをウィンドウサイズに合わせる
document.body.appendChild(renderer.domElement);
camera.position.z = 500; // カメラをZ軸上に500だけ離して配置
// ベクトル計算を行うためのヘルパー関数群
// ベクトルの減算
function vectorSubtract(v1, v2) {
return new THREE.Vector3(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
}
// ベクトルの加算
function vectorAdd(v1, v2) {
return new THREE.Vector3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
}
// ベクトルのスカラー倍
function vectorMultiplyScalar(v, scalar) {
return new THREE.Vector3(v.x * scalar, v.y * scalar, v.z * scalar);
}
// ベクトルの内積
function dotProduct(v1, v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
// ボールクラスの定義
class Ball {
constructor(x, y, z, radius, mass, dx, dy, dz, color) {
// ボールの形状を球体で作成
const geometry = new THREE.SphereGeometry(radius, 32, 32);
// ボールの材質を設定し、色をランダムに指定
const material = new THREE.MeshBasicMaterial({ color: color });
this.mesh = new THREE.Mesh(geometry, material);
this.mesh.position.set(x, y, z); // ボールの初期位置を設定
this.radius = radius; // ボールの半径
this.mass = mass; // ボールの質量
this.velocity = new THREE.Vector3(dx, dy, dz); // ボールの初速度
scene.add(this.mesh); // ボールをシーンに追加
}
// ボールの動きを更新するメソッド
update(balls) {
// ボールの位置を速度ベクトルに基づいて更新
this.mesh.position.add(this.velocity);
// 画面の端でボールが反射する処理
if (this.mesh.position.x + this.radius > width / 2 || this.mesh.position.x - this.radius < -width / 2) {
this.velocity.x = -this.velocity.x; // X軸方向の速度を反転
}
if (this.mesh.position.y + this.radius > height / 2 || this.mesh.position.y - this.radius < -height / 2) {
this.velocity.y = -this.velocity.y; // Y軸方向の速度を反転
}
if (this.mesh.position.z + this.radius > 500 || this.mesh.position.z - this.radius < -500) {
this.velocity.z = -this.velocity.z; // Z軸方向の速度を反転
}
// 他のボールとの衝突判定と処理
for (let i = 0; i < balls.length; i++) {
const other = balls[i];
if (this === other) continue; // 自分自身との衝突は無視
// 2つのボール間の位置ベクトルの差を計算
const deltaPos = vectorSubtract(this.mesh.position, other.mesh.position);
const distance = deltaPos.length(); // 2つのボール間の距離を計算
// 衝突が発生しているか判定
if (distance < this.radius + other.radius) {
// 衝突の解決 (運動量保存則に基づく)
const normal = deltaPos.normalize(); // 衝突の法線ベクトルを計算
const relativeVelocity = vectorSubtract(this.velocity, other.velocity); // 相対速度を計算
const velocityAlongNormal = dotProduct(relativeVelocity, normal); // 法線方向の速度成分
if (velocityAlongNormal > 0) continue; // 既に離れている場合は無視
const e = 1.0; // 反発係数 (1.0は完全弾性衝突)
const j = -(1 + e) * velocityAlongNormal / (1 / this.mass + 1 / other.mass);
const impulse = vectorMultiplyScalar(normal, j); // 衝突時のインパルスを計算
this.velocity.add(vectorMultiplyScalar(impulse, 1 / this.mass)); // 自分の速度を更新
other.velocity.sub(vectorMultiplyScalar(impulse, 1 / other.mass)); // 相手の速度を更新
// ボールが重ならないように位置を調整
const overlap = 0.5 * (this.radius + other.radius - distance);
this.mesh.position.add(vectorMultiplyScalar(normal, overlap)); // 自分を動かす
other.mesh.position.sub(vectorMultiplyScalar(normal, overlap)); // 相手を動かす
}
}
}
}
const balls = []; // ボールの配列
const ballCount = 500; // ボールの数
// ランダムな位置、速度、質量でボールを生成
for (let i = 0; i < ballCount; i++) {
const radius = Math.random() * 20 + 10; // ボールの半径をランダムに設定
const mass = radius; // 質量は半径に比例
const x = (Math.random() - 0.5) * width; // X座標の初期位置をランダムに設定
const y = (Math.random() - 0.5) * height; // Y座標の初期位置をランダムに設定
const z = (Math.random() - 0.5) * 1000; // Z座標の初期位置をランダムに設定
const dx = (Math.random() - 0.5) * 4; // X方向の初速度をランダムに設定
const dy = (Math.random() - 0.5) * 4; // Y方向の初速度をランダムに設定
const dz = (Math.random() - 0.5) * 4; // Z方向の初速度をランダムに設定
const color = Math.random() * 0xffffff; // 色をランダムに設定
balls.push(new Ball(x, y, z, radius, mass, dx, dy, dz, color)); // ボールを作成して配列に追加
}
// アニメーションループ
function animate() {
requestAnimationFrame(animate); // 次のフレームで再度animateを呼び出す
balls.forEach(ball => ball.update(balls)); // 各ボールの位置と速度を更新
renderer.render(scene, camera); // シーンをレンダリング
}
animate(); // アニメーションを開始
</script>
</body>
</html>