๐ŸŒˆ๏ธ apply a wallpaper based on the weather outside
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(apply): physical model, basic functions

Angel Wang (Jul 27, 2026, 3:19 PM -0600) ca7f7028 a66e4aee

+65 -3
+3 -3
TODO.md
··· 41 41 42 42 ### 1. Port the physical model (reuse, don't reinvent) 43 43 44 - - [ ] Port constants: Rayleigh/Mie/ozone coefficients, scale heights, 44 + - [x] Port constants: Rayleigh/Mie/ozone coefficients, scale heights, 45 45 ground/atmosphere radii, `groundAlbedo` โ€” same as `gradient.ts` / 46 46 the `common` shader tab. 47 47 - [ ] Port `getScatteringValues` (density + extinction at a given altitude). 48 - - [ ] Port the phase functions (`rayleighPhase`, `miePhase`). 49 - - [ ] Port `rayIntersectSphere` / `intersectSphere`. 48 + - [x] Port the phase functions (`rayleighPhase`, `miePhase`). 49 + - [x] Port `rayIntersectSphere` / `intersectSphere`. 50 50 - [ ] Decide: **do you need a full LUT texture, or a direct function call?** 51 51 Since you're only evaluating a handful of directions per frame (not a 52 52 whole screen), you likely don't need GPU textures at all โ€” a plain
+62
src/apply/colour.ts
··· 1 + import { Colordx, colordx } from "@colordx/core" 2 + 3 + // constants from Hillaire, section 4 table 1 4 + const RAYLEIGH_SCATTER = [5.802e-6, 13.558e-6, 33.1e-6] // rgb 5 + const MIE_SCATTER = 3.996e-6 6 + const MIE_ABSORB = 4.44e-6 7 + const OZONE_SCATTER = 0 8 + const OZONE_ABSORB = [0.65e-6, 1.881e-6, 0.085e-6] // rgb 9 + 10 + const GROUND_ALBEDO = 0.3 11 + 12 + const RAYLEIGH_SCALE_HEIGHT = 8e3 // metres 13 + const MIE_SCALE_HEIGHT = 1.2e3 // metres 14 + 15 + const GROUND_RADIUS = 6_360e3 // radius of the earth, metres 16 + const TOP_RADIUS = 6_460e3 // radius of the atmosphere, metres 17 + 18 + type Vec3 = [number, number, number]; 19 + 20 + function dot(v1: Vec3, v2: Vec3) { 21 + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; 22 + } 23 + 24 + // ACES tonemapper (Knarkowicz) 25 + function aces(colour: Colordx): Colordx { 26 + Object.keys(colour.toRgb()).forEach((_key, colour) => { 27 + const n = colour * (2.51 * colour + 0.03) 28 + const d = colour * (2.43 * colour + 0.59) + 0.14 29 + return Math.max(0, Math.min(1, n / d)) 30 + }) 31 + return colordx(colour) 32 + } 33 + 34 + function rayleighPhase(angle: number): number { 35 + return (3 * (1 + Math.cos(angle) ** 2)) / (16 * Math.PI); 36 + } 37 + 38 + function miePhase(angle: number): number { 39 + const g = 0.8; 40 + const scale = 3 / (8 * Math.PI); 41 + const num = (1 - g ** 2) * (1 + Math.cos(angle) ** 2); 42 + const denom = 43 + (2 + g ** 2) * (1 + g ** 2 - 2 * g * Math.cos(angle)) ** (3 / 2); 44 + return (scale * num) / denom; 45 + } 46 + 47 + // From "5.3.2 Intersecting Ray or Segment Against Sphere" (Real-Time Collision Detection) 48 + function intersectSphere(rayOrigin: Vec3, direction: Vec3, radius: number): number | null { 49 + // Sphere center is at origin (= 0), so rayOrigin - sphereCenter = rayOrigin 50 + const b = dot(rayOrigin, direction); 51 + const c = dot(rayOrigin, rayOrigin) - (radius ** 2); 52 + const discr = (b ** 2) - c; 53 + if (discr < 0) 54 + // Ray misses sphere 55 + return null; 56 + const t = -b - Math.sqrt(discr); 57 + if (t < 0) 58 + // Ray inside sphere; Use far discriminant 59 + return -b + Math.sqrt(discr); 60 + return t; 61 + } 62 +