Browse Source

chore, bug: fixed the outline shader and refactored numbers

tags/v0.12.0
Shaun 1 year ago
parent
commit
2e6bf42dc7
5 changed files with 329 additions and 175 deletions
  1. +194
    -87
      src/client/js/rendering/numbers.js
  2. +15
    -11
      src/client/js/rendering/renderer.js
  3. +69
    -55
      src/client/js/rendering/shaders/outline.js
  4. +31
    -18
      src/client/js/rendering/shaders/outline/frag.js
  5. +20
    -4
      src/client/js/rendering/shaders/outline/vert.js

+ 194
- 87
src/client/js/rendering/numbers.js View File

@@ -9,8 +9,28 @@ define([
renderer,
config
) {
//Defaults
const MOVE_SPEED = 0.5;

const TTL = 35;
const FONT_SIZE = 18;
const FONT_SIZE_CRIT = 22;
const LAYER_NAME = 'effects';

const PADDING = scaleMult;

const POSITION = {
BOTTOM_CENTER: 0,
LEFT_BOTTOM: 1,
RIGHT_BOTTOM: 2,
TOP_CENTER: 3
};

//Internals
const list = [];

//Create an object of the form: { elementName: elementIntegerColor, ... } from corresponding variable values.
// These variables are defiend in main.less and take the form: var(--color-element-elementName)
// These variables are defined in main.less and take the form: var(--color-element-elementName)
const elementColors = Object.fromEntries(
['default', 'arcane', 'frost', 'fire', 'holy', 'poison'].map(e => {
const variableName = `--color-element-${e}`;
@@ -22,101 +42,188 @@ define([
})
);

return {
list: [],
//Helpers
const getColor = ({ color, element }) => {
if (color)
return color;

return elementColors[element];
};

const getText = ({ amount, text, heal }) => {
if (amount === undefined)
return text;

const div = ((~~(amount * 10) / 10) > 0) ? 10 : 100;
let result = ~~(amount * div) / div;

if (heal)
result = `+${result}`;

return result;
};

const getPosition = ({ position, event: isEvent, heal }) => {
if (position)
return position;

//Events render under the target, centered
if (isEvent)
return POSITION.BOTTOM_CENTER;
else if (heal)
return POSITION.LEFT_BOTTOM;

return POSITION.RIGHT_BOTTOM;
};

const getXY = (msg, position, sprite) => {
let x = 0;
let y = 0;

if (position === POSITION.TOP_CENTER) {
x = (scale / 2) - (sprite.width / 2);
y = -PADDING - sprite.height;
} else if (position === POSITION.BOTTOM_CENTER) {
x = (scale / 2) - (sprite.width / 2);
y = scale + PADDING;
} else if (position === POSITION.RIGHT_BOTTOM) {
x = scale;
y = scale - sprite.height + (scaleMult * 2);
} else if (position === POSITION.LEFT_BOTTOM) {
x = -sprite.width - PADDING;
y = scale - sprite.height + (scaleMult * 2);
}

return { x, y };
};

init: function () {
events.on('onGetDamage', this.onGetDamage.bind(this));
},
const getMovementDelta = ({ movementDelta }, position) => {
if (movementDelta)
return movementDelta;

onGetDamage: function (msg) {
if (config.damageNumbers === 'off')
if (position === POSITION.BOTTOM_CENTER)
return [0, 1];

return [0, -1];
};

const getFontSize = ({ fontSize, crit }) => {
if (fontSize)
return fontSize;
else if (crit)
return FONT_SIZE_CRIT;

return FONT_SIZE;
};

//Events
const onGetDamage = msg => {
const { ttl = TTL } = msg;

if (config.damageNumbers === 'off')
return;

const target = objects.objects.find(o => o.id === msg.id);
if (!target || !target.isVisible)
return;

const sprite = renderer.buildText({
fontSize: getFontSize(msg),
layerName: LAYER_NAME,
text: getText(msg),
color: getColor(msg),
visible: false
});

const position = getPosition(msg);
const movementDelta = getMovementDelta(msg, position);
const { x, y } = getXY(msg, position, sprite);

sprite.x = (target.x * scale) + x;
sprite.y = (target.y * scale) + y;
sprite.visible = true;

const numberObj = {
obj: target,
x,
y,
ttl,
ttlMax: ttl,
movementDelta,
sprite
};

list.push(numberObj);
};

//Call this method from inside update to generate test numbers
// around the player
/* eslint-disable-next-line no-unused-vars */
const test = () => {
objects.objects.forEach(o => {
if (!o.player)
return;

let target = objects.objects.find(function (o) {
return (o.id === msg.id);
const amount = Math.random() < 0.5 ? ~~(Math.random() * 100) : undefined;
const isEvent = amount ? false : Math.random() < 0.5;
const text = amount ? undefined : 'text';
const heal = Math.random() < 0.5;
let position;
if (!amount)
position = Math.random() < 0.5 ? POSITION.TOP_CENTER : POSITION.BOTTOM_CENTER;
const element = ['default', 'arcane', 'frost', 'fire', 'holy', 'poison'][~~(Math.random() * 6)];
const crit = amount > 50;

onGetDamage({
id: o.id,
event: isEvent,
text,
amount,
element,
heal,
position,
crit
});
if (!target || !target.isVisible)
return;
});
};

let ttl = 35;

let numberObj = {
obj: target,
amount: msg.amount,
x: (target.x * scale),
y: (target.y * scale) + scale - (scale / 4),
ttl: ttl,
ttlMax: ttl,
event: msg.event,
text: msg.text,
crit: msg.crit,
heal: msg.heal,
element: msg.element
};

if (numberObj.event) {
numberObj.x += (scale / 2);
numberObj.y += (scale / 2);
} else if (numberObj.heal)
numberObj.x -= scale;
else
numberObj.x += scale;

let text = numberObj.text;
if (!numberObj.event) {
let amount = numberObj.amount;
let div = ((~~(amount * 10) / 10) > 0) ? 10 : 100;
text = (numberObj.heal ? '+' : '') + (~~(amount * div) / div);
}
const update = () => {
let lLen = list.length;
for (let i = 0; i < lLen; i++) {
const l = list[i];

const colorVariableName = config.damageNumbers === 'element' ? numberObj.element : 'default';
numberObj.sprite = renderer.buildText({
fontSize: numberObj.crit ? 22 : 18,
layerName: 'effects',
x: numberObj.x,
y: numberObj.y,
text: text,
color: elementColors[colorVariableName]
});
l.ttl--;
if (l.ttl === 0) {
list.splice(i, 1);
lLen--;

renderer.destroyObject({
layerName: 'effects',
sprite: l.sprite
});

this.list.push(numberObj);
},

update: function () {
let list = this.list;
let lLen = list.length;

for (let i = 0; i < lLen; i++) {
let l = list[i];
l.ttl--;

if (l.ttl === 0) {
renderer.destroyObject({
layerName: 'effects',
sprite: l.sprite
});
list.splice(i, 1);
i--;
lLen--;
continue;
}

if (l.event)
l.y += 1;
else
l.y -= 1;

let alpha = l.ttl / l.ttlMax;
l.sprite.alpha = alpha;

l.sprite.x = ~~(l.x / scaleMult) * scaleMult;
l.sprite.y = ~~(l.y / scaleMult) * scaleMult;

if (l.event)
l.sprite.x -= (l.sprite.width) / 2;
continue;
}

l.x += l.movementDelta[0] * MOVE_SPEED;
l.y += l.movementDelta[1] * MOVE_SPEED;

l.sprite.x = (l.obj.x * scale) + l.x;
l.sprite.y = (l.obj.y * scale) + l.y;

l.sprite.alpha = l.ttl / l.ttlMax;
}
};

const init = () => {
events.on('onGetDamage', onGetDamage);
};

//Exports
return {
init,
update,
onGetDamage,
POSITION
};
});

+ 15
- 11
src/client/js/rendering/renderer.js View File

@@ -741,13 +741,12 @@ define([
},

addFilter: function (sprite) {
let thickness = (sprite.width > scale) ? 8 : 16;
const filter = new shaderOutline();

let filter = new shaderOutline(this.renderer.width, this.renderer.height, thickness, '0xffffff');
if (!sprite.filters)
sprite.filters = [filter];
else
sprite.filters.push();
sprite.filters.push(filter);

return filter;
},
@@ -758,19 +757,24 @@ define([
},

buildText: function (obj) {
let textSprite = new PIXI.Text(obj.text, {
const { text, visible, x, y, parent: spriteParent, layerName } = obj;
const { fontSize = 14, color = 0xF2F5F5 } = obj;

const textSprite = new PIXI.Text(text, {
fontFamily: 'bitty',
fontSize: (obj.fontSize || 14),
fill: obj.color || 0xF2F5F5,
fontSize: fontSize,
fill: color,
stroke: 0x2d2136,
strokeThickness: 4,
align: 'center'
strokeThickness: 4
});

textSprite.x = obj.x - (textSprite.width / 2);
textSprite.y = obj.y;
if (visible === false)
textSprite.visible = false;

textSprite.x = x - (textSprite.width / 2);
textSprite.y = y;

let parentSprite = obj.parent || this.layers[obj.layerName];
const parentSprite = spriteParent ?? this.layers[layerName];
parentSprite.addChild(textSprite);

return textSprite;


+ 69
- 55
src/client/js/rendering/shaders/outline.js View File

@@ -2,62 +2,76 @@ define([
'js/rendering/shaders/outline/vert',
'js/rendering/shaders/outline/frag'
], function (
vert,
frag
vertex,
fragment
) {
let OutlineFilter = function (viewWidth, viewHeight, thickness, color) {
thickness = thickness || 1;
PIXI.Filter.call(this,
vert,
frag.replace(/%THICKNESS%/gi, (1.0 / thickness).toFixed(7))
);

this.uniforms.pixelWidth = 0.002;
this.uniforms.pixelHeight = 1.0 / (viewHeight || 1);
this.uniforms.thickness = thickness;
this.uniforms.outlineColor = new Float32Array([0, 0, 0, 1]);
this.alpha = 0;
if (color)
this.color = color;
};

OutlineFilter.prototype = Object.create(PIXI.Filter.prototype);
OutlineFilter.prototype.constructor = OutlineFilter;

Object.defineProperties(OutlineFilter.prototype, {
color: {
get: function () {
return PIXI.utils.rgb2hex(this.uniforms.outlineColor);
},
set: function (value) {
PIXI.utils.hex2rgb(value, this.uniforms.outlineColor);
}
},

alpha: {
set: function (value) {
this.uniforms.alpha = value;
}
},

viewWidth: {
get: function () {
return 1 / this.uniforms.pixelWidth;
},
set: function (value) {
this.uniforms.pixelWidth = 1 / value;
}
},

viewHeight: {
get: function () {
return 1 / this.uniforms.pixelHeight;
},
set: function (value) {
this.uniforms.pixelHeight = 1 / value;
}
}
});
const _thickness = 1;
const _alpha = 1.0;
const _knockout = false;

class OutlineFilter extends PIXI.Filter {
constructor (thickness = 5, color = 0xFFFFFF, quality = 0.1, alpha = 1.0, knockout = false) {
const angleStep = Math.PI / 2;
color = PIXI.utils.hex2rgb(color);

super(vertex, fragment.replace('$angleStep$', angleStep));

this.uniforms.uThickness = new Float32Array([thickness, thickness]);
this.uniforms.uColor = new Float32Array([0, 0.0, 0.0, 1]);
this.uniforms.uAlpha = alpha;
this.uniforms.uKnockout = knockout;

Object.assign(this, { thickness, color, quality, alpha, knockout });
}

getAngleStep (quality) {
const samples = Math.max(
quality * MAX_SAMPLES,
MIN_SAMPLES,
);

return (Math.PI * 2 / samples).toFixed(7);
}

apply (filterManager, input, output, clear) {
this.uniforms.uThickness[0] = this.thickness / input._frame.width;
this.uniforms.uThickness[1] = this.thickness / input._frame.height;
this.uniforms.uAlpha = this.alpha;
this.uniforms.uKnockout = this.knockout;
this.uniforms.uColor = PIXI.utils.hex2rgb(this.color, this.uniforms.uColor);

filterManager.applyFilter(this, input, output, clear);
}

get alpha () {
return this._alpha;
}
set alpha (value) {
this._alpha = value;
}

get color () {
return PIXI.utils.rgb2hex(this.uniforms.uColor);
}
set color (value) {
PIXI.utils.hex2rgb(value, this.uniforms.uColor);
}

get knockout () {
return this._knockout;
}
set knockout (value) {
this._knockout = value;
}

get thickness () {
return this._thickness;
}
set thickness (value) {
this._thickness = value;
this.padding = value;
}
}

return OutlineFilter;
});

+ 31
- 18
src/client/js/rendering/shaders/outline/frag.js View File

@@ -6,28 +6,41 @@ define([
return `
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform vec4 filterClamp;

uniform float thickness;
uniform vec4 outlineColor;
uniform float pixelWidth;
uniform float alpha;
vec2 px = vec2(pixelWidth, pixelWidth);
uniform float uAlpha;
uniform vec2 uThickness;
uniform vec4 uColor;
uniform bool uKnockout;

void main(void) {
const float PI = 3.14159265358979323846264;
vec4 ownColor = texture2D(uSampler, vTextureCoord);
vec4 curColor;
const float DOUBLE_PI = 2. * 3.14159265358979323846264;
const float ANGLE_STEP = $angleStep$;

float outlineMaxAlphaAtPos(vec2 pos) {
if (uThickness.x == 0. || uThickness.y == 0.) {
return 0.;
}

vec4 displacedColor;
vec2 displacedPos;
float maxAlpha = 0.;
for (float angle = 0.; angle < PI * 2.; angle += %THICKNESS% ) {
curColor = texture2D(uSampler, vec2(vTextureCoord.x + thickness * px.x * cos(angle), vTextureCoord.y + thickness * px.y * sin(angle)));
maxAlpha = max(maxAlpha, curColor.a);

for (float angle = 0.; angle <= DOUBLE_PI; angle += ANGLE_STEP) {
displacedPos.x = vTextureCoord.x + uThickness.x * cos(angle);
displacedPos.y = vTextureCoord.y + uThickness.y * sin(angle);
displacedColor = texture2D(uSampler, clamp(displacedPos, filterClamp.xy, filterClamp.zw));
maxAlpha = max(maxAlpha, displacedColor.a);
}
if (maxAlpha > 0.1)
maxAlpha = alpha;
else
maxAlpha = 0.0;
float resultAlpha = max(maxAlpha, ownColor.a);
gl_FragColor = vec4((ownColor.rgb + outlineColor.rgb * (1. - ownColor.a)) * resultAlpha, resultAlpha);

return maxAlpha;
}

void main(void) {
vec4 sourceColor = texture2D(uSampler, vTextureCoord);
vec4 contentColor = sourceColor * float(!uKnockout);
float outlineAlpha = uAlpha * outlineMaxAlphaAtPos(vTextureCoord.xy) * (1.-sourceColor.a);
vec4 outlineColor = vec4(vec3(uColor) * outlineAlpha, outlineAlpha);
gl_FragColor = contentColor + outlineColor;
}
`;
});

+ 20
- 4
src/client/js/rendering/shaders/outline/vert.js View File

@@ -5,14 +5,30 @@ define([
) {
return `
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;

uniform mat3 projectionMatrix;

varying vec2 vTextureCoord;

void main(void){
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aTextureCoord;
uniform vec4 inputSize;
uniform vec4 outputFrame;

vec4 filterVertexPosition( void )
{
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;

return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}

vec2 filterTextureCoord( void )
{
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}

void main(void)
{
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
}
`;
});

Loading…
Cancel
Save