133 lines
10 KiB
Plaintext
133 lines
10 KiB
Plaintext
#version 450
|
|
#extension GL_GOOGLE_include_directive : require
|
|
layout(local_size_x = 64) in;
|
|
#include "common.glsl"
|
|
|
|
// Draws entity sprites into the per-cell entity layer. One workgroup per sprite instance.
|
|
// The sprite is placed at its entity current position (entities.comp moves them), mirrored when the entity faces left, and
|
|
// animated: mobile entities play their walk animation while moving and the idle one otherwise.
|
|
struct Ent { vec4 a, box, mv, ai; ivec4 st; vec4 hp; ivec4 x; ivec4 rg; ivec4 rg2; ivec4 aux; vec4 ex; vec4 ex2; ivec4 ex3; vec4 ex4; vec4 ex5; vec4 ex6; vec4 ex7; ivec4 ex8; vec4 ex9; vec4 ex10; vec4 ex11; vec4 ex12, ex13, ex14, ex15, ex16, ex17, ex18, ex19; };
|
|
layout(std430, set = 0, binding = 19) readonly buffer EntTexels { uint enttex[]; };
|
|
layout(std430, set = 0, binding = 20) readonly buffer EntInst { ivec4 entinst[]; }; // 4 per instance, see main.cpp
|
|
layout(std430, set = 0, binding = 21) buffer EntLayer { uint entlayer[]; };
|
|
layout(std430, set = 0, binding = 22) readonly buffer Ents { Ent ents[]; };
|
|
layout(std430, set = 0, binding = 23) readonly buffer Ctrl { ivec4 inp; ivec4 inp2; ivec4 inp3; vec4 outp; vec4 outp2; vec4 view; vec4 hand0; ivec4 hand1; vec4 hand2; } ctrl;
|
|
layout(std430, set = 0, binding = 39) readonly buffer EntPrev { vec4 prevpos[]; }; // positions before the last tick
|
|
layout(std430, set = 0, binding = 54) buffer EntDepth { uint entdepth[]; };
|
|
// the rigid bodies (rigid_common.glsl) and the burn state of their pixels (rigid_burn.comp: bits 0-15 the burn age): a burnt pixel is charred
|
|
struct SBody { vec4 p; vec4 v; ivec4 i; vec4 q; vec4 l; vec4 n; vec4 m; ivec4 jn; vec4 ja0, ja1, ja2, ja3; vec4 jb; vec4 d; };
|
|
layout(std430, set = 0, binding = 31) readonly buffer Bodies { ivec4 bhdr; SBody bodies[]; };
|
|
layout(std430, set = 0, binding = 41) readonly buffer Burn { uint burnpx[]; };
|
|
shared int sBody;
|
|
// the burning-cell palette (main.cpp puts colors_lava.bmp after the tables; render.comp / glow.comp read it the same way)
|
|
layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; };
|
|
uint fpal_word(uint k) { return tbl[1024u + 9u * mats[mats.length() - 1u].w + k]; }
|
|
uint burning_texel(uint m, uint h) { // 00724580: a burning cell takes a random pixel of colors_lava.bmp (the row of its fire_colors_index) every frame; alpha 253 marks it for glow.comp
|
|
const uint row = min(fpal_word(132u + m), 5u);
|
|
return (fpal_word(96u + row * 6u + h % 6u) & 0x00FFFFFFu) | 0xFD000000u;
|
|
} // per cell: the depth key of the nearest opaque texel (cleared to ~0 every frame)
|
|
|
|
// The depth of an instance: SpriteComponent z_index (larger = further back; the instance keeps z x 100), then the later instance in front of an earlier one of the same z
|
|
uint gKey = 0xFFFFFFFFu;
|
|
uint gFlash = 0u; // the hit flash (entities.comp hit_flash): 0, or the colour every opaque texel takes (white; red for a critical hit), keeping its alpha
|
|
float gFade = 0.0; // LifetimeComponent fade_sprites (00b9a010): the sprites' alpha is 1 - this (ex10.w of an entity that is not a creature; the host sets it)
|
|
|
|
|
|
// Two passes: 0 draws the ordinary texels, 1 the additive ones (alpha 3: the glow of emissive sprites). The glow is added to what is already in the cell (the creature under
|
|
// its eyes keeps its pixels); drawing both kinds in one dispatch let the writers of a cell race, which made the creature vanish behind its glow in some frames.
|
|
void put_texel(int idx, uint t) {
|
|
uint a = t >> 24;
|
|
if (a == 0u) return;
|
|
if (gFade > 0.0 && a >= 4u && a != 253u && a != 254u) { // (the alpha byte keeps clear of the markers 1-3, 253, 254)
|
|
a = uint(float(a) * (1.0 - gFade) + 0.5);
|
|
if (a < 4u) return;
|
|
a = min(a, 252u);
|
|
t = (t & 0x00FFFFFFu) | (a << 24);
|
|
}
|
|
const bool additive = a == 3u;
|
|
if (gFlash != 0u && !additive && a >= 4u) t = (t & 0xFF000000u) | gFlash;
|
|
if (pc.pass_ == 2) { if (!additive) atomicMin(entdepth[idx], gKey); return; } // depth pass
|
|
if (!additive && entdepth[idx] != gKey) return; // hidden by a nearer texel
|
|
if (additive != (pc.pass_ == 1)) return;
|
|
if (!additive) { entlayer[idx] = a == 1u ? (t | 0x02000000u) & 0xFEFFFFFFu : t; return; }
|
|
const uint cur = entlayer[idx];
|
|
if (cur == 0u) { entlayer[idx] = t; return; }
|
|
const uvec3 s = uvec3((cur >> 16) & 255u, (cur >> 8) & 255u, cur & 255u) + uvec3((t >> 16) & 255u, (t >> 8) & 255u, t & 255u);
|
|
const uvec3 c = min(s, uvec3(255u));
|
|
entlayer[idx] = (cur & 0xFF000000u) | (c.r << 16) | (c.g << 8) | c.b;
|
|
}
|
|
|
|
void main() {
|
|
uint id = gl_WorkGroupID.x;
|
|
ivec4 h0 = entinst[4u * id], hi = entinst[4u * id + 1u], hw = entinst[4u * id + 2u];
|
|
Ent e = ents[h0.x];
|
|
gKey = (uint(clamp(h0.w + 32768, 0, 65535)) << 16) | (65535u - (id & 65535u));
|
|
if ((e.st.w & (8 | 4096)) != 0) return; // dead, or its sprites switched off (4096: a creature, main.cpp on_components_enabled)
|
|
if (e.st.x != 4 && (e.ex8.x & 8192) != 0) return;
|
|
{ // the hit flash: creatures, the player and physics items (a worm segment, kind 0, keeps its root slot in ex19.w)
|
|
const int fc = int(e.ex19.w + 0.5);
|
|
if (e.st.x != 0 && (fc & 7) >= 3) gFlash = (fc & 8) != 0 ? 0xFF0000u : 0xFFFFFFu;
|
|
}
|
|
gFade = (e.st.x == 1 || e.st.x == 2 || e.st.x == 3) ? 0.0 : clamp(e.ex10.w, 0.0, 1.0); // INVISIBILITY active (a prop's ex8.xy is the hit point of its leak) (00b015d0 case 0x51: its sprites are told not to draw)
|
|
{ vec2 pv = prevpos[h0.x].xy; if (distance(pv, e.a.xy) < 10.0) e.a.xy = mix(pv, e.a.xy, ctrl.view.z); } // drawn between the last two ticks
|
|
const bool rigid = (e.st.w & (32 | 64)) != 0; // drawn rotated by the body angle in a.z (64: the part of a Verlet chain, angle in ex7.x)
|
|
bool mobile = e.st.x != 0 && !rigid;
|
|
bool moving = mobile && abs(e.a.z) > 0.08;
|
|
ivec4 an = moving ? hw : hi; // tex, w, h, frames | wait << 16
|
|
int w = an.y, h = an.z, frames = an.w & 0xFFFF, wait = max(an.w >> 16, 1);
|
|
int frame = (pc.frame / wait + int(h0.x)) % max(frames, 1);
|
|
{ // an action the host played (the attacks of the AnimalAI): its animation once from its start frame (row 4 of the instance: texture, frames | wait << 16, start frame, w | h << 16)
|
|
const ivec4 hx = entinst[4u * id + 3u];
|
|
const bool loopA = hx.z < 0; // (a negative start: start + 1 negated, a movement animation that loops: jump_up, jump_fall, swim_*, fly_*, burn)
|
|
const int af = max(hx.y & 0xFFFF, 1), aw = max(hx.y >> 16, 1), t = pc.frame - (loopA ? -hx.z - 1 : hx.z);
|
|
if (hx.x > 0 && t >= 0 && (loopA || t < af * aw) && (hx.w & 0xFFFF) > 0 && (hx.w >> 16) > 0) { w = hx.w & 0xFFFF; h = hx.w >> 16; an = ivec4(hx.x, w, h, hx.y); frames = af; wait = aw; frame = loopA ? (t / aw) % af : t / aw; } // (its own frame size: the zombie attacks in 19 x 20 frames, stands in 18 x 19; the sprite offset is the file's)
|
|
}
|
|
if (rigid) {
|
|
if (gl_LocalInvocationID.x == 0u) sBody = -1;
|
|
barrier();
|
|
for (int k = int(gl_LocalInvocationID.x); k < min(bhdr.x, 1024); k += 64) if (bodies[k].i.y == int(h0.x) && (bodies[k].i.z & 2) == 0) sBody = k;
|
|
barrier();
|
|
const int body = (e.st.w & 64) == 0 && w * h <= 4096 ? sBody : -1;
|
|
int rf = (pc.frame / wait + int(h0.x)) % max(frames, 1);
|
|
const float ang = (e.st.w & 64) != 0 ? e.ex7.x : e.a.z;
|
|
const float sx = (e.st.w & 64) != 0 && e.ex7.y > 0.0 ? e.ex7.y : 1.0; // a part stretched along its length (the segments of a limb: IKLimbComponent)
|
|
float c = cos(ang), s = sin(ang);
|
|
vec2 anchor = vec2(float(h0.y), float(h0.z));
|
|
float r = length(max(anchor, vec2(float(w), float(h)) - anchor) * vec2(max(sx, 1.0), 1.0)) + 1.5;
|
|
int n = int(2.0 * r) + 2;
|
|
ivec2 lo = ivec2(floor(e.a.xy - vec2(r))) - ivec2(pc.view_x, pc.view_y);
|
|
int rbase = an.x + rf * w * h;
|
|
for (int i = int(gl_LocalInvocationID.x); i < n * n; i += 64) {
|
|
ivec2 p = lo + ivec2(i % n, i / n);
|
|
if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) continue;
|
|
vec2 d = vec2(p + ivec2(pc.view_x, pc.view_y)) + vec2(0.5) - e.a.xy;
|
|
ivec2 sp = ivec2(floor(anchor + vec2((c * d.x + s * d.y) / sx, -s * d.x + c * d.y)));
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= w || sp.y >= h) continue;
|
|
uint t = enttex[uint(rbase + sp.y * w + sp.x)];
|
|
if (body >= 0 && (t >> 24) > 3u) { // 0070e060: a burnt solid loses one of every channel per eight of its burn age
|
|
const uint bst = burnpx[uint(body) * 4096u + uint(sp.y * w + sp.x)];
|
|
const uint bm = uint(e.ex5.z + 0.5); // (the body's material + 1, as rigid_burn.comp reads it)
|
|
if ((bst & 0x80000000u) != 0u && bm > 0u) { put_texel(p.y * pc.gw + p.x, burning_texel(bm - 1u, hash3(uint(sp.x + sp.y * w), uint(body), uint(pc.frame)))); continue; } // a burning pixel: the fire colours
|
|
const uint dk = (bst & 0xFFFFu) >> 3;
|
|
if (dk > 0u) { const uvec3 c3 = uvec3((t >> 16) & 255u, (t >> 8) & 255u, t & 255u); const uvec3 r3 = uvec3(max(ivec3(c3) - int(dk), ivec3(0))); t = (t & 0xFF000000u) | (r3.r << 16) | (r3.g << 8) | r3.b; }
|
|
}
|
|
put_texel(p.y * pc.gw + p.x, t); // alpha 1 is the marker of additive light in render.comp: faint pixels (the halo of the portal) take alpha 2
|
|
}
|
|
return;
|
|
}
|
|
bool flip = mobile && e.st.y < 0;
|
|
ivec2 pos = ivec2(floor(e.a.xy));
|
|
int ox = flip ? (w - 1 - h0.y) : h0.y;
|
|
ivec2 origin = pos - ivec2(ox, h0.z) - ivec2(pc.view_x, pc.view_y); // window position of the sprite top-left
|
|
if (origin.x >= pc.gw || origin.y >= pc.gh || origin.x + w <= 0 || origin.y + h <= 0) return;
|
|
int base = an.x + frame * w * h;
|
|
for (int i = int(gl_LocalInvocationID.x); i < w * h; i += 64) {
|
|
int sxp = i % w;
|
|
ivec2 p = origin + ivec2(sxp, i / w);
|
|
if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) continue;
|
|
uint t = enttex[uint(base + (flip ? (i / w) * w + (w - 1 - sxp) : i))];
|
|
if (h0.x == 0u && (t >> 24) == 255u) t = (t & 0x00FFFFFFu) | 0xFE000000u; // the player's pixels (alpha 254): render.comp draws them at the sub-cell position
|
|
put_texel(p.y * pc.gw + p.x, t);
|
|
}
|
|
}
|