layout (location = 0) in vec2 a_pos; out vec2 v_uv; out vec2 v_world; out vec2 v_particle_velocity; out float v_particle_valid; layout (std140) uniform ParticleGpuValidationUBO { highp vec4 u_pass; highp vec4 u_marker; highp vec4 u_mask; highp vec4 u_visual; highp vec4 u_field_bounds; highp vec4 u_field_scale; highp mat4 u_matrix; }; uniform sampler2D u_state_texture; uniform sampler2D u_previous; uniform sampler2D u_field_texture; uniform sampler2D u_next_field_texture; vec2 fieldUv(vec2 world) { return vec2((world.x - u_field_bounds.x) / (u_field_bounds.z - u_field_bounds.x), (world.y - u_field_bounds.y) / (u_field_bounds.w - u_field_bounds.y)); } bool validFieldUv(vec2 uv) { return all(greaterThanEqual(uv, vec2(0.0))) && all(lessThanEqual(uv, vec2(1.0))); } void main() { v_uv = a_pos; v_world = a_pos; v_particle_velocity = vec2(0.0); v_particle_valid = 1.0; if (u_pass.x > 4.5) { gl_Position = u_matrix * vec4(a_pos * 8192.0, 0.0, 1.0); } else if (u_pass.x < 1.5) { gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0); } else if (u_pass.x > 3.5) { // a_pos.x packs State texel X plus one endpoint. The formal particle // path draws the StateRead -> StateWrite displacement in current map // clip space; it does not synthesize a fixed world-length streak. float endpoint = mod(a_pos.x, 2.0) * 2.0 - 1.0; vec2 stateIndex = vec2(floor(a_pos.x * 0.5), a_pos.y); vec2 stateUV = (stateIndex + vec2(0.5)) / u_mask.xy; vec4 previousState = texture(u_previous, stateUV); vec4 currentState = texture(u_state_texture, stateUV); vec2 currentPosition = currentState.rg; vec2 uv = fieldUv(currentPosition); 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); bool valid = validFieldUv(uv) && field0.a >= 0.5 && field1.a >= 0.5 && !any(isnan(velocity)); vec2 mercatorVelocity = vec2(velocity.x, -velocity.y); float speed = length(mercatorVelocity); vec2 segmentPosition = endpoint < 0.0 ? previousState.rg : currentPosition; v_uv = stateUV; v_world = currentPosition; v_particle_velocity = mercatorVelocity; // A zero alpha is written by State on a respawn frame. This suppresses // oldPosition -> newRandomPosition cross-screen segments. v_particle_valid = valid && speed > 0.00001 && currentState.a > 0.5 && distance(previousState.rg, currentPosition) > 0.000000001 ? 1.0 : 0.0; if (v_particle_valid < 0.5) { gl_Position = vec4(-2.0, -2.0, 0.0, 1.0); } else { gl_Position = u_matrix * vec4(segmentPosition * 8192.0, 0.0, 1.0); } } else { if (u_mask.z > 0.5) { // Trail targets are viewport-sized screen-space history buffers. gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0); } else { // VALIDATION ONLY: a_pos is canonical z0 world geometry; u_matrix // is PaintParameters::matrixForTile() for the selected world copy. gl_Position = u_matrix * vec4(a_pos * 8192.0, 0.0, 1.0); } } }