#version 450 #extension GL_GOOGLE_include_directive : require layout(local_size_x = 8, local_size_y = 8) in; #include "common.glsl" #include "far.glsl" #include "fx_state.glsl" // Explosions and spills: circular edits of the cell grid, two vec4 per edit. // e[2k] = (world x, world y, radius, max durability) // e[2k+1] = (mode, material, probability, 0) mode 0: dig (cells whose durability is at most the limit become air) // mode 1: fill air cells with the material, each with the given probability // mode 4: a flask sucks the liquid: the cell becomes air when it still holds material e[2k+1].y // mode 3: the same with a velocity (a poured flask): e[2k].w = horizontal velocity (cells per frame), e[2k+1].w = vertical velocity // mode 5: CellEaterComponent (00a639e0): every cell inside the radius whose material is not e[2k+1].y (ignored_material) and not [indestructible] // (with ignored_material_tag) is removed with probability e[2k+1].z (eat_probability); e[2k].w = 1: only_stain (nothing removed) // mode 6: MagicConvertMaterialComponent (00bab8c0 / 00baac80): the rings e[2k].z .. e[2k].w (excluded) of the square (e[2k+1].w = 0) or circle (1) // around the point: a cell of material e[2k+1].y (-1: any material) becomes e[2k+1].z // mode 7: a line from (e[2k].x, e[2k].y) to (e[2k].z, e[2k].w): its empty cells become material e[2k+1].y (an ArcComponent of type MATERIAL; the host // cut the line at the first cell of another material) // mode 8: MaterialSeaSpawnerComponent (00bbc060), two records: (left x, top y, width, first row) (8, material, end row, 0), then // (sine_wavelength, sine_amplitude, noise_scale, noise_threshold) (0, 0, 0, 0): the empty cells of the rows // first .. end (excluded) at y = top + row + int(sin(x / wavelength) x amplitude) where 3D simplex noise (00872560: // x scale, y scale, 0) x 0.5 + 0.5 >= threshold become the material // mode 10: every non-air cell not e[2k+1].y / .z becomes e[2k+1].z when static, else .y (ConvertEverythingToGold) // mode 11: the one cell at (x, y) is set on fire with the fade e[2k+1].y: ordinary ignition (0070f5e0: a burnable cell, not burning, // r % 101 < 100 - autoignition_temperature) or, with e[2k+1].z = 1, forced (0070f750: no draw) - TorchComponent, hack_is_set_fire // mode 9: AnimalAI eating (00a232e0): the cells of material e[2k+1].y inside the box (x, y) +- (e[2k].z, e[2k].w) become, each with // probability e[2k+1].z, air (e[2k+1].w = 0) or material e[2k+1].w - 1 (food_particle_effect_material) // Up to 256 edits per frame. layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; }; // after 1024 + 8 words per material: the material tag bits (one word per material, bits assigned by the host) layout(std430, set = 0, binding = 27) readonly buffer Edits { ivec4 head; vec4 e[512]; } ed; // filled by the host and by the GPU projectiles #include "simplex3.glsl" // pass 1: the far blocks (far.glsl) - per edit (z) the cells of a 128 x 128 box around its point outside the window: dig (0), fill (1), the cell eater (5), // magic convert (6: freezing charge's liquids to ice); // the slot is marked dirty (main.cpp copies it back to the store) void far_pass() { const int k = int(gl_GlobalInvocationID.z); if (k >= ed.head.x || k >= 256) return; const vec4 q = ed.e[2 * k], r = ed.e[2 * k + 1]; const int mode = int(r.x + 0.5); if (mode != 0 && mode != 1 && mode != 5 && mode != 6 && mode != 12) return; const ivec2 wc = ivec2(floor(q.xy)) - 64 + ivec2(gl_GlobalInvocationID.xy); const ivec2 lp = wc - ivec2(pc.view_x, pc.view_y); if (lp.x >= 0 && lp.y >= 0 && lp.x < pc.gw && lp.y < pc.gh) return; // (the window's own pass) const vec2 w = vec2(wc) + 0.5; if (mode != 6 && distance(w, q.xy) > q.z) return; const int s = far_slot(wc); if (s == 0) return; const int fi = far_index(s, wc); const uint c = farcells[fi], m = cell_mat(c); const uint hi = uint(fi) * 2654435761u; if (mode == 12) { // a frozen stain (as the window's) if (m == 0u || (mflags(m) & F_STATIC) == 0u || (mflags(m) & F_FIRE) != 0u || (c & FROZEN_BIT) != 0u) return; farcells[fi] = c | FROZEN_BIT; } else if (mode == 6) { // (as the window's: the rings q.z .. q.w of the circle / square, from r.y (-1: any) to r.z) if (m == 0u) return; const vec2 d = w - q.xy; const int ring = r.w > 0.5 ? int(length(d) + 0.5) : int(max(abs(d.x), abs(d.y))); if (ring < int(q.z) || ring >= int(q.w) || (r.y >= 0.0 && m != uint(r.y)) || m == uint(r.z)) return; farcells[fi] = uint(r.z) | (((hash3(hi, uint(pc.frame), uint(k) + 41u) >> 16) & 0xFFu) << 16); } else if (mode == 5) { if (m == 0u || (r.y >= 0.0 && m == uint(r.y)) || (q.w > 1.5 && (mflags(m) & F_INDESTRUCT) != 0u)) return; if (r.w > 0.5 && ((tbl[1024u + 8u * mats[mats.length() - 1u].w + m] >> uint(r.w - 1.0)) & 1u) != 0u) return; if (float(hash3(hi, uint(pc.frame), uint(k) + 37u) % 101u) >= r.z * 100.0 || q.w == 1.0 || q.w == 3.0) return; farcells[fi] = 0u; } else if (mode == 0) { if (m == 0u || uintBitsToFloat(mats[3u * m + 2u].y) > q.w) return; farcells[fi] = 0u; } else { if (m != 0u) return; const uint h = hash3(hi, uint(pc.frame), uint(k) + 31u); if (float(h & 0xFFFFu) / 65536.0 >= r.z) return; farcells[fi] = uint(r.y) | (((h >> 16) & 0xFFu) << 16); } farmap[kFarDirty + s - 1] = 1; } void main() { if (pc.pass_ == 1) { far_pass(); return; } ivec2 p = ivec2(gl_GlobalInvocationID.xy); if (p.x >= pc.gw || p.y >= pc.gh) return; int i = p.y * pc.gw + p.x; uint m = cell_mat(cells[i]); vec2 w = vec2(p + ivec2(pc.view_x, pc.view_y)) + 0.5; for (int k = 0; k < ed.head.x && k < 256; ++k) { vec4 q = ed.e[2 * k], r = ed.e[2 * k + 1]; if (r.x > 1.5 && r.x < 2.5) continue; // mode 2 (angular explosion) is explode_rays.comp if (r.x > 10.5 && r.x < 11.5) { // ignite one cell if (p + ivec2(pc.view_x, pc.view_y) != ivec2(floor(q.xy)) || m == 0u || ((mats[3u * m + 2u].z >> 16) & 1u) == 0u || (cells[i] & BURN_BIT) != 0u) continue; if (r.z < 0.5 && int(hash3(uint(i), uint(pc.frame), uint(k) + 71u) % 101u) >= 100 - int(mats[3u * m + 2u].w & 255u)) continue; cells[i] |= BURN_BIT; birth[i] = with_fade(birth[i], int(r.y)); continue; } if (r.x > 12.5 && r.x < 14.5) { // the player digs out of the ground (main.cpp, 00bfa850): q = the box; 13: its loose cells fly out as real particles (007175c0: not a // liquid_static material - the powders and liquids), r.y = the speed s; 14: every cell of the box goes (stuck in the static ground for long) if (m == 0u || w.x < q.x || w.x > q.z + 1.0 || w.y < q.y || w.y > q.w + 1.0) continue; if (r.x > 13.5) { cells[i] = 0u; ext[i] = vec2(0.0); m = 0u; continue; } if ((mflags(m) & F_STATIC) != 0u || (mflags(m) & (F_LIQUID | F_SAND)) == 0u) continue; const uint h1 = hash3(uint(i), uint(pc.frame), uint(k) + 81u), h2 = hash3(uint(i), uint(pc.frame), uint(k) + 83u); const float s = r.y, c = mix(-1.0, -0.65, float(h1 & 0xFFFFu) / 65536.0) * s, b = mix(-0.5, 0.5, float(h1 >> 16) / 65536.0) * s; const float a = float(h2 & 0xFFFFu) / 65536.0 - 0.5, mul = 0.65 + 0.4 * float(h2 >> 16) / 65536.0; const vec2 v = vec2(cos(a) * b - sin(a) * c, sin(a) * b + cos(a) * c) * mul / 60.0; if (emit_material_particle(w, v, 600.0, PART_GRAV, mats[3u * m].x, int(m))) { cells[i] = 0u; ext[i] = vec2(0.0); m = 0u; } continue; } if (r.x > 11.5 && r.x < 12.5) { // a frozen stain (MagicConvertMaterialComponent stain_frozen: freeze_charge.xml): the static cells inside the radius look frozen (FROZEN_BIT on a static cell) if (m != 0u && distance(w, q.xy) <= q.z && (mflags(m) & F_STATIC) != 0u && (mflags(m) & F_FIRE) == 0u) cells[i] |= FROZEN_BIT; continue; } if (r.x > 4.5 && r.x < 5.5) { // cell eater if (m == 0u || distance(w, q.xy) > q.z) continue; if ((r.y >= 0.0 && m == uint(r.y)) || (q.w > 1.5 && (mflags(m) & F_INDESTRUCT) != 0u)) continue; if (r.w > 0.5 && ((tbl[1024u + 8u * mats[mats.length() - 1u].w + m] >> uint(r.w - 1.0)) & 1u) != 0u) continue; // ignored_material_tag if (float(hash3(uint(i), uint(pc.frame), uint(k) + 37u) % 101u) >= r.z * 100.0) continue; if (q.w == 1.0 || q.w == 3.0) continue; // only_stain: the colour darkens in the engine (not drawn here) { // 00a639e0: the eaten cell leaves a cosmetic particle of its material (00716ce0 force-created at the cell, velocity uniform(-30, 30) px/s on each axis, // lifetime arguments 5 .. 10): the debris in the tunnel of a black hole. TODO(to-do #36): the unit of that lifetime - the real game's debris // (spell_reference/BH_SPEED_PLAIN.gif) is gone after 0.5 .. 1 s, so 30 .. 60 frames here const uint h = hash3(uint(i), uint(pc.frame), uint(k) + 39u); const vec2 v = (vec2(float(h & 0xFFFFu), float(h >> 16)) / 65536.0 * 60.0 - 30.0) / 60.0; emit_particle_g(w, v, 6.0 * (5.0 + 5.0 * float(hash3(uint(i), uint(pc.frame), 41u) & 0xFFFFu) / 65536.0), PART_GRAV, mats[3u * m].x, false, false, true); } cells[i] = 0u; ext[i] = vec2(0.0); m = 0u; continue; } if (r.x > 8.5 && r.x < 9.5) { // a creature eats if (m == 0u || m != uint(r.y) || abs(w.x - q.x) > q.z + 0.5 || abs(w.y - q.y) > q.w + 0.5) continue; if (float(hash3(uint(i), uint(pc.frame), uint(k) + 53u) & 0xFFFFu) / 65536.0 >= r.z) continue; if (r.w < 0.5) { cells[i] = 0u; ext[i] = vec2(0.0); m = 0u; continue; } const uint h = hash3(uint(i), uint(pc.frame), uint(k) + 59u); m = uint(r.w - 1.0); cells[i] = m | (((h >> 16) & 0xFFu) << 16); ext[i] = vec2(0.0); birth[i] = birth_frame(); continue; } if (r.x > 5.5 && r.x < 6.5) { // magic convert if (m == 0u) continue; const vec2 d = w - q.xy; const int ring = r.w > 0.5 ? int(length(d) + 0.5) : int(max(abs(d.x), abs(d.y))); if (ring < int(q.z) || ring >= int(q.w)) continue; if ((r.y >= 0.0 && m != uint(r.y)) || m == uint(r.z)) continue; const uint h = hash3(uint(i), uint(pc.frame), uint(k) + 41u); cells[i] = uint(r.z) | (((h >> 16) & 0xFFu) << 16); ext[i] = vec2(0.0); birth[i] = birth_frame(); m = uint(r.z); continue; } if (r.x > 7.5 && r.x < 8.5) { // a material sea (its parameters are the next record) const vec4 sp = ed.e[2 * k + 2]; k += 1; if (m != 0u) continue; const int xi = int(floor(w.x)), yi = int(floor(w.y)); if (xi < int(q.x) || xi >= int(q.x) + int(q.z)) continue; const int row = yi - int(q.y) - int(sin(float(xi) / sp.x) * sp.y); if (row < int(q.w) || row >= int(r.z)) continue; if (simplex3(vec3(float(xi) * sp.z, float(yi) * sp.z, 0.0)) * 0.5 + 0.5 < sp.w) continue; const uint h = hash3(uint(i), uint(pc.frame), uint(k) + 47u); cells[i] = uint(r.y) | (((h >> 16) & 0xFFu) << 16); ext[i] = vec2(0.0); birth[i] = birth_frame(); m = uint(r.y); continue; } if (r.x > 9.5 && r.x < 10.5) { // everything to gold (00704f70: every material but air is redefined as gold, the static ones as gold_static; here the cells take it) if (m == 0u || m == uint(r.y) || m == uint(r.z)) continue; const uint g = (mflags(m) & F_STATIC) != 0u ? uint(r.z) : uint(r.y); const uint h = hash3(uint(i), uint(pc.frame), uint(k) + 61u); cells[i] = g | (((h >> 16) & 0xFFu) << 16); ext[i] = vec2(0.0); birth[i] = birth_frame(); m = g; continue; } if (r.x > 6.5 && r.x < 7.5) { // a material line if (m != 0u) continue; const vec2 ab = q.zw - q.xy; const float l2 = dot(ab, ab); const float t = l2 > 0.0 ? clamp(dot(w - q.xy, ab) / l2, 0.0, 1.0) : 0.0; if (distance(w, q.xy + ab * t) > 0.5) continue; const uint h = hash3(uint(i), uint(pc.frame), uint(k) + 43u); cells[i] = uint(r.y) | (((h >> 16) & 0xFFu) << 16); ext[i] = vec2(0.0); birth[i] = birth_frame(); m = uint(r.y); continue; } if (distance(w, q.xy) > q.z) continue; const bool moving = r.x > 2.5 && r.x < 3.5; if (r.x > 3.5) { if (m == uint(r.y)) { cells[i] = 0u; ext[i] = vec2(0.0); m = 0u; } continue; } if (r.x < 0.5) { if (m != 0u && uintBitsToFloat(mats[3u * m + 2u].y) <= q.w) { cells[i] = 0u; ext[i] = vec2(0.0); m = 0u; } } else if (m == 0u) { uint h = hash3(uint(i), uint(pc.frame), uint(k) + 31u); if (float(h & 0xFFFFu) / 65536.0 < r.z) { cells[i] = uint(r.y) | (((h >> 16) & 0xFFu) << 16); ext[i] = moving ? vec2(r.w, q.w) : vec2(0.0); // x = vertical, y = horizontal velocity birth[i] = birth_frame(); m = uint(r.y); } } } }