feat: add direction particle advection
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -6,6 +6,7 @@ layout (std140) uniform ParticleGpuValidationUBO {
|
||||
highp vec4 u_field_bounds;
|
||||
highp vec4 u_respawn_bounds;
|
||||
highp vec4 u_field_scale;
|
||||
highp vec4 u_advection;
|
||||
highp vec4 u_particle_color;
|
||||
highp vec4 u_trail_color;
|
||||
highp vec4 u_weather_stops[8];
|
||||
@@ -72,15 +73,24 @@ bool outsideRespawnBounds(vec2 position) {
|
||||
position.y < u_respawn_bounds.y || position.y > u_respawn_bounds.w;
|
||||
}
|
||||
|
||||
bool fieldVelocityAt(vec2 position, out vec2 velocity) {
|
||||
bool fieldAdvectionAt(vec2 position, out vec2 advection) {
|
||||
vec2 uv = fieldUv(position);
|
||||
vec4 field0 = texture(u_field_texture, uv);
|
||||
vec4 field1 = texture(u_next_field_texture, uv);
|
||||
velocity = mix((field0.rg * 2.0 - 1.0) * u_field_scale.x,
|
||||
(field1.rg * 2.0 - 1.0) * u_field_scale.y,
|
||||
u_marker.x);
|
||||
return validFieldUv(uv) && field0.a >= 0.5 && field1.a >= 0.5 && !any(isnan(velocity)) &&
|
||||
length(velocity) > 0.00001;
|
||||
vec2 mixed = mix((field0.rg * 2.0 - 1.0) * u_field_scale.x,
|
||||
(field1.rg * 2.0 - 1.0) * u_field_scale.y,
|
||||
u_marker.x);
|
||||
if (!validFieldUv(uv) || field0.a < 0.5 || field1.a < 0.5 || any(isnan(mixed)) ||
|
||||
any(greaterThan(abs(mixed), vec2(3.4e38)))) return false;
|
||||
if (u_advection.x > 0.5) {
|
||||
float directionLength = length(mixed);
|
||||
if (isnan(directionLength) || directionLength > 3.4e38 || directionLength <= u_advection.z) return false;
|
||||
advection = mixed / directionLength * u_advection.y;
|
||||
return true;
|
||||
}
|
||||
// VELOCITY keeps its previous component-wise interpolation and magnitude.
|
||||
advection = mixed;
|
||||
return length(advection) > 0.00001;
|
||||
}
|
||||
|
||||
vec4 weatherRampColor(float value) {
|
||||
@@ -146,8 +156,8 @@ void main() {
|
||||
// forecast playback must not change respawn cadence.
|
||||
float age = initialSeed ? initialAgeHash * maxAge : previous.b + u_pass.w;
|
||||
vec2 position = previous.rg;
|
||||
vec2 velocity;
|
||||
bool valid = !respawn && fieldVelocityAt(position, velocity);
|
||||
vec2 advection;
|
||||
bool valid = !respawn && fieldAdvectionAt(position, advection);
|
||||
// The field bounds remain the only sampling domain. Once the
|
||||
// camera is stable, slots already outside the expanded viewport
|
||||
// may recycle into its separate preferred respawn domain.
|
||||
@@ -158,11 +168,11 @@ void main() {
|
||||
|
||||
if (!respawn) {
|
||||
float latitude = mercatorLatitudeRadians(position.y);
|
||||
vec2 displacement = vec2(velocity.x * u_pass.y / EARTH_CIRCUMFERENCE_METERS,
|
||||
-velocity.y * u_pass.y /
|
||||
vec2 displacement = vec2(advection.x * u_pass.y / EARTH_CIRCUMFERENCE_METERS,
|
||||
-advection.y * u_pass.y /
|
||||
(EARTH_CIRCUMFERENCE_METERS * max(cos(latitude), 0.01)));
|
||||
vec2 nextPosition = position + displacement;
|
||||
if (fieldVelocityAt(nextPosition, velocity)) {
|
||||
if (fieldAdvectionAt(nextPosition, advection)) {
|
||||
position = nextPosition;
|
||||
} else {
|
||||
respawn = true;
|
||||
@@ -175,7 +185,7 @@ void main() {
|
||||
// avoid a CPU upload or an unbounded shader loop.
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
vec2 candidate = respawnSeed(v_uv, previous.rg, float(attempt));
|
||||
if (fieldVelocityAt(candidate, velocity)) {
|
||||
if (fieldAdvectionAt(candidate, advection)) {
|
||||
position = candidate;
|
||||
found = true;
|
||||
break;
|
||||
@@ -201,12 +211,10 @@ void main() {
|
||||
vec2 uv = fieldUv(position);
|
||||
vec4 field0 = texture(u_field_texture, uv);
|
||||
vec4 field1 = texture(u_next_field_texture, uv);
|
||||
vec2 velocity = mix((field0.rg * 2.0 - 1.0) * u_field_scale.x,
|
||||
(field1.rg * 2.0 - 1.0) * u_field_scale.y,
|
||||
u_marker.x);
|
||||
if (!validFieldUv(uv) || field0.a < 0.5 || field1.a < 0.5) {
|
||||
vec2 advection;
|
||||
if (!fieldAdvectionAt(position, advection)) {
|
||||
position = seed;
|
||||
velocity = vec2(0.0);
|
||||
advection = vec2(0.0);
|
||||
}
|
||||
// U/V remain canonical metres per second. u_pass.y is the explicit
|
||||
// visual simulation delta in seconds; the validation clock may run
|
||||
@@ -214,11 +222,11 @@ void main() {
|
||||
// Web Mercator x spans Earth's circumference; y additionally scales
|
||||
// with sec(latitude). V is north-positive while world y is southward.
|
||||
float latitude = mercatorLatitudeRadians(position.y);
|
||||
float dx = velocity.x * u_pass.y / EARTH_CIRCUMFERENCE_METERS;
|
||||
float dy = -velocity.y * u_pass.y /
|
||||
float dx = advection.x * u_pass.y / EARTH_CIRCUMFERENCE_METERS;
|
||||
float dy = -advection.y * u_pass.y /
|
||||
(EARTH_CIRCUMFERENCE_METERS * max(cos(latitude), 0.01));
|
||||
position = position + vec2(dx, dy);
|
||||
if (!validFieldUv(fieldUv(position)) || any(isnan(velocity))) position = seed;
|
||||
if (!validFieldUv(fieldUv(position)) || any(isnan(advection))) position = seed;
|
||||
fragColor = vec4(position, fract(previous.b + 0.03125 * u_pass.y), 1.0);
|
||||
} else if (mode < 1.5) {
|
||||
// Formal Trail first fades the previous target. Its full-particle point
|
||||
|
||||
Reference in New Issue
Block a user