【Three.js】球状にオブジェクトを配置する方法2つ
2025/12/14
球状にオブジェクトを配置したいときに使える方法を2つ挙げます。多分もっと色々方法はある。
以下に例として、パーティクルのFloat32Arrayを編集して均等に配置する。他のMeshとかでもおなじでしょう。
前提知識
球座標系(3次元極座標)は緯度・経度をtheta(θ)とphi(φ)で角度を表す。
なんかサイトとか分野によってどっちがthetaかphiかは変わる感じ?
この動画が非常にわかりやすい。
https://www.youtube.com/watch?v=5qXMuaWe-HA
ので、この動画にthetaとphiを倣う。
今回は緯度をtheta、経度をphiとする。
縦方向がthetaのほうが馴染むしね。
角度 | 定義 | 範囲 |
|---|---|---|
theta(θ) | 緯度。極角。 | 0 ~ π |
phi(φ) | 経度。方位学。 | 0 ~ 2π |
方法1(汎用)
上の添付動画の通り。
const count = 100;
const radius = 2;
const positionArray = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const i3 = i * 3;
const theta = Math.acos(2 * Math.random() - 1);
const phi = Math.random() * 2 * Math.PI;
const rSinTheta = radius * Math.sin(theta);
positionArray[i3 + 0] = rSinTheta * Math.cos(phi);
positionArray[i3 + 1] = rSinTheta * Math.sin(phi);
positionArray[i3 + 2] = radius * Math.cos(theta);
}thetaの計算について
thetaの計算は、Math.random() * Math.PIとするのが本能的かもしれないし、見た目も少ししか変わらないかもしれない。
もちろん、y = acos(x)はcos(y)がxの時のyの値を返すので、返ってくる値の範囲は変わらない。(0 ~ π)
ただ、Math.random() * Math.PIでthetaを計算すると以下の問題が発生する。
- 赤道付近は半径が大きく面積が広いため、密度が薄く
- 極付近は半径が小さく面積が狭いため、密度が濃く
結果、見た目に偏りが生じる。
ので、acosを用いてcos(theta)を-1から1の間で均等に分布させる。2 * Math.random() - 1 は -1 〜 1 の均一乱数
結果として、球面上の点の密度が均等になります。
acosとかがわからなかったらhttps://www.koh-fukuzawa.jp/hw1qqg8cssj参照
方法2(ライブラリ)
three.jsにお力を借りる。
※Sphericalにカーソル当てると、Spherical(radius?: number, phi?: number, theta?: number)とでる。phiとthetaの定義が上と違うようなので注意。だって上の方がわかりやすかったんだもん
const count = 100;
const radius = 2;
const positionArray = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const i3 = i * 3;
const theta = Math.acos(2 * Math.random() - 1);
const phi = Math.random() * 2 * Math.PI;
const spherical = new THREE.Spherical(radius, theta, phi);
const position = new THREE.Vector3();
position.setFromSpherical(spherical);
positionArray[i3 + 0] = position.x;
positionArray[i3 + 1] = position.y;
positionArray[i3 + 2] = position.z;
}まあ、3次元の理解を深めるためにも方法1をおすすめする。