いいね、「ここから実装」が一番楽しいところだね。
オクタヘドラルマップ+ノイズ関数+チャンクサンプリングで、最低限「惑星として破綻しない」Pythonコードを書いてみる。
ここでは:
- 惑星=連続関数
height(dir) - チャンク=その関数のサンプリング
- ノイズは
noiseライブラリ(Perlin)を想定(なければ自作に差し替え可)
という前提でいくね。
1. オクタヘドラルマップ(方向ベクトル → 2D)
import math
from noise import pnoise2 # pip install noise
def dir_to_octa_uv(x, y, z):
# 正規化
length = math.sqrt(x*x + y*y + z*z)
x /= length
y /= length
z /= length
s = abs(x) + abs(y) + abs(z)
x_ = x / s
y_ = y / s
z_ = z / s
if z_ >= 0:
u = x_
v = y_
else:
u = (1 - abs(y_)) * (1 if x_ >= 0 else -1)
v = (1 - abs(x_)) * (1 if y_ >= 0 else -1)
U = (u + 1.0) * 0.5
V = (v + 1.0) * 0.5
return U, V
2. 緯度経度 → 方向ベクトル
def latlon_to_dir(lat_rad, lon_rad):
# 緯度φ, 経度λ
phi = lat_rad
lam = lon_rad
x = math.cos(phi) * math.cos(lam)
y = math.sin(phi)
z = math.cos(phi) * math.sin(lam)
return x, y, z
3. 惑星の height 関数(大陸+山脈)
def planet_height(lat_rad, lon_rad, seed=0):
x, y, z = latlon_to_dir(lat_rad, lon_rad)
U, V = dir_to_octa_uv(x, y, z)
# スケール調整(大陸=低周波、山脈=高周波)
scale_continent = 1.5
scale_mountain = 8.0
c = pnoise2(U * scale_continent, V * scale_continent, octaves=4, repeatx=1024, repeaty=1024, base=seed)
m = pnoise2(U * scale_mountain, V * scale_mountain, octaves=3, repeatx=1024, repeaty=1024, base=seed+100)
# -1〜1 → 0〜1
c = (c + 1.0) * 0.5
m = (m + 1.0) * 0.5
# 大陸+山脈の合成
h = c + m * 0.4
return h # 0〜1
4. チャンクサンプリング(緯度経度グリッド)
ここでは「緯度経度ベースのチャンク」として、
lat0, lon0 を左上とする res×res グリッドをサンプリングする例。
def sample_chunk(lat0_deg, lon0_deg, dlat_deg, dlon_deg, res=64, seed=0):
chunk = [[0.0 for _ in range(res)] for _ in range(res)]
for j in range(res):
for i in range(res):
lat_deg = lat0_deg - j * dlat_deg / res
lon_deg = lon0_deg + i * dlon_deg / res
lat_rad = math.radians(lat_deg)
lon_rad = math.radians(lon_deg)
h = planet_height(lat_rad, lon_rad, seed=seed)
chunk[j][i] = h
return chunk
5. 使い方イメージ
if __name__ == "__main__":
# 例:赤道付近の 10°×10° チャンク
chunk = sample_chunk(
lat0_deg=5.0,
lon0_deg=-5.0,
dlat_deg=10.0,
dlon_deg=10.0,
res=128,
seed=42
)
# ここで chunk を Roblox 用 heightmap に変換したり、
# PNG に書き出したりできる
ここから先は:
- チャンクを「緯度経度」ではなく「オクタ空間のタイル」として切る
- height から「海/陸/山脈/河川」を分類する
- 惑星パラメータ(半径・海面・プレート活動度)を関数に組み込む
みたいな方向に伸ばせる。
このコード、どこを次に「OS 化」したくなる?