bevy-aqua-core 0.1.1

Aqua cascade and material ABI, water-field types, bed map, shared render plumbing, and WGSL contracts.
// Shared current-frame water-surface deformation. This module is the single
// source of truth for forward rendering and later motion-vector consumers.

#define_import_path bevy_aqua_core::deform

#import bevy_pbr::{forward_io::Vertex, mesh_functions}
#import aqua::cascade::{BodyParams, DEBUG_MODE_BEAUTY, DEBUG_MODE_FAR_TIER, MIN_NORMAL_Y, SAFE_LENGTH_SQUARED, begin_invocation, cascade_layout, effective_flow, far_tier_weight, field_params, lod_count, owning_body, sample_displacement, sample_field_flow, sample_field_level, set_effective_flow, set_effective_time, snap_and_transition, surface}
#import aqua::waves::displace::{far_displacement, far_normal_cross, sample_fft_normal_cross}
#import bevy_aqua_core::river::river_analytic_displacement

// The full current-frame result is intentionally independent of a render-stage
// output. `sample_xz` is the stable location used to evaluate deformation;
// `undisplaced_xz` preserves the forward material's existing varying contract.
struct DeformationResult {
    world_position: vec4<f32>,
    base_world_position: vec3<f32>,
    world_normal: vec3<f32>,
    sample_xz: vec2<f32>,
    undisplaced_xz: vec2<f32>,
    lod: u32,
    sample_alpha: f32,
    displacement: vec3<f32>,
    wave_height: f32,
    body_slot: u32,
    body_params: BodyParams,
    body_level: f32,
    bounded: bool,
    river: bool,
    river_flow: vec4<f32>,
}

fn deformation_safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
    let length_squared = dot(value, value);
    let normalized = value * inverseSqrt(max(length_squared, SAFE_LENGTH_SQUARED));
    return select(fallback, normalized, length_squared > SAFE_LENGTH_SQUARED);
}

