worldgen

git clone https://git.godosa.eu/worldgen

master

raw · 2609 bytes

"""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