#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); } }