539 lines
15 KiB
JavaScript
539 lines
15 KiB
JavaScript
/**
|
|
* The grid shader used by {@link GridMesh}.
|
|
*/
|
|
class GridShader extends AbstractBaseShader {
|
|
|
|
/**
|
|
* The grid type uniform.
|
|
* @type {string}
|
|
*/
|
|
static TYPE_UNIFORM = `
|
|
const int TYPE_SQUARE = ${CONST.GRID_TYPES.SQUARE};
|
|
const int TYPE_HEXODDR = ${CONST.GRID_TYPES.HEXODDR};
|
|
const int TYPE_HEXEVENR = ${CONST.GRID_TYPES.HEXEVENR};
|
|
const int TYPE_HEXODDQ = ${CONST.GRID_TYPES.HEXODDQ};
|
|
const int TYPE_HEXEVENQ = ${CONST.GRID_TYPES.HEXEVENQ};
|
|
|
|
uniform lowp int type;
|
|
|
|
#define TYPE_IS_SQUARE (type == TYPE_SQUARE)
|
|
#define TYPE_IS_HEXAGONAL ((TYPE_HEXODDR <= type) && (type <= TYPE_HEXEVENQ))
|
|
#define TYPE_IS_HEXAGONAL_COLUMNS ((type == TYPE_HEXODDQ) || (type == TYPE_HEXEVENQ))
|
|
#define TYPE_IS_HEXAGONAL_ROWS ((type == TYPE_HEXODDR) || (type == TYPE_HEXEVENR))
|
|
#define TYPE_IS_HEXAGONAL_EVEN ((type == TYPE_HEXEVENR) || (type == TYPE_HEXEVENQ))
|
|
#define TYPE_IS_HEXAGONAL_ODD ((type == TYPE_HEXODDR) || (type == TYPE_HEXODDQ))
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The grid thickness uniform.
|
|
* @type {string}
|
|
*/
|
|
static THICKNESS_UNIFORM = "uniform float thickness;";
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The grid color uniform.
|
|
* @type {string}
|
|
*/
|
|
static COLOR_UNIFORM = "uniform vec4 color;";
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The resolution (pixels per grid space units) uniform.
|
|
* @type {string}
|
|
*/
|
|
static RESOLUTION_UNIFORM = "uniform float resolution;";
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The antialiased step function.
|
|
* The edge and x values is given in grid space units.
|
|
* @type {string}
|
|
*/
|
|
static ANTIALIASED_STEP_FUNCTION = `
|
|
#define ANTIALIASED_STEP_TEMPLATE(type) \
|
|
type antialiasedStep(type edge, type x) { \
|
|
return clamp(((x - edge) * resolution) + 0.5, type(0.0), type(1.0)); \
|
|
}
|
|
|
|
ANTIALIASED_STEP_TEMPLATE(float)
|
|
ANTIALIASED_STEP_TEMPLATE(vec2)
|
|
ANTIALIASED_STEP_TEMPLATE(vec3)
|
|
ANTIALIASED_STEP_TEMPLATE(vec4)
|
|
|
|
#undef ANTIALIASED_STEP_TEMPLATE
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The line converage function, which returns the alpha value at a point with the given distance (in grid space units)
|
|
* from an antialiased line (or point) with the given thickness (in grid space units).
|
|
* @type {string}
|
|
*/
|
|
static LINE_COVERAGE_FUNCTION = `
|
|
float lineCoverage(float distance, float thickness, float alignment) {
|
|
float alpha0 = antialiasedStep((0.0 - alignment) * thickness, distance);
|
|
float alpha1 = antialiasedStep((1.0 - alignment) * thickness, distance);
|
|
return alpha0 - alpha1;
|
|
}
|
|
|
|
float lineCoverage(float distance, float thickness) {
|
|
return lineCoverage(distance, thickness, 0.5);
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Hexagonal functions conversion for between grid and cube space.
|
|
* @type {string}
|
|
*/
|
|
static HEXAGONAL_FUNCTIONS = `
|
|
vec2 pointToCube(vec2 p) {
|
|
float x = p.x;
|
|
float y = p.y;
|
|
float q;
|
|
float r;
|
|
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : 0.0;
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
q = ((2.0 * SQRT1_3) * x) - (2.0 / 3.0);
|
|
r = (-0.5 * (q + e)) + y;
|
|
} else {
|
|
r = ((2.0 * SQRT1_3) * y) - (2.0 / 3.0);
|
|
q = (-0.5 * (r + e)) + x;
|
|
}
|
|
return vec2(q, r);
|
|
}
|
|
|
|
vec2 cubeToPoint(vec2 a) {
|
|
float q = a[0];
|
|
float r = a[1];
|
|
float x;
|
|
float y;
|
|
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : 0.0;
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
x = (SQRT3 / 2.0) * (q + (2.0 / 3.0));
|
|
y = (0.5 * (q + e)) + r;
|
|
} else {
|
|
y = (SQRT3 / 2.0) * (r + (2.0 / 3.0));
|
|
x = (0.5 * (r + e)) + q;
|
|
}
|
|
return vec2(x, y);
|
|
}
|
|
|
|
vec2 offsetToCube(vec2 o) {
|
|
float i = o[0];
|
|
float j = o[1];
|
|
float q;
|
|
float r;
|
|
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : -1.0;
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
q = j;
|
|
r = i - ((j + (e * mod(j, 2.0))) * 0.5);
|
|
} else {
|
|
q = j - ((i + (e * mod(i, 2.0))) * 0.5);
|
|
r = i;
|
|
}
|
|
return vec2(q, r);
|
|
}
|
|
|
|
ivec2 offsetToCube(ivec2 o) {
|
|
int i = o[0];
|
|
int j = o[1];
|
|
int q;
|
|
int r;
|
|
int e = TYPE_IS_HEXAGONAL_EVEN ? 1 : -1;
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
q = j;
|
|
r = i - ((j + (e * (j & 1))) / 2);
|
|
} else {
|
|
q = j - ((i + (e * (i & 1))) / 2);
|
|
r = i;
|
|
}
|
|
return ivec2(q, r);
|
|
}
|
|
|
|
vec2 cubeToOffset(vec2 a) {
|
|
float q = a[0];
|
|
float r = a[1];
|
|
float i;
|
|
float j;
|
|
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : -1.0;
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
j = q;
|
|
i = r + ((q + (e * mod(q, 2.0))) * 0.5);
|
|
} else {
|
|
i = r;
|
|
j = q + ((r + (e * mod(r, 2.0))) * 0.5);
|
|
}
|
|
return vec2(i, j);
|
|
}
|
|
|
|
ivec2 cubeToOffset(ivec2 a) {
|
|
int q = a[0];
|
|
int r = a[1];
|
|
int i;
|
|
int j;
|
|
int e = TYPE_IS_HEXAGONAL_EVEN ? 1 : -1;
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
j = q;
|
|
i = r + ((q + (e * (q & 1))) / 2);
|
|
} else {
|
|
i = r;
|
|
j = q + ((r + (e * (r & 1))) / 2);
|
|
}
|
|
return ivec2(i, j);
|
|
}
|
|
|
|
vec2 cubeRound(vec2 a) {
|
|
float q = a[0];
|
|
float r = a[1];
|
|
float s = -q - r;
|
|
float iq = floor(q + 0.5);
|
|
float ir = floor(r + 0.5);
|
|
float is = floor(s + 0.5);
|
|
float dq = abs(iq - q);
|
|
float dr = abs(ir - r);
|
|
float ds = abs(is - s);
|
|
if ( (dq > dr) && (dq > ds) ) {
|
|
iq = -ir - is;
|
|
} else if ( dr > ds ) {
|
|
ir = -iq - is;
|
|
} else {
|
|
is = -iq - ir;
|
|
}
|
|
return vec2(iq, ir);
|
|
}
|
|
|
|
float cubeDistance(vec2 a, vec2 b) {
|
|
vec2 c = b - a;
|
|
float q = c[0];
|
|
float r = c[1];
|
|
return (abs(q) + abs(r) + abs(q + r)) * 0.5;
|
|
}
|
|
|
|
int cubeDistance(ivec2 a, ivec2 b) {
|
|
ivec2 c = b - a;
|
|
int q = c[0];
|
|
int r = c[1];
|
|
return (abs(q) + abs(r) + abs(q + r)) / 2;
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get the nearest vertex of a grid space to the given point.
|
|
* @type {string}
|
|
*/
|
|
static NEAREST_VERTEX_FUNCTION = `
|
|
vec2 nearestVertex(vec2 p) {
|
|
if ( TYPE_IS_SQUARE ) {
|
|
return floor(p + 0.5);
|
|
}
|
|
|
|
if ( TYPE_IS_HEXAGONAL ) {
|
|
vec2 c = cubeToPoint(cubeRound(pointToCube(p)));
|
|
vec2 d = p - c;
|
|
float a = atan(d.y, d.x);
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
a = floor((a / (PI / 3.0)) + 0.5) * (PI / 3.0);
|
|
} else {
|
|
a = (floor(a / (PI / 3.0)) + 0.5) * (PI / 3.0);
|
|
}
|
|
return c + (vec2(cos(a), sin(a)) * SQRT1_3);
|
|
}
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* This function returns the distance to the nearest edge of a grid space given a point.
|
|
* @type {string}
|
|
*/
|
|
static EDGE_DISTANCE_FUNCTION = `
|
|
float edgeDistance(vec2 p) {
|
|
if ( TYPE_IS_SQUARE ) {
|
|
vec2 d = abs(p - floor(p + 0.5));
|
|
return min(d.x, d.y);
|
|
}
|
|
|
|
if ( TYPE_IS_HEXAGONAL ) {
|
|
vec2 a = pointToCube(p);
|
|
vec2 b = cubeRound(a);
|
|
vec2 c = b - a;
|
|
float q = c[0];
|
|
float r = c[1];
|
|
float s = -q - r;
|
|
return (2.0 - (abs(q - r) + abs(r - s) + abs(s - q))) * 0.25;
|
|
}
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* This function returns an vector (x, y, z), where
|
|
* - x is the x-offset along the nearest edge,
|
|
* - y is the y-offset (the distance) from the nearest edge, and
|
|
* - z is the length of the nearest edge.
|
|
* @type {string}
|
|
*/
|
|
static EDGE_OFFSET_FUNCTION = `
|
|
vec3 edgeOffset(vec2 p) {
|
|
if ( TYPE_IS_SQUARE ) {
|
|
vec2 d = abs(p - floor(p + 0.5));
|
|
return vec3(max(d.x, d.y), min(d.x, d.y), 1.0);
|
|
}
|
|
|
|
if ( TYPE_IS_HEXAGONAL ) {
|
|
vec2 c = cubeToPoint(cubeRound(pointToCube(p)));
|
|
vec2 d = p - c;
|
|
float a = atan(d.y, d.x);
|
|
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
|
|
a = (floor(a / (PI / 3.0)) + 0.5) * (PI / 3.0);
|
|
} else {
|
|
a = floor((a / (PI / 3.0)) + 0.5) * (PI / 3.0);
|
|
}
|
|
vec2 n = vec2(cos(a), sin(a));
|
|
return vec3((0.5 * SQRT1_3) + dot(d, vec2(-n.y, n.x)), 0.5 - dot(d, n), SQRT1_3);
|
|
}
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* A function that draws the grid given a grid point, style, thickness, and color.
|
|
* @type {string}
|
|
*/
|
|
static DRAW_GRID_FUNCTION = `
|
|
const int STYLE_LINE_SOLID = 0;
|
|
const int STYLE_LINE_DASHED = 1;
|
|
const int STYLE_LINE_DOTTED = 2;
|
|
const int STYLE_POINT_SQUARE = 3;
|
|
const int STYLE_POINT_DIAMOND = 4;
|
|
const int STYLE_POINT_ROUND = 5;
|
|
|
|
vec4 drawGrid(vec2 point, int style, float thickness, vec4 color) {
|
|
float alpha;
|
|
|
|
if ( style == STYLE_POINT_SQUARE ) {
|
|
vec2 offset = abs(nearestVertex(point) - point);
|
|
float distance = max(offset.x, offset.y);
|
|
alpha = lineCoverage(distance, thickness);
|
|
}
|
|
|
|
else if ( style == STYLE_POINT_DIAMOND ) {
|
|
vec2 offset = abs(nearestVertex(point) - point);
|
|
float distance = (offset.x + offset.y) * SQRT1_2;
|
|
alpha = lineCoverage(distance, thickness);
|
|
}
|
|
|
|
else if ( style == STYLE_POINT_ROUND ) {
|
|
float distance = distance(point, nearestVertex(point));
|
|
alpha = lineCoverage(distance, thickness);
|
|
}
|
|
|
|
else if ( style == STYLE_LINE_SOLID ) {
|
|
float distance = edgeDistance(point);
|
|
alpha = lineCoverage(distance, thickness);
|
|
}
|
|
|
|
else if ( (style == STYLE_LINE_DASHED) || (style == STYLE_LINE_DOTTED) ) {
|
|
vec3 offset = edgeOffset(point);
|
|
if ( (style == STYLE_LINE_DASHED) && TYPE_IS_HEXAGONAL ) {
|
|
float padding = thickness * ((1.0 - SQRT1_3) * 0.5);
|
|
offset.x += padding;
|
|
offset.z += (padding * 2.0);
|
|
}
|
|
|
|
float intervals = offset.z * 0.5 / thickness;
|
|
if ( intervals < 0.5 ) {
|
|
alpha = lineCoverage(offset.y, thickness);
|
|
} else {
|
|
float interval = thickness * (2.0 * (intervals / floor(intervals + 0.5)));
|
|
float dx = offset.x - (floor((offset.x / interval) + 0.5) * interval);
|
|
float dy = offset.y;
|
|
|
|
if ( style == STYLE_LINE_DOTTED ) {
|
|
alpha = lineCoverage(length(vec2(dx, dy)), thickness);
|
|
} else {
|
|
alpha = min(lineCoverage(dx, thickness), lineCoverage(dy, thickness));
|
|
}
|
|
}
|
|
}
|
|
|
|
return color * alpha;
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
static vertexShader = `
|
|
#version 300 es
|
|
|
|
${this.GLSL1_COMPATIBILITY_VERTEX}
|
|
|
|
precision ${PIXI.settings.PRECISION_VERTEX} float;
|
|
|
|
in vec2 aVertexPosition;
|
|
|
|
uniform mat3 translationMatrix;
|
|
uniform mat3 projectionMatrix;
|
|
uniform vec4 meshDimensions;
|
|
uniform vec2 canvasDimensions;
|
|
uniform vec4 sceneDimensions;
|
|
uniform vec2 screenDimensions;
|
|
uniform float gridSize;
|
|
|
|
out vec2 vGridCoord; // normalized grid coordinates
|
|
out vec2 vCanvasCoord; // normalized canvas coordinates
|
|
out vec2 vSceneCoord; // normalized scene coordinates
|
|
out vec2 vScreenCoord; // normalized screen coordinates
|
|
|
|
void main() {
|
|
vec2 pixelCoord = (aVertexPosition * meshDimensions.zw) + meshDimensions.xy;
|
|
vGridCoord = pixelCoord / gridSize;
|
|
vCanvasCoord = pixelCoord / canvasDimensions;
|
|
vSceneCoord = (pixelCoord - sceneDimensions.xy) / sceneDimensions.zw;
|
|
vec3 tPos = translationMatrix * vec3(aVertexPosition, 1.0);
|
|
vScreenCoord = tPos.xy / screenDimensions;
|
|
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
|
|
}
|
|
`;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
static get fragmentShader() {
|
|
return `
|
|
#version 300 es
|
|
|
|
${this.GLSL1_COMPATIBILITY_FRAGMENT}
|
|
|
|
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
|
|
|
|
in vec2 vGridCoord; // normalized grid coordinates
|
|
in vec2 vCanvasCoord; // normalized canvas coordinates
|
|
in vec2 vSceneCoord; // normalized scene coordinates
|
|
in vec2 vScreenCoord; // normalized screen coordinates
|
|
|
|
${this.CONSTANTS}
|
|
|
|
${this.TYPE_UNIFORM}
|
|
${this.THICKNESS_UNIFORM}
|
|
${this.COLOR_UNIFORM}
|
|
${this.RESOLUTION_UNIFORM}
|
|
|
|
${this.ANTIALIASED_STEP_FUNCTION}
|
|
${this.LINE_COVERAGE_FUNCTION}
|
|
${this.HEXAGONAL_FUNCTIONS}
|
|
${this.NEAREST_VERTEX_FUNCTION}
|
|
${this.EDGE_DISTANCE_FUNCTION}
|
|
${this.EDGE_OFFSET_FUNCTION}
|
|
|
|
${this._fragmentShader}
|
|
|
|
uniform float alpha;
|
|
|
|
out vec4 fragColor;
|
|
|
|
void main() {
|
|
fragColor = _main() * alpha;
|
|
}
|
|
`;
|
|
}
|
|
|
|
/* ---------------------------------------- */
|
|
|
|
/**
|
|
* The fragment shader source. Subclasses can override it.
|
|
* @type {string}
|
|
* @protected
|
|
*/
|
|
static _fragmentShader = `
|
|
uniform lowp int style;
|
|
|
|
${this.DRAW_GRID_FUNCTION}
|
|
|
|
vec4 _main() {
|
|
return drawGrid(vGridCoord, style, thickness, color);
|
|
}
|
|
`;
|
|
|
|
/* ---------------------------------------- */
|
|
|
|
/** @override */
|
|
static defaultUniforms = {
|
|
canvasDimensions: [1, 1],
|
|
meshDimensions: [0, 0, 1, 1],
|
|
sceneDimensions: [0, 0, 1, 1],
|
|
screenDimensions: [1, 1],
|
|
gridSize: 1,
|
|
type: 0,
|
|
thickness: 0,
|
|
resolution: 0,
|
|
color: [0, 0, 0, 0],
|
|
alpha: 0,
|
|
style: 0
|
|
};
|
|
|
|
/* ---------------------------------------- */
|
|
|
|
/**
|
|
* Configure the shader.
|
|
* @param {object} options
|
|
*/
|
|
configure(options) {
|
|
if ( "style" in options ) {
|
|
this.uniforms.style = options.style ?? 0;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------- */
|
|
|
|
#color = new PIXI.Color(0);
|
|
|
|
/* ---------------------------------------- */
|
|
|
|
/** @override */
|
|
_preRender(mesh, renderer) {
|
|
const data = mesh.data;
|
|
const size = data.size;
|
|
const uniforms = this.uniforms;
|
|
const dimensions = canvas.dimensions;
|
|
uniforms.meshDimensions[0] = mesh.x;
|
|
uniforms.meshDimensions[1] = mesh.y;
|
|
uniforms.meshDimensions[2] = data.width; // === mesh.width
|
|
uniforms.meshDimensions[3] = data.height; // === mesh.height
|
|
uniforms.canvasDimensions[0] = dimensions.width;
|
|
uniforms.canvasDimensions[1] = dimensions.height;
|
|
uniforms.sceneDimensions = dimensions.sceneRect;
|
|
uniforms.screenDimensions = canvas.screenDimensions;
|
|
uniforms.gridSize = size;
|
|
uniforms.type = data.type;
|
|
uniforms.thickness = data.thickness / size;
|
|
uniforms.alpha = mesh.worldAlpha;
|
|
this.#color.setValue(data.color).toArray(uniforms.color);
|
|
|
|
// Only uniform scale is supported!
|
|
const {resolution} = renderer.renderTexture.current ?? renderer;
|
|
let scale = resolution * mesh.worldTransform.a / data.width;
|
|
const projection = renderer.projection.transform;
|
|
if ( projection ) {
|
|
const {a, b} = projection;
|
|
scale *= Math.sqrt((a * a) + (b * b));
|
|
}
|
|
uniforms.resolution = scale * size;
|
|
}
|
|
}
|