laws/blackbody.js
45 lines · in natura-src-1.0.0.zip as natura/laws/blackbody.js · a module · its entry in the module reference · its test: laws/blackbody-test.js
// laws/blackbody.js -- the colour of HEAT, the second member of the neap.framework LAWS library (2026-08-22).
//
// A hot thing GLOWS, and its colour is set by ONE number: its temperature. Planck's law gives the spectrum
// a black body radiates at temperature T, and pushed through the eye that spectrum has a colour -- the
// Planckian locus, sweeping deep RED (a cooling ember, ~1000K) through ORANGE and WHITE (the Sun, ~5800K)
// into BLUE-WHITE (a hot star, >10000K). This is the sibling of laws/interference-colour: both turn PHYSICS
// into a PERCEIVED COLOUR, and this one REUSES that module's CIE-1931 colour pipeline (xbar/ybar/zbar +
// XYZ->sRGB) -- the point of a laws library is that the laws COMPOSE.
//
// Captures the colour-of-temperature helper the pieces keep re-deriving inline (embers/fire, heated iron in
// The Forge, star colour). Render-free + node-tested (the framework bar). Pure; no Date.now / Math.random. UMD.
(function (root, factory) {
var ic = (typeof module !== 'undefined' && module.exports) ? require('./interference-colour.js')
: (root.NaturaLaws && root.NaturaLaws.interferenceColour);
if (typeof module !== 'undefined' && module.exports) module.exports = factory(ic);
else (root.NaturaLaws = root.NaturaLaws || {}).blackbody = factory(ic);
})(typeof self !== 'undefined' ? self : this, function (IC) {
'use strict';
var LMIN = 380, LMAX = 720, STEP = 5;
var C2 = 1.438e7; // second radiation constant hc/k, in nm*K
// Planck's law (RELATIVE spectral radiance -- we only need the SHAPE for the colour): B(lambda,T).
// lambda in nm, T in kelvin.
function planck(lambda, T) {
return 1 / (Math.pow(lambda, 5) * (Math.exp(C2 / (lambda * T)) - 1));
}
// the perceived colour of a black body at temperature T (K) -> sRGB [r,g,b] 0-255.
// integrated against the SHARED CIE-1931 CMF (from laws/interference-colour), then normalised to a common
// luminance so what varies with T is the HUE -- the pure Planckian LOCUS (an emitter, not a reflector, so
// this does NOT white-balance against an illuminant the way the interference-colour law does).
function colourOf(T) {
var X = 0, Y = 0, Z = 0;
for (var l = LMIN; l <= LMAX; l += STEP) {
var B = planck(l, T);
X += B * IC.xbar(l); Y += B * IC.ybar(l); Z += B * IC.zbar(l);
}
if (Y <= 0) Y = 1e-9;
var s = 1 / Y; // constant-luminance normalise -> the locus hue at each temperature
return IC.xyzToSrgb(X * s, Y * s, Z * s);
}
return { planck: planck, colourOf: colourOf, C2: C2 };
});