Files
Foundry-VTT-Docker/resources/app/client/pixi/webgl/shaders/samplers/token-ring.js
2025-01-04 00:34:03 +01:00

340 lines
11 KiB
JavaScript

/**
* The shader definition which powers the TokenRing.
*/
class TokenRingSamplerShader extends PrimaryBaseSamplerShader {
/** @override */
static classPluginName = "tokenRingBatch";
/* -------------------------------------------- */
/** @override */
static pausable = false;
/* -------------------------------------------- */
/** @inheritdoc */
static batchGeometry = [
...(super.batchGeometry ?? []),
{id: "aRingTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aBackgroundTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aRingColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aBackgroundColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aStates", size: 1, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aScaleCorrection", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aRingColorBand", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aTextureScaleCorrection", size: 1, normalized: false, type: PIXI.TYPES.FLOAT}
];
/* -------------------------------------------- */
/** @inheritdoc */
static batchVertexSize = super.batchVertexSize + 12;
/* -------------------------------------------- */
/** @inheritdoc */
static reservedTextureUnits = super.reservedTextureUnits + 1;
/* -------------------------------------------- */
/**
* A null UVs array used for nulled texture position.
* @type {Float32Array}
*/
static nullUvs = new Float32Array([0, 0, 0, 0, 0, 0, 0, 0]);
/* -------------------------------------------- */
/** @inheritdoc */
static batchDefaultUniforms(maxTex) {
return {
...super.batchDefaultUniforms(maxTex),
tokenRingTexture: maxTex + super.reservedTextureUnits,
time: 0
};
}
/* -------------------------------------------- */
/** @override */
static _preRenderBatch(batchRenderer) {
super._preRenderBatch(batchRenderer);
batchRenderer.renderer.texture.bind(CONFIG.Token.ring.ringClass.baseTexture,
batchRenderer.uniforms.tokenRingTexture);
batchRenderer.uniforms.time = canvas.app.ticker.lastTime / 1000;
batchRenderer.uniforms.debugColorBands = CONFIG.Token.ring.debugColorBands;
}
/* ---------------------------------------- */
/** @inheritdoc */
static _packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
super._packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex);
const {float32View, uint32View} = attributeBuffer;
// Prepare token ring attributes
const vertexData = element.vertexData;
const trConfig = CONFIG.Token.ringClass;
const object = element.object.object || {};
const ringColor = PIXI.Color.shared.setValue(object.ring?.ringColorLittleEndian ?? 0xFFFFFF).toNumber();
const bkgColor = PIXI.Color.shared.setValue(object.ring?.bkgColorLittleEndian ?? 0xFFFFFF).toNumber();
const ringUvsFloat = object.ring?.ringUVs ?? trConfig.tokenRingSamplerShader.nullUvs;
const bkgUvsFloat = object.ring?.bkgUVs ?? trConfig.tokenRingSamplerShader.nullUvs;
const states = (object.ring?.effects ?? 0) + 0.5;
const scaleCorrectionX = (object.ring?.scaleCorrection ?? 1) * (object.ring?.scaleAdjustmentX ?? 1);
const scaleCorrectionY = (object.ring?.scaleCorrection ?? 1) * (object.ring?.scaleAdjustmentY ?? 1);
const colorBandRadiusStart = object.ring?.colorBand.startRadius ?? 0;
const colorBandRadiusEnd = object.ring?.colorBand.endRadius ?? 0;
const textureScaleAdjustment = object.ring?.textureScaleAdjustment ?? 1;
// Write attributes into buffer
const vertexSize = this.vertexSize;
const attributeOffset = PrimaryBaseSamplerShader.batchVertexSize;
for ( let i = 0, j = attributeOffset; i < vertexData.length; i += 2, j += vertexSize ) {
let k = aIndex + j;
float32View[k++] = ringUvsFloat[i];
float32View[k++] = ringUvsFloat[i + 1];
float32View[k++] = bkgUvsFloat[i];
float32View[k++] = bkgUvsFloat[i + 1];
uint32View[k++] = ringColor;
uint32View[k++] = bkgColor;
float32View[k++] = states;
float32View[k++] = scaleCorrectionX;
float32View[k++] = scaleCorrectionY;
float32View[k++] = colorBandRadiusStart;
float32View[k++] = colorBandRadiusEnd;
float32View[k++] = textureScaleAdjustment;
}
}
/* ---------------------------------------- */
/* GLSL Shader Code */
/* ---------------------------------------- */
/**
* The fragment shader header.
* @type {string}
*/
static #FRAG_HEADER = `
const uint STATE_RING_PULSE = 0x02U;
const uint STATE_RING_GRADIENT = 0x04U;
const uint STATE_BKG_WAVE = 0x08U;
const uint STATE_INVISIBLE = 0x10U;
/* -------------------------------------------- */
bool hasState(in uint state) {
return (vStates & state) == state;
}
/* -------------------------------------------- */
vec2 rotation(in vec2 uv, in float a) {
uv -= 0.5;
float s = sin(a);
float c = cos(a);
return uv * mat2(c, -s, s, c) + 0.5;
}
/* -------------------------------------------- */
float normalizedCos(in float val) {
return (cos(val) + 1.0) * 0.5;
}
/* -------------------------------------------- */
float wave(in float dist) {
float sinWave = 0.5 * (sin(-time * 4.0 + dist * 100.0) + 1.0);
return mix(1.0, 0.55 * sinWave + 0.8, clamp(1.0 - dist, 0.0, 1.0));
}
/* -------------------------------------------- */
vec4 colorizeTokenRing(in vec4 tokenRing, in float dist) {
if ( tokenRing.a > 0.0 ) tokenRing.rgb /= tokenRing.a;
vec3 rcol = hasState(STATE_RING_PULSE)
? mix(tokenRing.rrr, tokenRing.rrr * 0.35, (cos(time * 2.0) + 1.0) * 0.5)
: tokenRing.rrr;
vec3 ccol = vRingColor * rcol;
vec3 gcol = hasState(STATE_RING_GRADIENT)
? mix(ccol, vBackgroundColor * tokenRing.r, smoothstep(0.0, 1.0, dot(rotation(vTextureCoord, time), vec2(0.5))))
: ccol;
vec3 col = mix(tokenRing.rgb, gcol, step(vRingColorBand.x, dist) - step(vRingColorBand.y, dist));
return vec4(col, 1.0) * tokenRing.a;
}
/* -------------------------------------------- */
vec4 colorizeTokenBackground(in vec4 tokenBackground, in float dist) {
if (tokenBackground.a > 0.0) tokenBackground.rgb /= tokenBackground.a;
float wave = hasState(STATE_BKG_WAVE) ? (0.5 + wave(dist) * 1.5) : 1.0;
vec3 bgColor = tokenBackground.rgb;
vec3 tintColor = vBackgroundColor.rgb;
vec3 resultColor;
// Overlay blend mode
if ( tintColor == vec3(1.0, 1.0, 1.0) ) {
// If tint color is pure white, keep the original background color
resultColor = bgColor;
} else {
// Overlay blend mode
for ( int i = 0; i < 3; i++ ) {
if ( bgColor[i] < 0.5 ) resultColor[i] = 2.0 * bgColor[i] * tintColor[i];
else resultColor[i] = 1.0 - 2.0 * (1.0 - bgColor[i]) * (1.0 - tintColor[i]);
}
}
return vec4(resultColor, 1.0) * tokenBackground.a * wave;
}
/* -------------------------------------------- */
vec4 processTokenColor(in vec4 finalColor) {
if ( !hasState(STATE_INVISIBLE) ) return finalColor;
// Computing halo
float lum = perceivedBrightness(finalColor.rgb);
vec3 haloColor = vec3(lum) * vec3(0.5, 1.0, 1.0);
// Construct final image
return vec4(haloColor, 1.0) * finalColor.a
* (0.55 + normalizedCos(time * 2.0) * 0.25);
}
/* -------------------------------------------- */
vec4 blend(vec4 src, vec4 dst) {
return src + (dst * (1.0 - src.a));
}
/* -------------------------------------------- */
float getTokenTextureClip() {
return step(3.5,
step(0.0, vTextureCoord.x) +
step(0.0, vTextureCoord.y) +
step(vTextureCoord.x, 1.0) +
step(vTextureCoord.y, 1.0));
}
`;
/* ---------------------------------------- */
/**
* Fragment shader body.
* @type {string}
*/
static #FRAG_MAIN = `
vec4 color;
vec4 result;
%forloop%
if ( vStates == 0U ) result = color * vColor;
else {
// Compute distances
vec2 scaledDistVec = (vOrigTextureCoord - 0.5) * 2.0 * vScaleCorrection;
// Euclidean distance computation
float dist = length(scaledDistVec);
// Rectangular distance computation
vec2 absScaledDistVec = abs(scaledDistVec);
float rectangularDist = max(absScaledDistVec.x, absScaledDistVec.y);
// Clip token texture color (necessary when a mesh is padded on x and/or y axis)
color *= getTokenTextureClip();
// Blend token texture, token ring and token background
result = blend(
processTokenColor(color * (vColor / vColor.a)),
blend(
colorizeTokenRing(texture(tokenRingTexture, vRingTextureCoord), dist),
colorizeTokenBackground(texture(tokenRingTexture, vBackgroundTextureCoord), dist)
) * step(rectangularDist, 1.0)
) * vColor.a;
}
`;
/* ---------------------------------------- */
/**
* Fragment shader body for debug code.
* @type {string}
*/
static #FRAG_MAIN_DEBUG = `
if ( debugColorBands ) {
vec2 scaledDistVec = (vTextureCoord - 0.5) * 2.0 * vScaleCorrection;
float dist = length(scaledDistVec);
result.rgb += vec3(0.0, 0.5, 0.0) * (step(vRingColorBand.x, dist) - step(vRingColorBand.y, dist));
}
`;
/* ---------------------------------------- */
/** @override */
static _batchVertexShader = `
in vec2 aRingTextureCoord;
in vec2 aBackgroundTextureCoord;
in vec2 aScaleCorrection;
in vec2 aRingColorBand;
in vec4 aRingColor;
in vec4 aBackgroundColor;
in float aTextureScaleCorrection;
in float aStates;
out vec2 vRingTextureCoord;
out vec2 vBackgroundTextureCoord;
out vec2 vOrigTextureCoord;
flat out vec2 vRingColorBand;
flat out vec3 vRingColor;
flat out vec3 vBackgroundColor;
flat out vec2 vScaleCorrection;
flat out uint vStates;
void _main(out vec2 vertexPosition, out vec2 textureCoord, out vec4 color) {
vRingTextureCoord = aRingTextureCoord;
vBackgroundTextureCoord = aBackgroundTextureCoord;
vRingColor = aRingColor.rgb;
vBackgroundColor = aBackgroundColor.rgb;
vStates = uint(aStates);
vScaleCorrection = aScaleCorrection;
vRingColorBand = aRingColorBand;
vOrigTextureCoord = aTextureCoord;
vertexPosition = (translationMatrix * vec3(aVertexPosition, 1.0)).xy;
textureCoord = (aTextureCoord - 0.5) * aTextureScaleCorrection + 0.5;
color = aColor * tint;
}
`;
/* -------------------------------------------- */
/** @override */
static _batchFragmentShader = `
in vec2 vRingTextureCoord;
in vec2 vBackgroundTextureCoord;
in vec2 vOrigTextureCoord;
flat in vec3 vRingColor;
flat in vec3 vBackgroundColor;
flat in vec2 vScaleCorrection;
flat in vec2 vRingColorBand;
flat in uint vStates;
uniform sampler2D tokenRingTexture;
uniform float time;
uniform bool debugColorBands;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
${TokenRingSamplerShader.#FRAG_HEADER}
vec4 _main() {
${TokenRingSamplerShader.#FRAG_MAIN}
${TokenRingSamplerShader.#FRAG_MAIN_DEBUG}
return result;
}
`;
}