aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/mapgen/sphere.py
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-06 23:52:03 +0200
committergodosa <godosa@godosa.eu>2026-10-06 23:52:03 +0200
commit346b1c5195bffc71ceaa9262453e3c189656400b (patch)
tree01ac0d31e2724cd6abcc689a5a228e2cbea2f6cf /mapgen/sphere.py
downloadworldgen-346b1c5195bffc71ceaa9262453e3c189656400b.tar.gz
worldgen-346b1c5195bffc71ceaa9262453e3c189656400b.zip
worldgen: initial public history
Diffstat (limited to 'mapgen/sphere.py')
-rw-r--r--mapgen/sphere.py68
1 files changed, 68 insertions, 0 deletions
diff --git a/mapgen/sphere.py b/mapgen/sphere.py
new file mode 100644
index 0000000..394b9ef
--- /dev/null
+++ b/mapgen/sphere.py
@@ -0,0 +1,68 @@
+"""Unit-sphere geometry and plate kinematics. Vectors are (..., 3); lat/lon in degrees."""
+from __future__ import annotations
+
+import numpy as np
+
+
+def latlon_to_xyz(lat, lon):
+ la, lo = np.radians(lat), np.radians(lon)
+ return np.stack([np.cos(la) * np.cos(lo), np.cos(la) * np.sin(lo), np.sin(la)], axis=-1)
+
+
+def xyz_to_latlon(p):
+ p = p / np.linalg.norm(p, axis=-1, keepdims=True)
+ return np.degrees(np.arcsin(np.clip(p[..., 2], -1, 1))), np.degrees(np.arctan2(p[..., 1], p[..., 0]))
+
+
+def east_north(p):
+ """Local unit east/north vectors. At the poles east is taken as +y (finite, arbitrary)."""
+ p = np.asarray(p, dtype=np.float64)
+ e = np.stack([-p[..., 1], p[..., 0], np.zeros_like(p[..., 0])], axis=-1) # z × p
+ ne = np.linalg.norm(e, axis=-1, keepdims=True)
+ polar = ne[..., 0] < 1e-12
+ e = np.where(polar[..., None], np.array([0.0, 1.0, 0.0]), e / np.maximum(ne, 1e-300))
+ n = np.cross(p, e)
+ return e, n
+
+
+def tangent_dir(p, q):
+ """Unit tangent at p pointing along the great circle toward q."""
+ t = q - np.sum(p * q, axis=-1, keepdims=True) * p
+ return t / np.maximum(np.linalg.norm(t, axis=-1, keepdims=True), 1e-15)
+
+
+def gc_dist_km(p, q, radius_km):
+ return radius_km * np.arccos(np.clip(np.sum(p * q, axis=-1), -1.0, 1.0))
+
+
+def motion_to_omega(lat, lon, azimuth_deg, speed_cm_yr, radius_km):
+ """Angular velocity (rad/yr) that moves the point (lat, lon) along azimuth at speed."""
+ p = latlon_to_xyz(np.array([lat]), np.array([lon]))
+ e, n = east_north(p)
+ az = np.radians(azimuth_deg)
+ v = (np.sin(az) * e[0] + np.cos(az) * n[0]) * (speed_cm_yr / 100.0) # m/yr
+ return np.cross(p[0], v) / (radius_km * 1000.0)
+
+
+def velocity(p, omega, radius_km):
+ """Surface velocity (m/yr) of points p on plates with angular velocity omega (broadcast)."""
+ return np.cross(omega, p) * (radius_km * 1000.0)
+
+
+def rotate_about(p, v, ang):
+ """Rotate tangent vectors v about normals p by ang radians (counter-clockwise seen from outside)."""
+ ang = np.asarray(ang, dtype=np.float64)[..., None]
+ return v * np.cos(ang) + np.cross(p, v) * np.sin(ang)
+
+
+def azimuth_deg(center, p):
+ """Bearing (deg clockwise from north, [0, 360)) from center (3,) to points p (..., 3)."""
+ e, n = east_north(center[None])
+ t = tangent_dir(np.broadcast_to(center, p.shape), p)
+ return np.degrees(np.arctan2(t @ e[0], t @ n[0])) % 360.0
+
+
+def great_circle_point(p0, t0, dist_km, radius_km):
+ """Point reached from p0 moving dist_km along unit tangent t0."""
+ a = dist_km / radius_km
+ return np.cos(a) * p0 + np.sin(a) * t0