* Adding clipper to generate the terrain's negative powder-contour offset. * Fixing RDB issues (fracture, active trigger, performances) * Fixing blood emission * Fixing the freezing formula on liquids * Fixing Steve spawning due to a missed test. * Reaction table properly build
196 lines
13 KiB
Plaintext
196 lines
13 KiB
Plaintext
#version 450
|
|
#extension GL_GOOGLE_include_directive : require
|
|
layout(local_size_x = 256) in;
|
|
#include "rigid_common.glsl"
|
|
|
|
// Fire inside the rigid bodies of a burnable material (docs/FIRE_FRACTURE_AND_LIQUIDS.md, DISPLACEMENT_AND_FIRE.md, CEILING_ATTACHMENT_AND_CONTACT_GEOMETRY.md).
|
|
// One workgroup per body, a thread per group of pixels. A pixel has a state word in `burn`: bit 31 burning, bit 30 burnt away (reported to the host, which removes it from
|
|
// the body and splits it), bits 16-23 fade, bits 0-15 the burn age. A burning pixel does what a burning solid cell does:
|
|
// * common burning (0070fb10): flames into (x-1..x+1, y-1) with generates_flames percent, smoke, requires_oxygen fade (restored by an accepted flame, else -1 per update,
|
|
// burning stops below 1), and one spread attempt: r % 101 <= fade, then ONE of the four neighbours, then the ignition draw r % 101 < 100 - autoignition_temperature;
|
|
// * then the age grows, and on every 32nd frame a pixel with fire_hp != -1 and age > fire_hp is removed.
|
|
// A pixel that is not burning catches fire from the fire sources in its cell and the four around it with the probabilities of burn.comp. Water puts it out.
|
|
layout(std430, set = 0, binding = 41) buffer Burn { uint burn[]; };
|
|
layout(std430, set = 0, binding = 23) buffer Ctrl { ivec4 inp; ivec4 inp2; ivec4 inp3; vec4 outp; vec4 outp2; vec4 view; vec4 hand0; ivec4 hand1; vec4 hand2; ivec4 burnN; ivec4 burnEv[256]; } ctrl;
|
|
|
|
const uint BURNING = 0x80000000u, BURNT = 0x40000000u;
|
|
|
|
float rnd(uint a, uint b, uint c) { return float(hash3(a, b, c) & 0xFFFFu) / 65536.0; }
|
|
layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; }; // 1024 noise words, 4 per material (burning explosion), then 2 per material: flame and smoke material + 1
|
|
uint flame_material(uint m, uint dflt) { const uint v = tbl[1024u + 4u * mats[mats.length() - 1u].w + 2u * m]; return v != 0u ? v - 1u : dflt; }
|
|
uint smoke_material(uint m, uint dflt) { const uint v = tbl[1024u + 4u * mats[mats.length() - 1u].w + 2u * m + 1u]; return v != 0u ? v - 1u : dflt; }
|
|
|
|
#include "biome_mods.glsl"
|
|
layout(std430, set = 0, binding = 28) readonly buffer RHead { uint rhead[]; };
|
|
layout(std430, set = 0, binding = 29) readonly buffer RRec { uint rrec[]; };
|
|
const ivec2 TD4[4] = ivec2[4](ivec2(0,-1), ivec2(-1,0), ivec2(1,0), ivec2(0,1));
|
|
uint native_material(uint m) { const uint n = mats[mats.length()-1u].w; return rhead[2u*n*(n+1u)+m]; }
|
|
uint body_neighbor(ivec2 q, Body B, int s, vec2 com, float cs, float sn, uint m, uint base) {
|
|
const ivec2 grid = q - ivec2(pc.view_x, pc.view_y);
|
|
if (grid.x < 0 || grid.y < 0 || grid.x >= pc.gw || grid.y >= pc.gh) return NONE;
|
|
const ivec2 local = ivec2(floor(rot2(cs, -sn, vec2(q) + 0.5 - B.p.xy) + com));
|
|
if (shape_solid(s, local.x, local.y) && (burn[base + uint(local.y*shi(s,0)+local.x)] & BURNT) == 0u) return m;
|
|
return cell_mat(cells[grid.y*pc.gw+grid.x]);
|
|
}
|
|
// Solid-cell 0070e060 calls the combined/fast paths when body record +0x2e
|
|
// permits them. Ice glass must keep that path after leaving the terrain grid.
|
|
int solid_conversion(ivec2 wc, Body B, int s, vec2 com, float cs, float sn, uint m, uint base, uint seed) {
|
|
if ((B.i.z & 65536) != 0) return -1; // native collapse record disables reactions
|
|
const uint r = hash3(seed, uint(pc.frame), 40u) & 32767u;
|
|
const uint R = r % 10001u;
|
|
const uint dir = hash3(seed, uint(pc.frame), 41u) & 3u;
|
|
const ivec2 q = wc + TD4[dir];
|
|
const uint nm = body_neighbor(q, B, s, com, cs, sn, m, base);
|
|
if (nm == NONE) return -1;
|
|
const bool full = (pc.frame % 11) == 0;
|
|
if (full) {
|
|
const uint bb = bm_bytes(vec2(wc));
|
|
const uint fc = (bb >> 16) & 255u, uc = bb >> 24;
|
|
const uvec2 cv = bm_conversions(m);
|
|
const bool cold = fc != 0u && (cv.x & 65535u) != 0u && (hash3(seed,uint(pc.frame),42u)&32767u) % ((cv.x>>16)+1u) < fc;
|
|
const bool warm = uc != 0u && (cv.y & 65535u) != 0u && (hash3(seed,uint(pc.frame),43u)&32767u) % ((cv.y>>16)+1u) < uc;
|
|
if (cold) return nm == 0u || (mflags(nm)&F_LIQCELL) == 0u || (mflags(nm)&F_POWDER) != 0u ? int((cv.x&65535u)-1u) : -1;
|
|
if (warm) return native_material(nm) != native_material(m) ? int((cv.y&65535u)-1u) : -1;
|
|
}
|
|
const uint n = mats[mats.length()-1u].w, table = full ? 0u : n*(n+1u);
|
|
const uint h = rhead[table + m*n+nm], count = h & 255u;
|
|
if (count == 0u || rhead[table+n*n+m] == 0u || R > rhead[table+n*n+m]) return -1;
|
|
const uint e = ((h>>8) + R%count)*5u, pw = rrec[e+1u];
|
|
const uint requiredDir = (pw>>16)&7u, actualDir = dir == 0u ? 1u : dir == 1u ? 3u : dir == 2u ? 4u : 2u;
|
|
if (R > (pw&65535u) || (requiredDir != 0u && requiredDir != actualDir)) return -1;
|
|
// Complex sprite reactions need per-pixel material storage; leave those to
|
|
// the existing fracture path rather than applying only half their outputs.
|
|
if ((rrec[e+2u]&65535u) != 65535u || (rrec[e+3u]&65535u) != 0u || (rrec[e+4u]&0x7FFFFFFFu) != 0u) return -1;
|
|
const uint target = rrec[e]&65535u, other = rrec[e]>>16;
|
|
if (target == 65535u || native_material(target) == native_material(m)) return -1;
|
|
if (other != 65535u && native_material(other) != native_material(nm)) return -1;
|
|
return int(target);
|
|
}
|
|
|
|
void main() {
|
|
int bi = int(gl_WorkGroupID.x);
|
|
if (bi >= bhdr.x) return;
|
|
Body B = bodies[bi];
|
|
if ((B.i.z & B_REMOVED) != 0 || B.i.y < 0) return;
|
|
const uint matp1 = uint(ents[B.i.y].ex5.z + 0.5);
|
|
if (matp1 == 0u) return;
|
|
const uint m = matp1 - 1u;
|
|
const uint mz = mats[3u * m + 2u].z, mw = mats[3u * m + 2u].w;
|
|
// Most rigid materials have no fast reactions. Avoid visiting their pixels
|
|
// on the ten ordinary updates between combined updates.
|
|
if (((mz >> 16) & 1u) == 0u) {
|
|
if ((B.i.z & 65536) != 0) return;
|
|
const uint n = mats[mats.length()-1u].w;
|
|
if ((pc.frame % 11) != 0 && rhead[n*(n+1u)+n*n+m] == 0u) return;
|
|
}
|
|
const int s = B.i.x;
|
|
const int w = shi(s, 0), h = shi(s, 1);
|
|
if (w * h > 4096) return;
|
|
const vec2 com = vec2(shf(s, 5), shf(s, 6));
|
|
const float cs = cos(B.p.z), sn = sin(B.p.z);
|
|
const uvec4 sp = mats[mats.length() - 1u]; // fire, smoke, steam, count
|
|
const int ai = int(mw & 255u), tempFire = int((mw >> 8) & 255u), smokeP = int((mw >> 16) & 255u), flameP = int((mw >> 24) & 255u);
|
|
const int fireHp = int(mz << 16) >> 16; // signed 16 bits
|
|
const bool noBurnOut = fireHp == -1;
|
|
const bool needO2 = ((mz >> 19) & 1u) != 0u;
|
|
const float pIgnite = max(0.0, float(100 - ai)) / 101.0;
|
|
const uint base = uint(bi) * 4096u;
|
|
for (int px = int(gl_LocalInvocationID.x); px < w * h; px += 256) {
|
|
const ivec2 pp = ivec2(px % w, px / w);
|
|
if (!shape_solid(s, pp.x, pp.y)) continue;
|
|
uint st = burn[base + uint(px)];
|
|
if ((st & BURNT) != 0u) continue;
|
|
const vec2 wp = B.p.xy + rot2(cs, sn, vec2(pp) + 0.5 - com);
|
|
const ivec2 wc = ivec2(floor(wp));
|
|
const uint seed = uint(bi) * 4099u + uint(px);
|
|
if ((st & BURNING) == 0u) {
|
|
const int target = solid_conversion(wc, B, s, com, cs, sn, m, base, seed);
|
|
if (target >= 0 && (mflags(uint(target)) & (F_LIQCELL | F_GAS | F_FIRE | F_AIR)) != 0u) {
|
|
const ivec2 grid = wc - ivec2(pc.view_x, pc.view_y);
|
|
if (grid.x >= 0 && grid.y >= 0 && grid.x < pc.gw && grid.y < pc.gh) {
|
|
const int index = grid.y*pc.gw+grid.x;
|
|
const int ei = atomicAdd(ctrl.burnN.x, 1);
|
|
if (ei < 256) {
|
|
if (atomicCompSwap(cells[index], 0u, uint(target)) == 0u) {
|
|
material_changed(index, 0u, uint(target));
|
|
ctrl.burnEv[ei] = ivec4(bi, px, target+1, 0);
|
|
burn[base+uint(px)] = BURNT;
|
|
continue;
|
|
}
|
|
ctrl.burnEv[ei] = ivec4(-1);
|
|
} else atomicAdd(ctrl.burnN.x, -1);
|
|
}
|
|
}
|
|
if (((mz >> 16) & 1u) == 0u) continue;
|
|
// fire sources in the pixel's cell and the four around it (cells are free under a body)
|
|
float keep = 1.0, bestFade = -1.0;
|
|
bool wet = false;
|
|
for (int d = 0; d < 5; ++d) {
|
|
const ivec2 q = wc + ivec2(d == 1 ? 1 : d == 2 ? -1 : 0, d == 3 ? 1 : d == 4 ? -1 : 0) - ivec2(pc.view_x, pc.view_y);
|
|
if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue;
|
|
const uint cw = cells[q.y * pc.gw + q.x], cm = cell_mat(cw);
|
|
if (cm == 0u) continue;
|
|
const uint nz = mats[3u * cm + 2u].z, nw = mats[3u * cm + 2u].w;
|
|
if (((nz >> 18) & 1u) != 0u) wet = true;
|
|
const bool nBurning = (cw & BURN_BIT) != 0u;
|
|
if (!(nBurning || ((nz >> 17) & 1u) != 0u)) continue;
|
|
int nfi = fade_of(birth[q.y * pc.gw + q.x]);
|
|
if (!nBurning && nfi == 0) nfi = int((nw >> 8) & 255u);
|
|
const float nf = float(nfi);
|
|
const float gate = nfi < 0 ? 0.0 : min(nf + 1.0, 101.0) / 101.0;
|
|
float pSrc = gate * 0.25 * pIgnite;
|
|
if ((mflags(cm) & F_FIRE) != 0u) pSrc = 1.0 - (1.0 - pSrc) * (1.0 - pSrc); // a fire cell attempts twice
|
|
keep *= 1.0 - min(pSrc, 1.0);
|
|
if (rnd(seed, uint(pc.frame), 20u + uint(d)) < pSrc && bestFade < 0.0) bestFade = nf; // the fade of the source that ignited it
|
|
}
|
|
if (!wet && bestFade >= 0.0 && rnd(seed, uint(pc.frame), 1u) > keep) burn[base + uint(px)] = BURNING | (uint(min(bestFade, 255.0)) << 16) | (st & 0xFFFFu);
|
|
continue;
|
|
}
|
|
// burning
|
|
uint age = st & 0xFFFFu;
|
|
int fade = int((st >> 16) & 255u);
|
|
bool wet = false;
|
|
for (int d = 0; d < 5 && !wet; ++d) {
|
|
const ivec2 q = wc + ivec2(d == 1 ? 1 : d == 2 ? -1 : 0, d == 3 ? 1 : d == 4 ? -1 : 0) - ivec2(pc.view_x, pc.view_y);
|
|
if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue;
|
|
const uint cm = cell_mat(cells[q.y * pc.gw + q.x]);
|
|
wet = cm != 0u && ((mats[3u * cm + 2u].z >> 18) & 1u) != 0u;
|
|
}
|
|
if (wet) { burn[base + uint(px)] = age; continue; }
|
|
bool flameAccepted = false;
|
|
if (flameP > 0 && int(rnd(seed, uint(pc.frame), 2u) * 101.0) < flameP) {
|
|
const ivec2 t = wc + ivec2(int(rnd(seed, uint(pc.frame), 3u) * 3.0) - 1, -1) - ivec2(pc.view_x, pc.view_y);
|
|
if (t.x >= 0 && t.y >= 0 && t.x < pc.gw && t.y < pc.gh) {
|
|
const int ti = t.y * pc.gw + t.x;
|
|
const uint cur = cells[ti];
|
|
if (cell_mat(cur) == 0u) { // an empty place (the word may hold a leftover colour variation): the flame takes it
|
|
flameAccepted = atomicCompSwap(cells[ti], cur, flame_material(m, sp.x) | (hash3(seed, uint(pc.frame), 4u) & 0xFF0000u)) == cur;
|
|
if (flameAccepted) birth[ti] = with_fade(birth_frame(), int((mats[3u * flame_material(m, sp.x) + 2u].w >> 8) & 255u)); // (0070f8c0: a new flame has its material's own fade, temperature_of_fire; the intensity is a separate field)
|
|
}
|
|
}
|
|
}
|
|
if (needO2) fade = flameAccepted ? tempFire : fade - 1;
|
|
if (smokeP > 0 && int(rnd(seed, uint(pc.frame), 6u) * 101.0) < smokeP) {
|
|
const ivec2 t = wc + ivec2(int(rnd(seed, uint(pc.frame), 7u) * 3.0) - 1, -1) - ivec2(pc.view_x, pc.view_y);
|
|
if (t.x >= 0 && t.y >= 0 && t.x < pc.gw && t.y < pc.gh) atomicCompSwap(cells[t.y * pc.gw + t.x], 0u, smoke_material(m, sp.y) | (hash3(seed, uint(pc.frame), 8u) & 0xFF0000u));
|
|
}
|
|
if (fade < 1) { burn[base + uint(px)] = age; continue; } // out of oxygen: the pixel stops burning
|
|
// one spread attempt towards one of the four neighbours inside the body
|
|
if (int(hash3(seed, uint(pc.frame), 9u) % 101u) <= fade) {
|
|
const uint dsel = hash3(seed, uint(pc.frame), 10u) & 3u;
|
|
const ivec2 np = pp + (dsel == 0u ? ivec2(0, -1) : dsel == 1u ? ivec2(-1, 0) : dsel == 2u ? ivec2(1, 0) : ivec2(0, 1));
|
|
if (shape_solid(s, np.x, np.y) && int(hash3(seed, uint(pc.frame), 11u) % 101u) < 100 - ai) {
|
|
const uint ni = base + uint(np.y * w + np.x), old = burn[ni];
|
|
if ((old & (BURNING | BURNT)) == 0u) atomicCompSwap(burn[ni], old, BURNING | (uint(fade) << 16) | (old & 0xFFFFu));
|
|
}
|
|
}
|
|
age = (age + 1u) & 0xFFFFu; // 0070e060: a wrapping 16-bit age
|
|
if ((pc.frame & 31) == 0 && !noBurnOut && fireHp < int(age)) {
|
|
const int ei = atomicAdd(ctrl.burnN.x, 1);
|
|
if (ei < 256) { ctrl.burnEv[ei] = ivec4(bi, px, 0, 0); burn[base + uint(px)] = BURNT | age; continue; }
|
|
atomicAdd(ctrl.burnN.x, -1);
|
|
}
|
|
burn[base + uint(px)] = BURNING | (uint(fade) << 16) | age;
|
|
}
|
|
}
|