【Three.js】球状にオブジェクトを配置する方法2つ

球状にオブジェクトを配置したいときに使える方法を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を計算すると以下の問題が発生する。

  1. 赤道付近は半径が大きく面積が広いため、密度が薄く
  2. 極付近は半径が小さく面積が狭いため、密度が濃く

結果、見た目に偏りが生じる。

ので、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をおすすめする。