nx_henyey_greenstein.nx
buildroot/runtime/nx_henyey_greenstein.nx
about
nx_henyey_greenstein.nx -- anisotropic single-scatter phase function in Q10.
Epoch 2 FIRMAMENT primitive per nxc2/docs/NISHI_GAME_ENGINE_ROADMAP.md.
The Henyey-Greenstein phase function approximates light scattering
off small particles (atmospheric aerosols, water droplets, fog,
pigment). Published in Henyey & Greenstein 1941 "Diffuse Radiation
in the Galaxy" (Astrophysical Journal 93, 70-83) -- pre-1970
astrophysics, public-domain math.
Formula:
P(cos θ, g) = (1 - g^2) / (4π · (1 + g^2 - 2g·cos θ)^1.5)
where:
cos θ in [-1, 1]: scattering angle cosine (1 = forward, -1 = backward)
g in [-1, 1]: asymmetry parameter
g > 0 = forward-scattering (atmospheric haze ~ 0.8)
g = 0 = isotropic
g < 0 = backward-scattering (some pigments)
Output: probability density. Normalized so ∫P dΩ = 1 over sphere.
Range can exceed 1.0 when |g| near 1 and cos θ near sign(g)
(forward/backward peaking). Capped at NX_HG_SATURATED to keep
downstream arithmetic well-behaved.
Fixed-point strategy: keep numerator and denominator in raw
integer space and defer all divisions to a single final step.
Avoids the Q10 precision starvation that would otherwise hit the
(1 + g^2 - 2gc) term as it approaches 0.
Loss audit: the only information-discarding ops are (a) integer
sqrt (_isqrt) which truncates the fractional part, introducing
~1% error at typical g values; (b) clamp at |g| >= NX_HG_G_MAX
to avoid singularity; (c) final integer division. All three are
inherent to fixed-point evaluation of a singular function and are
named here per cardinal rule 25.
genealogy_id: henyey_greenstein_1941_phase
lineage_id: nx_henyey_greenstein_q10
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_tier.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 49 | const NX_MAGIC_1048576: i64 = 1048576 |
| 50 | const NX_MAGIC_1024: i64 = 1024 |
| 51 | const NX_MAGIC_1300: i64 = 1300 |
| 52 | const NX_MAGIC_1700: i64 = 1700 |
| 53 | const NX_MAGIC_9999: i64 = 9999 |
| 57 | const NX_HG_Q: nx_int = 1024 |
| 61 | const NX_HG_FOUR_PI_Q10: nx_int = 12868 |
| 68 | const NX_HG_G_MAX: nx_int = 1023 |
| 74 | const NX_HG_SATURATED: nx_int = 100000 |
| 81 | const NX_HG_BAND_STRONG_BACKWARD: nx_int = 0 // g <= -700 |
| 82 | const NX_HG_BAND_MODERATE_BACKWARD: nx_int = 1 // -700 < g <= -300 |
| 83 | const NX_HG_BAND_WEAK_BACKWARD: nx_int = 2 // -300 < g <= -50 |
| 84 | const NX_HG_BAND_ISOTROPIC: nx_int = 3 // -50 < g < 50 |
| 85 | const NX_HG_BAND_WEAK_FORWARD: nx_int = 4 // 50 <= g < 300 |
| 86 | const NX_HG_BAND_MODERATE_FORWARD: nx_int = 5 // 300 <= g < 700 |
| 87 | const NX_HG_BAND_STRONG_FORWARD: nx_int = 6 // g >= 700 |
| 89 | const NX_HG_THRESH_STRONG_BACK: nx_int = -700 |
| 90 | const NX_HG_THRESH_MOD_BACK: nx_int = -300 |
| 91 | const NX_HG_THRESH_WEAK_BACK: nx_int = -50 |
| 92 | const NX_HG_THRESH_WEAK_FWD: nx_int = 50 |
| 93 | const NX_HG_THRESH_MOD_FWD: nx_int = 300 |
| 94 | const NX_HG_THRESH_STRONG_FWD: nx_int = 700 |
functions
| 97 | func nx_henyey_greenstein_band_is_valid(b: nx_int) -> nx_int called by 1: main |
| 109 | func nx_henyey_greenstein_classify(g_q10: nx_int) -> nx_int called by 1: main |
| 124 | func _isqrt(n: nx_int) -> nx_int |
| 160 | func nx_henyey_greenstein_phase(cos_theta_q10: nx_int, g_q10: nx_int) -> nx_int |
| 195 | func main() -> i64 |