fn deform_current(vertex: Vertex, time: f32) -> DeformationResult {
    var result: DeformationResult;
    let world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
    var world_position = mesh_functions::mesh_position_local_to_world(
        world_from_local,
        vec4(vertex.position, 1.0),
    );
    let tile_lod = min(mesh_functions::get_tag(vertex.instance_index), lod_count() - 1u);
    var slot = 0u;
    var params = field_params.bodies[0];
    var vertex_level = 0.0;
    if field_params.info.x > 0.5 {
        let field = sample_field_level(world_position.xz);
        slot = u32(field.y + 0.5);
        params = owning_body(slot);
        vertex_level = field.x;
    }
    let bounded = slot > 0u;
    begin_invocation(bounded, params);
    set_effective_time(time);
    // River bodies advect wave content with their baked local current;
    // everything else keeps the global uniform current.
    var vertex_river_flow = vec4(0.0, 0.0, 1024.0, 0.0);
    if bounded {
        vertex_river_flow = sample_field_flow(world_position.xz);
    }
    set_effective_flow(select(
        surface.advection.xy,
        vertex_river_flow.xy,
        bounded && params.flags.y > 0.5,
    ));

    result.body_slot = slot;
    result.body_params = params;
    result.body_level = vertex_level;
    result.bounded = bounded;
    result.river = bounded && params.flags.y > 0.5;
    result.river_flow = vertex_river_flow;

    // River bodies synthesize their own closed-form waves along the local
    // current instead of sampling the shared ocean cascade.
    if result.river {
        let transitioned = snap_and_transition(
            world_position.xz,
            world_from_local[3].xz,
            cascade_layout.cascades[tile_lod],
        );
        let river_xz = transitioned.xy;
        vertex_river_flow = sample_field_flow(river_xz);
        effective_flow = vertex_river_flow.xy;
        let analytic = river_analytic_displacement(
            river_xz,
            vertex_river_flow,
            time,
        );
        let epsilon = 0.35;
        let height_x = river_analytic_displacement(
            river_xz + vec2(epsilon, 0.0),
            vertex_river_flow,
            time,
        ).x;
        let height_z = river_analytic_displacement(
            river_xz + vec2(0.0, epsilon),
            vertex_river_flow,
            time,
        ).x;
        let displaced = vec4(
            river_xz.x,
            vertex_level + analytic.x,
            river_xz.y,
            1.0,
        );
        result.world_position = displaced;
        result.base_world_position = world_position.xyz;
        result.world_normal = deformation_safe_normalize(
            vec3(
                -(height_x - analytic.x) / epsilon,
                1.0,
                -(height_z - analytic.x) / epsilon,
            ),
            vec3(0.0, 1.0, 0.0),
        );
        result.sample_xz = river_xz;
        result.undisplaced_xz = world_position.xz;
        result.lod = 0u;
        result.sample_alpha = 1.0;
        result.displacement = displaced.xyz - world_position.xyz;
        result.wave_height = analytic.x;
        result.river_flow = vertex_river_flow;
        return result;
    }

    if bounded {
        let transitioned = snap_and_transition(
            world_position.xz,
            world_from_local[3].xz,
            cascade_layout.cascades[tile_lod],
        );
        let displaced = vec4(transitioned.x, vertex_level, transitioned.y, 1.0);
        result.world_position = displaced;
        result.base_world_position = displaced.xyz;
        result.world_normal = vec3(0.0, 1.0, 0.0);
        result.sample_xz = transitioned.xy;
        result.undisplaced_xz = world_position.xz;
        result.lod = 0u;
        result.sample_alpha = 1.0;
        result.displacement = vec3(0.0);
        result.wave_height = 0.0;
        return result;
    }

    let transitioned = select(
        snap_and_transition(
            world_position.xz,
            world_from_local[3].xz,
            cascade_layout.cascades[tile_lod],
        ),
        vec3(world_position.x, world_position.z, 1.0),
        bounded,
    );
    world_position = vec4(
        transitioned.x,
        world_position.y,
        transitioned.y,
        world_position.w,
    );
    // Crest fades wave content that projects below the minimum useful
    // wavelength before the coarse vertex grid can alias it.
    let detail_lod = clamp(cascade_layout.center.z, 0.0, f32(lod_count() - 1u));
    let minimum_lod = u32(floor(detail_lod));
    let lod = max(tile_lod, minimum_lod);
    let detail_alpha = fract(detail_lod);
    var sample_alpha = select(transitioned.z, 1.0, bounded);
    if tile_lod < minimum_lod {
        sample_alpha = select(detail_alpha, 1.0, bounded);
    } else if tile_lod == minimum_lod {
        sample_alpha = select(max(sample_alpha, detail_alpha), 1.0, bounded);
    }
    let mode = u32(round(surface.debug.x));
    let far_diagnostic = mode == DEBUG_MODE_FAR_TIER;
    let base_world_position = world_position.xyz;
    let far_tier = select(
        0.0,
        far_tier_weight(base_world_position),
        mode == DEBUG_MODE_BEAUTY || far_diagnostic,
    );
    var displacement: vec3<f32>;
    var normal_cross: vec3<f32>;
    if far_tier >= 1.0 {
        displacement = far_displacement(transitioned.xy);
        normal_cross = far_normal_cross(transitioned.xy);
    } else {
        let near_displacement = sample_displacement(transitioned.xy, lod, sample_alpha);
        displacement = near_displacement;
        if surface.reflection.x > 0.5 {
            normal_cross = sample_fft_normal_cross(transitioned.xy, lod, sample_alpha);
        } else {
            let texel_width = cascade_layout.cascades[lod].texel_width;
            let displacement_x = sample_displacement(
                transitioned.xy + vec2(texel_width, 0.0),
                lod,
                sample_alpha,
            );
            let displacement_z = sample_displacement(
                transitioned.xy + vec2(0.0, texel_width),
                lod,
                sample_alpha,
            );
            let tangent_x = vec3(texel_width, 0.0, 0.0)
                + displacement_x - near_displacement;
            let tangent_z = vec3(0.0, 0.0, texel_width)
                + displacement_z - near_displacement;
            normal_cross = cross(tangent_z, tangent_x);
        }
        normal_cross.y = max(normal_cross.y, MIN_NORMAL_Y);
        if far_tier > 0.0 {
            displacement = mix(
                near_displacement,
                far_displacement(transitioned.xy),
                far_tier,
            );
            normal_cross = deformation_safe_normalize(
                mix(
                    deformation_safe_normalize(normal_cross, vec3(0.0, 1.0, 0.0)),
                    far_normal_cross(transitioned.xy),
                    far_tier,
                ),
                vec3(0.0, 1.0, 0.0),
            );
        }
    }
    normal_cross.y = max(normal_cross.y, MIN_NORMAL_Y);
    world_position = vec4(base_world_position + displacement, world_position.w);

    result.world_position = world_position;
    result.base_world_position = base_world_position;
    result.world_normal = deformation_safe_normalize(normal_cross, vec3(0.0, 1.0, 0.0));
    result.sample_xz = transitioned.xy;
    result.undisplaced_xz = transitioned.xy;
    result.lod = lod;
    result.sample_alpha = sample_alpha;
    result.displacement = displacement;
    result.wave_height = displacement.y;
    return result;
}