Task 3 of 5
Hue is an angle, and angles wrap. That one fact quietly breaks arithmetic you have been doing safely your entire career.
Take two readings of the same pixel, from two frames: 350° and 10°. Both are red. They are 20° apart on the wheel. Average them the obvious way and you get 180° — cyan. Not a slightly-off red: the opposite colour, out of two inputs that were nearly identical. Every mean, every interpolation, every blur that touches a hue channel has this hole in it.
The repair is to stop pretending the number line has no seam. Take b − a,
fold it onto the short way round by adding or subtracting 360 until it lands in
−180 … 180, walk half of it from a, and fold the answer back into 0 … 360.
Four lines, no cleverness — just refusing to subtract two angles as if they were
distances.
hueA[i] and
hueB[i] the short way round, as an angle in 0 … 360.output: [64], indexed with this.thread.xb − a into −180 … 180 before you halve it0 … 360, none negative and none 360 or more350 and 10, must come out at 0 — not 180The difference between two angles is only ever at most 180°. If the plain subtraction gives you more than that, you went the long way round:
let d = b[this.thread.x] - a[this.thread.x];
if (d > 180) { d = d - 360; }
if (d < -180) { d = d + 360; }Walk half of that difference from a, then bring the result back
onto the wheel. One check each way is enough, because a is already in
range and you moved it by at most 90°:
let m = a[this.thread.x] + d / 2;
if (m < 0) { m = m + 360; }
if (m >= 360) { m = m - 360; }
return m;The first eight pairs of the input straddle the seam deliberately.
(350, 10) and (10, 350) must both give 0,
(358.5, 4.5) gives 1.5, and (340, 20) gives
0 as well. If those four come out near 180, the fold is missing.
atan2 of the total. That is what
circular statistics libraries do, what CUDA and WGSL kernels do, and it removes the branches
entirely, which on a GPU is worth having.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.