shadowData = {
const angle = Math.PI / 4;
const minElev = Math.max(0, d3.min(elevationData));
const data = [];
const mPerPx = Math.pow(2, 15 - map.getZoom());
const getShadowForPixel = ({x, y, currentShadow, shadowDistance, distanceTraveled, previousElev, peak, decay}) => {
const n = y * width + x,
elev = elevationData[n];
if (elev >= peak && elev > previousElev) {
const newShadowDistance = ((elev - minElev) / Math.tan(angle)) / mPerPx;
return {
newPeak: elev,
newShadowDistance,
newDistanceTraveled: 0,
newCurrentShadow: 1,
newPreviousElev: elev,
newDecay: Math.SQRT2/newShadowDistance,
shadowValue: 0,
}
} else if (distanceTraveled < shadowDistance && decay <= 1) {
// in this case, we're still working through the shadow of a prior peak
// fade the shadow from its previous value a bit, and increment the distance tally
let newCurrentShadow = currentShadow - decay,
newDistanceTraveled = distanceTraveled + Math.SQRT2 // sqrt2 = distance of 1 pixel, diagonally
return {
newPeak: peak, // no change in peak
newShadowDistance: shadowDistance, // no change
newDistanceTraveled, // increased abobe
newCurrentShadow, // set above
newPreviousElev: elev,
newDecay: decay, // no change
shadowValue: Math.sqrt(1 - (elev/peak)) * newCurrentShadow,
/*
the actual shadow value (above) is further refined based on how close current elevation
is to peak elevation. the closer it is in elevation, the weaker the shadow. */
}
}
//or maybe there's just no shadow because we're far enough from a peak and haven't hit another one yet
return {
newPeak: 0,
newShadowDistance: 0,
newDistanceTraveled: 0,
newCurrentShadow: 0,
newPreviousElev: elev,
newDecay: .05,
shadowValue: 0,
}
}
// gets the shadows along diagonal lines, in the direction of light
// starts from pixel at (col, row) and then traverses a diagonal line, getting shadow data for each pixel
// keeps track of the running values like peak, currentShadow, etc. to pass to the pixel function
const getShadowsForDiagonal = (col, row) => {
let x = col;
let y = row;
let peak = 0;
let shadowDistance = 0;
let distanceTraveled = 0;
let currentShadow = 0;
let previousElev = 0;
let decay = .05;
while (x < width - 1 && y < height - 1) {
const {
newPeak,
newShadowDistance,
newDistanceTraveled,
newCurrentShadow,
newPreviousElev,
shadowValue,
newDecay,
} = getShadowForPixel({
x: x,
y: y,
currentShadow,
shadowDistance,
distanceTraveled,
previousElev,
peak,
decay,
});
currentShadow = newCurrentShadow;
shadowDistance = newShadowDistance;
distanceTraveled = newDistanceTraveled;
previousElev = newPreviousElev;
peak = newPeak;
decay = newDecay;
data[y * width + x] = shadowValue;
// this assumes a northwestern light source
x = x + 1;
y = y + 1;
}
}
// get shadows from "sun rays" starting at each pixel along the top
for (let col = 0; col < width - 1; col += 1) {
const row = 0;
getShadowsForDiagonal(col, row);
}
// and starting at each pixel down the left side
for (let row = 1; row < height - 1; row += 1) {
const col = 0;
getShadowsForDiagonal(col, row);
}
return data;
}