pokerogue/src/pipelines/sprite.ts

278 lines
9.6 KiB
TypeScript
Raw Normal View History

2023-06-05 02:47:43 +01:00
import Pokemon from "../pokemon";
2023-10-07 21:08:33 +01:00
import Trainer from "../trainer";
2023-12-30 02:04:40 +00:00
import FieldSpritePipeline from "./field-sprite";
2023-06-02 23:33:51 +01:00
2023-06-05 02:47:43 +01:00
const spriteFragShader = `
2023-06-02 23:33:51 +01:00
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
uniform sampler2D uMainSampler[%count%];
varying vec2 outTexCoord;
varying float outTexId;
2023-06-05 02:47:43 +01:00
varying vec2 outPosition;
2023-06-02 23:33:51 +01:00
varying float outTintEffect;
varying vec4 outTint;
2023-12-30 02:04:40 +00:00
uniform float time;
uniform int ignoreTimeTint;
2023-12-30 02:04:40 +00:00
uniform int isOutside;
uniform vec3 dayTint;
uniform vec3 duskTint;
uniform vec3 nightTint;
2023-06-06 04:01:00 +01:00
uniform int hasShadow;
uniform int yCenter;
uniform float fieldScale;
2023-06-05 02:47:43 +01:00
uniform float vCutoff;
uniform vec2 relPosition;
uniform vec2 size;
uniform float yOffset;
2023-06-02 23:33:51 +01:00
uniform vec4 tone;
2023-11-24 04:52:13 +00:00
uniform ivec4 spriteColors[32];
uniform ivec4 fusionSpriteColors[32];
2023-06-02 23:33:51 +01:00
const vec3 lumaF = vec3(.299, .587, .114);
2023-12-30 02:04:40 +00:00
float blendOverlay(float base, float blend) {
return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend));
}
vec3 blendOverlay(vec3 base, vec3 blend) {
return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b));
}
vec3 blendHardLight(vec3 base, vec3 blend) {
return blendOverlay(blend, base);
}
2023-06-05 02:47:43 +01:00
void main()
2023-06-02 23:33:51 +01:00
{
vec4 texture;
%forloop%
2023-11-24 04:52:13 +00:00
for (int i = 0; i < 32; i++) {
if (spriteColors[i][3] == 0)
break;
if (texture.a > 0.0 && int(texture.r * 255.0) == spriteColors[i].r && int(texture.g * 255.0) == spriteColors[i].g && int(texture.b * 255.0) == spriteColors[i].b) {
vec3 fusionColor = vec3(float(fusionSpriteColors[i].r) / 255.0, float(fusionSpriteColors[i].g) / 255.0, float(fusionSpriteColors[i].b) / 255.0);
vec3 bg = vec3(float(spriteColors[i].r) / 255.0, float(spriteColors[i].g) / 255.0, float(spriteColors[i].b) / 255.0);
float gray = (bg.r + bg.g + bg.b) / 3.0;
bg = vec3(gray, gray, gray);
vec3 fg = fusionColor;
2023-11-26 14:53:16 +00:00
texture.rgb = mix(1.0 - 2.0 * (1.0 - bg) * (1.0 - fg), 2.0 * bg * fg, step(bg, vec3(0.5)));
2023-11-24 04:52:13 +00:00
break;
}
}
2023-11-26 14:53:16 +00:00
vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);
// Multiply texture tint
vec4 color = texture * texel;
2023-11-24 04:52:13 +00:00
if (outTintEffect == 1.0) {
2023-06-02 23:33:51 +01:00
// Solid color + texture alpha
color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);
2023-11-24 04:52:13 +00:00
} else if (outTintEffect == 2.0) {
2023-06-02 23:33:51 +01:00
// Solid color, no texture
color = texel;
}
/* Apply gray */
float luma = dot(color.rgb, lumaF);
color.rgb = mix(color.rgb, vec3(luma), tone.w);
/* Apply tone */
color.rgb += tone.rgb * (color.a / 255.0);
2023-12-30 02:04:40 +00:00
/* Apply day/night tint */
if (color.a > 0.0 && ignoreTimeTint == 0) {
2023-12-30 02:04:40 +00:00
vec3 dayNightTint;
if (time < 0.25) {
dayNightTint = dayTint;
} else if (isOutside == 0 && time < 0.5) {
dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25);
} else if (time < 0.375) {
dayNightTint = mix(dayTint, duskTint, (time - 0.25) / 0.125);
} else if (time < 0.5) {
dayNightTint = mix(duskTint, nightTint, (time - 0.375) / 0.125);
} else if (time < 0.75) {
dayNightTint = nightTint;
} else if (isOutside == 0) {
dayNightTint = mix(nightTint, dayTint, (time - 0.75) / 0.25);
} else if (time < 0.875) {
dayNightTint = mix(nightTint, duskTint, (time - 0.75) / 0.125);
} else {
dayNightTint = mix(duskTint, dayTint, (time - 0.875) / 0.125);
}
color = vec4(blendHardLight(color.rgb, dayNightTint), color.a);
}
2023-06-06 04:01:00 +01:00
if (hasShadow == 1) {
2023-06-05 02:47:43 +01:00
float width = size.x - (yOffset / 2.0);
float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5;
float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y) / size.y);
2023-06-05 02:47:43 +01:00
2023-06-06 04:01:00 +01:00
if (yCenter == 1) {
2023-06-05 02:47:43 +01:00
spriteY += 0.5;
} else {
spriteY += 1.0;
}
bool yOverflow = outTexCoord.y >= vCutoff;
if ((spriteY >= 0.9 && (color.a == 0.0 || yOverflow))) {
float shadowSpriteY = (spriteY - 0.9) * (1.0 / 0.15);
if (distance(vec2(spriteX, shadowSpriteY), vec2(0.5, 0.5)) < 0.5) {
color = vec4(vec3(0.0, 0.0, 0.0), 0.5);
} else if (yOverflow) {
discard;
}
} else if (yOverflow) {
discard;
}
}
2023-06-02 23:33:51 +01:00
gl_FragColor = color;
}
`;
2023-06-05 02:47:43 +01:00
const spriteVertShader = `
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
uniform mat4 uProjectionMatrix;
attribute vec2 inPosition;
attribute vec2 inTexCoord;
attribute float inTexId;
attribute float inTintEffect;
attribute vec4 inTint;
varying vec2 outTexCoord;
varying float outTexId;
varying vec2 outPosition;
varying float outTintEffect;
varying vec4 outTint;
void main()
2023-06-02 23:33:51 +01:00
{
2023-06-05 02:47:43 +01:00
gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);
outTexCoord = inTexCoord;
outTexId = inTexId;
outPosition = inPosition;
outTint = inTint;
outTintEffect = inTintEffect;
}
`;
2023-12-30 02:04:40 +00:00
export default class SpritePipeline extends FieldSpritePipeline {
2023-06-02 23:33:51 +01:00
private _tone: number[];
constructor(game: Phaser.Game) {
2023-12-30 02:04:40 +00:00
super(game, {
2023-06-02 23:33:51 +01:00
game: game,
name: 'sprite',
2023-06-05 02:47:43 +01:00
fragShader: spriteFragShader,
vertShader: spriteVertShader
2023-06-02 23:33:51 +01:00
});
this._tone = [ 0, 0, 0, 0 ];
}
onPreRender(): void {
2023-12-30 02:04:40 +00:00
super.onPreRender();
2023-06-06 04:01:00 +01:00
this.set1i('hasShadow', 0);
this.set1i('yCenter', 0);
2023-06-05 02:47:43 +01:00
this.set2f('relPosition', 0, 0);
this.set2f('size', 0, 0);
this.set1f('yOffset', 0);
2023-11-24 04:52:13 +00:00
this.set4fv('tone', this._tone);
2023-06-02 23:33:51 +01:00
}
onBind(gameObject: Phaser.GameObjects.GameObject): void {
2023-12-30 02:04:40 +00:00
super.onBind(gameObject);
2023-06-02 23:33:51 +01:00
2023-06-05 02:47:43 +01:00
const sprite = (gameObject as Phaser.GameObjects.Sprite);
2023-06-02 23:33:51 +01:00
2023-06-05 02:47:43 +01:00
const data = sprite.pipelineData;
const tone = data['tone'] as number[];
const hasShadow = data['hasShadow'] as boolean;
const ignoreOverride = data['ignoreOverride'] as boolean;
const spriteColors = (ignoreOverride && data['spriteColorsBase']) || data['spriteColors'] || [] as number[][];
const fusionSpriteColors = (ignoreOverride && data['fusionSpriteColorsBase']) || data['fusionSpriteColors'] || [] as number[][];
2023-06-05 02:47:43 +01:00
const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer;
const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
const fieldScaleRatio = field.scale / 6;
const position = isEntityObj
2023-06-05 02:47:43 +01:00
? [ sprite.parentContainer.x, sprite.parentContainer.y ]
: [ sprite.x, sprite.y ];
position[0] += field.x / field.scale;
position[1] += field.y / field.scale;
position[0] += -(sprite.width - (sprite.frame.width)) / 2 + sprite.frame.x;
if (sprite.originY === 0.5)
position[1] += (sprite.height / 2) * ((isEntityObj ? sprite.parentContainer : sprite).scale - 1);
2023-06-06 04:01:00 +01:00
this.set1i('hasShadow', hasShadow ? 1 : 0);
this.set1i('yCenter', sprite.originY === 0.5 ? 1 : 0);
this.set1f('fieldScale', field.scale);
2023-06-05 02:47:43 +01:00
this.set2f('relPosition', position[0], position[1]);
this.set2f('size', sprite.frame.width, sprite.height);
this.set1f('yOffset', sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
2023-11-24 04:52:13 +00:00
this.set4fv('tone', tone);
const emptyColors = [ 0, 0, 0, 0 ];
const flatSpriteColors: integer[] = [];
const flatFusionSpriteColors: integer[] = [];
for (let c = 0; c < 32; c++) {
flatSpriteColors.splice(flatSpriteColors.length, 0, c < spriteColors.length ? spriteColors[c] : emptyColors);
flatFusionSpriteColors.splice(flatFusionSpriteColors.length, 0, c < fusionSpriteColors.length ? fusionSpriteColors[c] : emptyColors);
}
this.set4iv(`spriteColors`, flatSpriteColors.flat());
this.set4iv(`fusionSpriteColors`, flatFusionSpriteColors.flat());
2023-06-02 23:33:51 +01:00
}
2023-06-05 02:47:43 +01:00
batchQuad(gameObject: Phaser.GameObjects.GameObject, x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number,
u0: number, v0: number, u1: number, v1: number, tintTL: number, tintTR: number, tintBL: number, tintBR: number, tintEffect: number | boolean,
texture?: WebGLTexture, unit?: number): boolean {
const sprite = gameObject as Phaser.GameObjects.Sprite;
this.set1f('vCutoff', v1);
const hasShadow = sprite.pipelineData['hasShadow'] as boolean;
if (hasShadow) {
const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer;
const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
const fieldScaleRatio = field.scale / 6;
const baseY = (isEntityObj
2023-06-05 02:47:43 +01:00
? sprite.parentContainer.y
: sprite.y + sprite.height) * 6 / fieldScaleRatio;
const bottomPadding = Math.ceil(sprite.height * 0.05) * 6 / fieldScaleRatio;
const yDelta = (baseY - y1) / field.scale;
2023-06-05 02:47:43 +01:00
y2 = y1 = baseY + bottomPadding;
const pixelHeight = (v1 - v0) / (sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
v1 += (yDelta + bottomPadding / field.scale) * pixelHeight;
2023-06-02 23:33:51 +01:00
}
2023-06-05 02:47:43 +01:00
return super.batchQuad(gameObject, x0, y0, x1, y1, x2, y2, x3, y3, u0, v0, u1, v1, tintTL, tintTR, tintBL, tintBR, tintEffect, texture, unit);
2023-06-02 23:33:51 +01:00
}
get tone(): number[] {
return this._tone;
}
set tone(value: number[]) {
this._tone = value;
}
}