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

球状にオブジェクトを配置したいときに使える方法を2つ挙げます。多分もっと色々方法はある。

以下に例として、パーティクルのFloat32Arrayを編集して、複数のパーティクルを均等に配置する。

単体のメッシュを球状に配置する場合、thetaとphiを任意の値に定義すればいい。

前提知識

球座標系(3次元極座標)は緯度・経度をtheta(θ)とphi(φ)で角度を表す。

なんかサイトとか分野によってどっちがthetaかphiかは変わる感じ?

計算方法に関しては、この動画が非常にわかりやすい。
https://www.youtube.com/watch?v=5qXMuaWe-HA

動画と違うところは、赤道が緯度の0度で、y軸が上であること。

今回は緯度をtheta、経度をphiとする。
縦方向がthetaのほうが馴染むしね。

角度

定義

範囲

theta(θ)

緯度。極角。

-π/2 ~ π/2

phi(φ)

経度。方位学。

0 ~ 2π

方法1(汎用)

赤道を緯度0度として、上をy軸に変換し、
上の添付動画のように計算すると、

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.asin(2 * Math.random() - 1) - Math.PI / 2;
 const phi = Math.random() * 2 * Math.PI;
 positionArray[i3 + 0] = radius * Math.cos(theta) * Math.sin(phi);
 positionArray[i3 + 1] = radius * Math.sin(theta);
 positionArray[i3 + 2] = radius * Math.cos(theta) * Math.cos(phi);
}

thetaの計算について

thetaの計算は、範囲が-π/2~π/2でMath.random() * Math.PI - Math.PI / 2とするのが本能的かもしれないし、見た目も少ししか変わらないかもしれない。

ただ、Math.random() * Math.PIでthetaを計算すると以下の問題が発生する。

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

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

ので、asinを用いてsin(theta)を-1から1の間で均等に分布させる2 * Math.random() - 1 は -1 〜 1 の均一乱数

結果として、球面上の点の密度が均等になります

asinとかがわからなかったらhttps://www.koh-fukuzawa.jp/hw1qqg8cssj参照

よりわかりやすく

もしかしたら、緯度にこだわらず、北極の角度を0度、南極の角度を180度として、その範囲で閉じたthetaを定義するほうが、
範囲が-π/2 ~ π/2から0 ~ πになるので計算簡単。

その時の計算方法は以下

x: radius * Math.sin(theta) * Math.sin(phi);
y: radius * Math.cos(theta);
z: radius * Math.sin(theta) * Math.cos(phi);

方法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をおすすめする。