← Natura·Docs/Modules/Source

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 };
});