function createPolarDomainColoringShader (opts) {
opts = opts || {}
const magnitudeOctaves = opts.magnitudeOctaves === undefined ? 4 : +opts.magnitudeOctaves;
const phaseOctaves = opts.phaseOctaves === undefined ? 3 : +opts.phaseOctaves;
const phaseMultiplier = opts.phaseMultiplier === undefined ? 4 : +opts.phaseMultiplier;
const magnitudeMultiplier = opts.magnitudeMultiplier === undefined ? 4 : +opts.magnitudeMultiplier;
const argumentColoring = opts.argumentColoring === undefined ? 0.0 : +opts.argumentColoring;
const bias1 = opts.bias1 === undefined ? 0.0 : +opts.bias1;
const bias2 = opts.bias2 === undefined ? 0.0 : +opts.bias2;
return `${glslConstants}
${glslHypot}
${argumentColoring ? `
${glslRainbow}
` : ''}
${glslContrastFunction}
float complexContouringGridFunction (float x) {
return 4.0 * abs(fract(x - 0.5) - 0.5);
}
vec4 domainColoring (vec4 f_df,
vec2 steps,
vec2 scale,
vec2 gridOpacity,
vec2 shadingOpacity,
float lineWidth,
float lineFeather,
vec3 gridColor,
float argumentColoring
) {
float invlog2base, logspacing, logtier, n, invSteps;
vec2 res = scale * vec2(1.0, 1.0 / 6.28) * 20.0 * steps;
// Complex argument, scaled to the range [0, 4]
float carg = atan(f_df.y, f_df.x) * HALF_PI_INV * ${phaseMultiplier.toFixed(1)};
// Reciprocal of the complex magnitude
float cmagRecip = 1.0 / hypot(f_df.xy);
// Normalize z before using it to compute the magnitudes. Without this we lose half
// of the floating point range due to overflow.
vec2 znorm = f_df.xy * cmagRecip;
// Computed as d|f| / dz, evaluated in the +real direction (though any direction works)
float cmagGradientMag = hypot(vec2(dot(znorm, f_df.zw), dot(vec2(znorm.y, -znorm.x), f_df.zw)));
float cargGradientMag = cmagGradientMag * cmagRecip;
// Shade at logarithmically spaced magnitudes
float mappedCmag = -log2(cmagRecip);
float mappedCmagGradientMag = cmagGradientMag * cmagRecip;
// Magnitude steps
invlog2base = 1.0 / log2(steps.x);
logspacing = log2(mappedCmagGradientMag * res.x) * invlog2base;
logspacing = clamp(logspacing, -50.0, 50.0);
logtier = floor(logspacing);
n = log2(abs(mappedCmag)) * invlog2base - logtier;
invSteps = 1.0 / steps.x;
float magOctave0 = pow(steps.x, n) * sign(mappedCmag);
${[...Array(magnitudeOctaves - 1).keys()].map(i =>
`float magOctave${i + 1} = magOctave${i} * invSteps;`).join('\n ')}
${[...Array(magnitudeOctaves + 1).keys()].map(i =>
`float magWeight${i} = ${i === 0 || i === magnitudeOctaves ? '1e-4' : (1 + i * bias1 + bias2 * Math.pow(i, 2)).toFixed(2)};`).join('\n ')}
float width1 = max(0.0, lineWidth - lineFeather);
float width2 = lineWidth + lineFeather;
float w, scaleFactor;
float totalWeight = 0.0;
float magnitudeGrid = 0.0;
float magnitudeShading = 0.0;
scaleFactor = pow(steps.x, logtier) / cargGradientMag * 0.25;
${[...Array(magnitudeOctaves).keys()].map(i =>
`w = mix(magWeight${i}, magWeight${i + 1}, 1.0 - logspacing + logtier);
totalWeight += w;
magnitudeGrid += w * smoothstep(width1, width2, complexContouringGridFunction(magOctave${i}) * scaleFactor);
magnitudeShading += w * domainColoringContrastFunction(-magOctave${i});
scaleFactor *= steps.x;
`).join('\n ')}
magnitudeGrid /= totalWeight;
magnitudeShading /= totalWeight;
// Phase steps
invlog2base = 1.0 / log2(steps.y);
logspacing = log2(cargGradientMag * ${phaseMultiplier.toFixed(1)} * res.y) * invlog2base;
logspacing = clamp(logspacing, -50.0, 50.0);
logtier = floor(logspacing);
n = log2(abs(carg) + 1.0) * invlog2base - logtier;
invSteps = 1.0 / steps.y;
float phaseOctave0 = pow(steps.y, n) * sign(carg);
${[...Array(phaseOctaves - 1).keys()].map(i =>
`float phaseOctave${i + 1} = phaseOctave${i} * invSteps;`).join('\n ')}
${[...Array(phaseOctaves + 1).keys()].map(i =>
`const float phaseWeight${i} = ${i === 0 || i === phaseOctaves ? '1e-4' : (1 + i * bias1 + bias2 * Math.pow(i, 2)).toFixed(4)};`).join('\n ')}
totalWeight = 0.0;
float phaseShading = 0.0;
float phaseGrid = 0.0;
scaleFactor = pow(steps.y, logtier) / (cargGradientMag * ${phaseMultiplier.toFixed(1)}) * 2.0;
${[...Array(phaseOctaves).keys()].map(i =>
`w = mix(phaseWeight${i}, phaseWeight${i + 1}, 1.0 - logspacing + logtier);
totalWeight += w;
phaseGrid += w * smoothstep(width1, width2, complexContouringGridFunction(phaseOctave${i}) * scaleFactor);
phaseShading += w * domainColoringContrastFunction(phaseOctave${i});
scaleFactor *= steps.y;
`).join('\n ')}
phaseGrid /= totalWeight;
phaseShading /= totalWeight;
${argumentColoring ? (
`vec3 result = mix(vec3(1), 0.24 + 0.65 * rainbow(1.25 + carg * ${(1.0 / phaseMultiplier).toFixed(5)}), argumentColoring);`
) : (
`vec3 result = vec3(1);`
)}
float grid = 1.0;
grid = min(grid, 1.0 - (1.0 - magnitudeGrid) * gridOpacity.x);
grid = min(grid, 1.0 - (1.0 - phaseGrid) * gridOpacity.y);
float shading = 1.0 - (shadingOpacity.y * (phaseShading - 0.4) + shadingOpacity.x * (magnitudeShading - 0.4));
result *= shading;
result = mix(gridColor, result, grid);
//float cmag = -log(cmagRecip);
//float fixedMagShading = 0.5 * (1.0 - smoothstep(8.0, 1.0, cmag) * smoothstep(-8.0, -1.0, cmag));
//float lightnessCast = smoothstep(-1.0, 1.0, cmag);
//result = mix(result, vec3(lightnessCast), fixedMagShading);
return vec4(result, 1.0);
}`;
}