This commit is contained in:
Gaël committed 2026-10-19 20:59:15 +02:00
commit 87bff164f1
416 files changed
+279512

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
#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);
}
}
}
}