viewof traceImageFragShader = {
let neighborsDefines = Object.entries(neighborsEncoding.values)
.map(([key, value]) => `float f${_.startCase(key)} = ${(value / 255).toFixed(32)};`)
.join("\n");
const coordDefines = Object.entries(coordTypeEncoding.values)
.map(([key, value]) => `const float f${_.startCase(key)} = ${(value / 255).toFixed(32)};`)
.join("\n");
const dirKeys4 = ["N", "E", "S", "W"];;
const dirKeys = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
const cAndDirKeys = ["C", ...dirKeys];
const compassPointsN = [[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]];
const compassPoints = {
N: compassPointsN,
E: [...compassPointsN.slice(2), ...compassPointsN.slice(0, 2)],
S: [...compassPointsN.slice(4), ...compassPointsN.slice(0, 4)],
W: [...compassPointsN.slice(6), ...compassPointsN.slice(0, 6)]
}
const compassCenters = {
N: [ 0, 0],
E: [-1, 0],
S: [-1, -1],
W: [ 0, -1]
}
return highlightGlsl.fragment(`#version 300 es
precision mediump float;
// traceImageFragShader
uniform sampler2D uSampler;
uniform ivec4 mapBboxVec;
uniform ivec4 tileBboxVec;
out vec4 fragColor;
/* Common ------------------------------------------------------------------------------ */
struct bbox {
ivec2 xy0;
ivec2 xy1;
};
struct edges {
bool N;
bool E;
bool S;
bool W;
};
bbox getBbox(ivec4 mapBboxVec) {
return bbox(mapBboxVec.xy, mapBboxVec.zw);
}
bool isInsideBbox(ivec2 xy, bbox box) {
return !(any(lessThan(xy, box.xy0)) || any(greaterThan(xy, box.xy1)));
}
edges getEdges(ivec2 xy, bbox mapBbox) {
edges mapEdges = edges(false, false, false, false);
mapEdges.N = (xy.y == mapBbox.xy0.y);
mapEdges.E = (xy.x == mapBbox.xy1.x);
mapEdges.S = (xy.y == mapBbox.xy1.y);
mapEdges.W = (xy.x == mapBbox.xy0.x);
return mapEdges;
}
/* Neighbor finder --------------------------------------------------------------------- */
${neighborsDefines.trim()}
float getEncodedNeighbors(ivec2 xyC, edges mapEdges) {
float encodedNeighbors = 0.0;
vec4 cC = texelFetch(uSampler, xyC, 0);
if (cC.a == 0.0) return encodedNeighbors;
vec4 cN = texelFetchOffset(uSampler, xyC, 0, ivec2( 0, -1));
vec4 cE = texelFetchOffset(uSampler, xyC, 0, ivec2( 1, 0));
vec4 cS = texelFetchOffset(uSampler, xyC, 0, ivec2( 0, 1));
vec4 cW = texelFetchOffset(uSampler, xyC, 0, ivec2(-1, 0));
if (mapEdges.N || cC != cN) encodedNeighbors += fN;
if (mapEdges.E || cC != cE) encodedNeighbors += fE;
if (mapEdges.S || cC != cS) encodedNeighbors += fS;
if (mapEdges.W || cC != cW) encodedNeighbors += fW;
return encodedNeighbors;
}
/* coordType detector ------------------------------------------------------------------ */
${coordDefines.trim()}
struct directions {
${cAndDirKeys.map(k => ` ivec2 ${k};`).join("\n").trim() /* ivec2 C, ivec2 N...NW */}
};
struct directionColors {
${cAndDirKeys.map(k => ` vec4 ${k};`).join("\n").trim() /* vec4 C, vec4 N...NW */}
};
${Object.entries(compassPoints) /* const directions directions[N..W] = ... */
.map(([key, values]) => {
const params = [compassCenters[key], ...values].map(([x,y]) => `ivec2(${x}, ${y})`).join(", ");
return `const directions directions${key} = directions(${params});`
})
.join("\n")}
directionColors readColors(ivec2 xyC, directions dirs) {
ivec2 newXyC = xyC + dirs.C;
vec4 cC = texelFetch(uSampler, newXyC, 0);
return directionColors(cC, ${dirKeys.map(dir => `texelFetch(uSampler, newXyC + dirs.${dir}, 0)`).join(", ") /* texelFetch(uSampler, newXyC + dirs.[N..NW], 0) */});
}
bool isEdgeJunction(ivec2 xyC, edges mapEdges, directionColors dirColors) {
edges neighbors = edges(
dirColors.C.a + dirColors.W.a > 0.0,
dirColors.W.a + dirColors.SW.a > 0.0,
dirColors.N.a + dirColors.NW.a > 0.0,
dirColors.C.a + dirColors.N.a > 0.0
);
return ${dirKeys4.map(dir => `(mapEdges.${dir} && neighbors.${dir})`).join(" || ")};
}
bool isJunction(directionColors dirColors) {
// Count the number of unique colors in the 4px range [C, W, NW, N]
int colorCount = 1;
vec4 uniqueColor1 = dirColors.C;
vec4 uniqueColor2 = vec4(-1.0);
vec4 uniqueColor3 = vec4(-1.0);
if (dirColors.W != uniqueColor1) {
colorCount++;
uniqueColor2 = dirColors.W;
}
if (dirColors.NW != uniqueColor1 && dirColors.NW != uniqueColor2) {
colorCount++;
uniqueColor3 = dirColors.NW;
}
if (dirColors.N != uniqueColor1 && dirColors.N != uniqueColor2 && dirColors.N != uniqueColor3) {
colorCount++;
}
if (colorCount >= 3) {
// Standard junction: 3 distinct colors touch at this coord
return true;
}
if (colorCount == 2 && dirColors.C == dirColors.NW && dirColors.N == dirColors.W) {
// Special case junction: 2 distinct colors in an hourglass shape 4-pixel diagonal hourglass
return true;
}
return false;
}
bool isIntrusion(directionColors dirColors, ivec2 xyC, directions dirs) {
// Assumption: isMaybeCorner test already passed
// Horizontal check
for (int i = 1; i < 5; i++) {
ivec2 xyE = xyC + i * dirs.E;
vec4 cE = texelFetch(uSampler, xyE, 0);
vec4 cNE = texelFetch(uSampler, xyE + dirs.N, 0);
if (cNE == dirColors.C) {
// It advanced north again. This isn't an intrusion.
break;
}
if (cE != dirColors.C) {
// It retreated south without advancing north
return true;
}
}
// Vertical check
for (int i = 1; i < 5; i++) {
ivec2 xyS = xyC + i * dirs.S;
vec4 cS = texelFetch(uSampler, xyS, 0);
vec4 cSW = texelFetch(uSampler, xyS + dirs.W, 0);
if (cSW == dirColors.C) {
// It advanced west again. Not an intrusion.
break;
}
if (cS != dirColors.C) {
// It retreated east without advancing west.
return true;
}
}
return false;
}
bool isMaybeCorner(directionColors dirColors) {
return (dirColors.C != dirColors.W && dirColors.C != dirColors.NW && dirColors.C != dirColors.N);
}
bool isCorner(directionColors dirColors) {
// Assumption: isMaybeCorner test already passed
if (dirColors.C == dirColors.NE || dirColors.C == dirColors.SW) return false;
return (dirColors.C == dirColors.E && dirColors.C == dirColors.SE && dirColors.C == dirColors.S);
}
float getEncodedCoordType(ivec2 xyC, edges mapEdges) {
directions[4] allDirs = directions[4](${dirKeys4.map(k => `directions${k}`).join(", ")});
directionColors dirColorsN = readColors(xyC, directionsN);
// Edge junction test
if (isEdgeJunction(xyC, mapEdges, dirColorsN)) return fJunction;
// Junction test
if (isJunction(dirColorsN)) return fJunction;
// If we're on the south or west edge of the map, only junctions matter
if (mapEdges.S || mapEdges.W) return 0.0;
// Begin more expensive tests
directionColors[4] allDirColors = directionColors[4](dirColorsN, ${d3.range(1,4).map(i => `readColors(xyC, allDirs[${i}])`).join(", ")});
bool[4] allDirMaybes = bool[4](${d3.range(0,4).map(i => `isMaybeCorner(allDirColors[${i}])`).join(", ")});
// Intrusion test
for (int i = 0; i < 4; i++) {
if (!allDirMaybes[i]) continue;
directionColors dirColors = allDirColors[i];
directions dirs = allDirs[i];
ivec2 xyCOffset = xyC + dirs.C;
if (isIntrusion(dirColors, xyCOffset, dirs)) return fIntrusion;
}
// Corner test
for (int i = 0; i < 4; i++) {
if (!allDirMaybes[i]) continue;
directionColors dirColors = allDirColors[i];
if (isCorner(dirColors)) return fCorner;
}
// Default value
return 0.0;
}
/* main -------------------------------------------------------------------------------- */
const float fOpaque = ${(colorOpacityEncoding.values[255] / 255).toFixed(32)};
void main () {
fragColor = vec4(0.0);
ivec2 xyC = ivec2(round(gl_FragCoord.xy - 0.5));
vec4 cC = texelFetch(uSampler, xyC, 0);
bbox mapBbox = getBbox(mapBboxVec);
bbox tileBbox = getBbox(tileBboxVec);
// neighbors
float encodedNeighbors = 0.0;
edges mapEdges = getEdges(xyC, mapBbox);
if (cC.a != 0.0 && isInsideBbox(xyC, tileBbox)) encodedNeighbors = getEncodedNeighbors(xyC, mapEdges);
// coordType
float encodedCoordType = 0.0;
mapBbox.xy1 += ivec2(1);
mapEdges = getEdges(xyC, mapBbox);
if (mapEdges.S || mapEdges.W) tileBbox.xy1 += ivec2(1);
if (isInsideBbox(xyC, tileBbox)) encodedCoordType = getEncodedCoordType(xyC, mapEdges);
// If there's no trace data, exit
if (encodedNeighbors + encodedCoordType == 0.0) return;
// combine
float encodedColorOpacity = 0.0;
if (encodedNeighbors > 0.0) {
fragColor.rgb = cC.rgb;
encodedColorOpacity = fOpaque;
}
fragColor.a = encodedNeighbors + encodedCoordType + encodedColorOpacity;
}`);
}