mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-30 00:27:29 +01:00
255 lines
10 KiB
Plaintext
255 lines
10 KiB
Plaintext
#version 460 core
|
|
#define STAGE_COMPUTE 1
|
|
// the water on the windscreens (water_sim.glsl): every drop through the step. a thread per drop, a row of groups per glass.
|
|
// a blade going over the drop takes it along in front of itself until it turns back at the end of its sweep or stops, or until
|
|
// the drop is pushed out of the sweep; a drop of its own slides when gravity and the air pull it harder than its line of
|
|
// contact holds it, wetting its path and leaving droplets behind; a flake melts into a drop
|
|
|
|
#include "common.glsl"
|
|
#include "water_sim.glsl"
|
|
|
|
layout(local_size_x = WATER_GROUP_SIZE) in;
|
|
|
|
// how fresh the wetted path is at a place of the glass
|
|
float water_fresh(uint slot, vec2 uv)
|
|
{
|
|
return water_wetted_at(slot, uv, water_step.y, 8.0);
|
|
}
|
|
|
|
// the path from one place to another marked wetted, Halfwidth (metres) to the sides: the texels around it get the time and how
|
|
// near they are to the middle of the path
|
|
void water_wet_path(uint slot, water_frame_s frame, vec2 from, vec2 to, float halfwidth)
|
|
{
|
|
uint stamp = (uint(water_step.y * 2.0) + 1u) << 8u;
|
|
uint base = water_trail_base(slot);
|
|
float texels = float(WATER_TRAIL);
|
|
// the half width in texels, a texel and a half more for the filtering
|
|
float reach = halfwidth / max(min(length(frame.tu), length(frame.tv)), 1e-6) * texels + 1.5;
|
|
int spread = clamp(int(ceil(reach)), 1, 3);
|
|
vec2 a = from * texels - 0.5;
|
|
vec2 b = to * texels - 0.5;
|
|
int steps = clamp(int(ceil(length(b - a) * 2.0)), 1, 24);
|
|
for (int s = 1; s <= steps; ++s)
|
|
{
|
|
vec2 point = mix(a, b, float(s) / float(steps));
|
|
ivec2 centre = ivec2(floor(point + 0.5));
|
|
for (int y = -spread; y <= spread; ++y)
|
|
{
|
|
for (int x = -spread; x <= spread; ++x)
|
|
{
|
|
ivec2 texel = centre + ivec2(x, y);
|
|
if (any(lessThan(texel, ivec2(0))) || any(greaterThanEqual(texel, ivec2(WATER_TRAIL))))
|
|
continue;
|
|
float near = 1.0 - length(vec2(texel) - point) / reach;
|
|
if (near <= 0.0)
|
|
continue;
|
|
atomicMax(water_cells[base + uint(texel.y * WATER_TRAIL + texel.x)], stamp | uint(near * 255.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
uint slot = gl_WorkGroupID.y;
|
|
uint place = gl_GlobalInvocationID.x;
|
|
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
|
|
return;
|
|
uint index = slot * uint(WATER_DROPS) + place;
|
|
water_drop_s drop = water_drops[index];
|
|
if (drop.place.z <= 0.0)
|
|
return;
|
|
float seconds = water_step.x;
|
|
uint state = water_state[index];
|
|
vec4 extra = water_extra[index];
|
|
vec2 uv = drop.place.xy;
|
|
water_frame_s frame = water_frame(slot, uv);
|
|
if (frame.valid < 0.25)
|
|
{
|
|
water_remove(index);
|
|
return;
|
|
}
|
|
uint seed = water_pcg4d(uvec4(water_counts.x, slot, place, 9u)).x;
|
|
|
|
// a flake melts into a drop: the glass is warmed by the cab, the faster the warmer the air
|
|
bool flake = drop.motion.w < 1.0;
|
|
if (flake)
|
|
{
|
|
float warm = water_up.w;
|
|
float melt = (warm > 0.5) ? mix(3.0, 10.0, drop.motion.z) / (1.0 + 0.25 * warm) : mix(15.0, 45.0, drop.motion.z);
|
|
drop.motion.w = min(drop.motion.w + seconds / melt, 1.0);
|
|
flake = drop.motion.w < 1.0;
|
|
}
|
|
|
|
// the blades: one going over the drop in the step takes it along
|
|
uint carried = (state >> 1u) & 7u;
|
|
float way = (((state >> 4u) & 1u) != 0u) ? 1.0 : -1.0;
|
|
vec4 mask = water_blades_at(slot, uv);
|
|
vec4 reached = water_blades_texel(slot, uv);
|
|
for (int k = 0; k < 4; ++k)
|
|
{
|
|
float before = water_blades_before[k];
|
|
float now = water_blades_now[k];
|
|
if ((before < 0.0) || (now < 0.0))
|
|
{
|
|
if (carried == uint(k + 1))
|
|
carried = 0u;
|
|
continue;
|
|
}
|
|
float moved = now - before;
|
|
if (carried == uint(k + 1))
|
|
{
|
|
// the blade turned back at the end of its sweep or stopped: the water stays where it brought it
|
|
if ((moved * way <= 0.0) || (now >= 0.995) || (now <= 0.005))
|
|
carried = 0u;
|
|
}
|
|
// a blade goes over a drop which was ahead of it (by a little more than nothing: the water it left where it turned back at
|
|
// the end of its sweep isn't taken back) and is behind it now
|
|
else if ((carried == 0u) && (moved != 0.0) && (reached[k] >= 0.001) && ((mask[k] - before) * sign(moved) > 0.003) && ((mask[k] - now) * sign(moved) <= 0.0))
|
|
{
|
|
carried = uint(k + 1);
|
|
way = (moved > 0.0) ? 1.0 : -1.0;
|
|
// the blade doesn't take all of it: now and then a droplet stays behind
|
|
vec4 r = water_random(seed, 5u, place, slot);
|
|
if ((false == flake) && (drop.place.z > 1.0) && (r.x < 0.35))
|
|
{
|
|
float left = mix(0.02, 0.08, r.y);
|
|
if (water_add(slot, seed ^ 0x9e3779b9u, uv, left, 1.0, r.z, false))
|
|
drop.place.z -= left;
|
|
}
|
|
}
|
|
}
|
|
|
|
vec3 velocity = frame.tu * drop.motion.x + frame.tv * drop.motion.y;
|
|
if (carried != 0u)
|
|
{
|
|
// in front of the blade by its own size and the lip of the bead: the place where the mask is as far ahead of the blade,
|
|
// found along the gradient of the mask (along the arc of the blade)
|
|
int k = int(carried) - 1;
|
|
float radius = flake ? 0.5 * water_flake(drop.place.z) : water_radius(drop.place.z);
|
|
float a = dot(frame.tu, frame.tu);
|
|
float b = dot(frame.tu, frame.tv);
|
|
float c = dot(frame.tv, frame.tv);
|
|
float det = max(a * c - b * b, 1e-20);
|
|
float h = 1.0 / float(WATER_MAP);
|
|
bool edge = false;
|
|
for (int step = 0; step < 4; ++step)
|
|
{
|
|
// at the edge of the sweep (the end of the blade) the water squeezes out and stays
|
|
if ((water_blades_texel(slot, uv + vec2(h, 0.0))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(h, 0.0))[k] < 0.001) ||
|
|
(water_blades_texel(slot, uv + vec2(0.0, h))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(0.0, h))[k] < 0.001))
|
|
{
|
|
edge = true;
|
|
break;
|
|
}
|
|
float m = water_blades_at(slot, uv)[k];
|
|
vec2 gradient = vec2(water_blades_at(slot, uv + vec2(h, 0.0))[k] - water_blades_at(slot, uv - vec2(h, 0.0))[k],
|
|
water_blades_at(slot, uv + vec2(0.0, h))[k] - water_blades_at(slot, uv - vec2(0.0, h))[k]) / (2.0 * h);
|
|
float g2 = dot(gradient, gradient);
|
|
if (g2 < 1e-6)
|
|
break;
|
|
// how fast the mask changes along the glass, per metre
|
|
float permetre = sqrt(max((c * gradient.x * gradient.x - 2.0 * b * gradient.x * gradient.y + a * gradient.y * gradient.y) / det, 0.0));
|
|
float target = water_blades_now[k] + way * (0.0015 + radius) * permetre;
|
|
vec2 delta = gradient * ((target - m) / g2);
|
|
float length_delta = length(delta);
|
|
if (length_delta > 0.02)
|
|
delta *= 0.02 / length_delta;
|
|
uv += delta;
|
|
if (length_delta < 1e-5)
|
|
break;
|
|
}
|
|
// pushed out of the sweep it stays at its edge
|
|
if (edge || (water_blades_texel(slot, uv)[k] < 0.001))
|
|
carried = 0u;
|
|
velocity = vec3(0.0);
|
|
}
|
|
else if (false == flake)
|
|
{
|
|
float volume = drop.place.z;
|
|
float radius = water_radius(volume);
|
|
vec3 n = frame.normal;
|
|
vec3 up = water_up.xyz;
|
|
// gravity along the glass
|
|
float weight = 1000.0 * WATER_GRAVITY * volume * 1e-9;
|
|
vec3 force = -(up - n * dot(up, n)) * weight;
|
|
// the drag of the air going along the glass, with a share of the air meeting it head on turned up along it
|
|
vec3 air = water_air.xyz;
|
|
vec3 rise = up - n * dot(up, n);
|
|
rise = (length(rise) > 0.1) ? normalize(rise) : vec3(0.0);
|
|
vec3 flow = (air - n * dot(air, n) + rise * (max(-dot(air, n), 0.0) * WATER_HEAD_ON)) * WATER_BOUNDARY;
|
|
float area = 2.0 * radius * (0.364 * radius) * 0.8;
|
|
force += flow * (0.6 * area * length(flow));
|
|
// the hold of the line of contact, uneven over the glass, much less on a path wetted lately
|
|
vec2 spread = uv * vec2(length(frame.tu), length(frame.tv));
|
|
float uneven = mix(0.55, 1.45, water_noise(spread / 0.003, 3u + slot));
|
|
float hold = WATER_TENSION * 2.0 * radius * WATER_HOLD * uneven * mix(1.0, 0.3, water_fresh(slot, uv)) * mix(0.85, 1.15, drop.motion.z);
|
|
float pull = length(force);
|
|
vec3 target = vec3(0.0);
|
|
if (pull > hold)
|
|
{
|
|
vec3 dir = force / pull;
|
|
// the way bent towards the side which holds the drop less: the wetter one (the water keeps to the wetted paths) and the
|
|
// one where the glass is cleaner, so it meanders
|
|
vec3 side = cross(n, dir);
|
|
float offset = max(radius, 0.0015);
|
|
vec2 leftuv = uv + water_to_uv(frame, side * offset);
|
|
vec2 rightuv = uv - water_to_uv(frame, side * offset);
|
|
float wetter = water_fresh(slot, leftuv) - water_fresh(slot, rightuv);
|
|
float cleaner = water_noise(rightuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot) - water_noise(leftuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot);
|
|
float wander = water_noise(spread / 0.02 + vec2(17.0 * drop.motion.z), 11u + slot) - 0.5;
|
|
dir = normalize(dir + side * (wetter * 0.6 + cleaner * 0.5 + wander * 0.35));
|
|
target = dir * min((pull - hold) / (WATER_VISCOUS * radius), 1.5);
|
|
}
|
|
velocity += (target - velocity) * (1.0 - exp(-seconds / ((pull > hold) ? 0.05 : 0.015)));
|
|
velocity -= n * dot(velocity, n);
|
|
vec3 moved = velocity * seconds;
|
|
float travel = length(moved);
|
|
if (travel > 1e-7)
|
|
{
|
|
vec2 next = uv + water_to_uv(frame, moved);
|
|
water_wet_path(slot, frame, uv, next, 0.4 * radius);
|
|
// a sliding drop leaves droplets behind as it goes, and thins; a large one breaks up the faster (a lot of water runs as
|
|
// a stream of beads rather than as one big drop)
|
|
extra.x += travel;
|
|
if ((extra.x >= extra.y) && (volume > 1.5))
|
|
{
|
|
vec4 r = water_random(seed, 6u, place, slot);
|
|
float left = clamp(volume * mix(0.01, 0.03, r.x) * (1.0 + volume / 40.0), 0.01, 0.25 * volume);
|
|
vec3 behind = -(velocity / max(length(velocity), 1e-6)) * ((radius + 1.2 * water_radius(left)) * 1.05);
|
|
if (water_add(slot, seed ^ 0x85ebca6bu, uv + water_to_uv(frame, behind), left, 1.0, r.y, false))
|
|
drop.place.z -= left;
|
|
extra.x = 0.0;
|
|
extra.y = mix(0.002, 0.005, r.z) / (1.0 + volume / 100.0);
|
|
}
|
|
uv = next;
|
|
}
|
|
// without the rain the water dries, slowly
|
|
if (water_step.z <= 0.0)
|
|
drop.place.z -= seconds * 0.004 * radius * 1000.0;
|
|
}
|
|
else
|
|
{
|
|
velocity = vec3(0.0);
|
|
}
|
|
|
|
if ((drop.place.z < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
|
|
{
|
|
water_remove(index);
|
|
return;
|
|
}
|
|
water_frame_s there = water_frame(slot, uv);
|
|
if (there.valid < 0.25)
|
|
{
|
|
// off the glass, it falls off
|
|
water_remove(index);
|
|
return;
|
|
}
|
|
drop.place.xy = uv;
|
|
drop.place.w += seconds;
|
|
drop.motion.xy = water_to_uv(there, velocity);
|
|
water_drops[index] = drop;
|
|
water_extra[index] = extra;
|
|
water_state[index] = 1u | (carried << 1u) | ((way > 0.0) ? 16u : 0u);
|
|
}
|