rustial-renderer-bevy 0.0.1

Bevy Engine renderer for the rustial 2.5D map engine
//! Custom Bevy material for per-pixel horizon fog on tile quads.
//!
//! [`TileFogMaterial`] replaces `StandardMaterial` for tile entities.
//! It carries fog uniforms (eye position, fog start/end, density, fog
//! colour) and a tile texture, applying the fog ramp per-fragment in
//! the GPU shader -- identical to the WGPU renderer's `tile.wgsl`.
//!
//! This eliminates the visible per-tile fade boundaries that occur
//! when fog is applied by modifying `base_color.alpha` uniformly
//! across each tile quad.

use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderType};
use bevy::shader::ShaderRef;

/// GPU-side fog uniform block.
///
/// Layout matches the WGSL `TileFogUniforms` struct exactly:
/// seven consecutive `vec4<f32>` values (112 bytes, 16-byte aligned).
#[derive(Debug, Clone, Copy, ShaderType)]
pub struct FogUniforms {
    /// Background / clear colour (linear sRGB, RGBA).
    pub fog_color: Vec4,
    /// Camera eye world-space position (xyz, w unused).
    pub eye_pos: Vec4,
    /// Fog parameters: `(start, end, density, 0)`.
    pub fog_params: Vec4,
    /// Hillshade highlight colour.
    pub hillshade_highlight: Vec4,
    /// Hillshade shadow colour.
    pub hillshade_shadow: Vec4,
    /// Hillshade accent colour.
    pub hillshade_accent: Vec4,
    /// `(illumination_direction_rad, illumination_altitude_rad, exaggeration, opacity)`.
    pub hillshade_light: Vec4,
}

impl Default for FogUniforms {
    fn default() -> Self {
        Self {
            fog_color: Vec4::ONE,
            eye_pos: Vec4::ZERO,
            fog_params: Vec4::new(0.0, 1.0, 0.0, 0.0),
            hillshade_highlight: Vec4::ONE,
            hillshade_shadow: Vec4::new(0.0, 0.0, 0.0, 1.0),
            hillshade_accent: Vec4::new(0.42, 0.48, 0.42, 1.0),
            hillshade_light: Vec4::new(335.0f32.to_radians(), 45.0f32.to_radians(), 1.0, 0.0),
        }
    }
}

/// Per-material render flags.
#[derive(Debug, Clone, Copy, ShaderType)]
pub struct MaterialFlags {
    /// 1.0 when `tile_texture` is a real map texture, 0.0 otherwise.
    pub has_texture: f32,
    /// Per-tile fade-in opacity (0.0 = transparent, 1.0 = fully opaque).
    ///
    /// Set by the texture upload system from [`VisibleTile::fade_opacity`].
    pub fade_opacity: f32,
    /// Padding to 16-byte alignment for uniform buffers.
    pub _pad: Vec2,
}

impl Default for MaterialFlags {
    fn default() -> Self {
        Self {
            has_texture: 0.0,
            fade_opacity: 1.0,
            _pad: Vec2::ZERO,
        }
    }
}

/// Per-material terrain reconstruction uniforms.
#[derive(Debug, Clone, Copy, ShaderType)]
pub struct TerrainUniforms {
    /// `(nw_lat, nw_lon, se_lat, se_lon)` in degrees.
    pub geo_bounds: Vec4,
    /// `(scene_origin_x, scene_origin_y, scene_origin_z, projection_kind)`.
    pub scene_origin: Vec4,
    /// `(exaggeration, skirt_base, min_elev, max_elev)`.
    pub elev_params: Vec4,
    /// `(u_min, v_min, u_max, v_max)` in the source DEM texture.
    pub elev_region: Vec4,
    /// `.x = 1.0` when GPU terrain displacement is enabled for this material.
    pub options: Vec4,
}

impl TerrainUniforms {
    /// Flat / disabled terrain settings for ordinary tile quads.
    pub fn disabled() -> Self {
        Self {
            geo_bounds: Vec4::ZERO,
            scene_origin: Vec4::ZERO,
            elev_params: Vec4::ZERO,
            elev_region: Vec4::ZERO,
            options: Vec4::ZERO,
        }
    }
}

impl Default for TerrainUniforms {
    fn default() -> Self {
        Self::disabled()
    }
}

/// Per-pixel fog material for map tile quads and shared-grid terrain.
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone, Default)]
pub struct TileFogMaterial {
    /// Fog uniform block uploaded as a single `var<uniform>` at binding 0.
    #[uniform(0)]
    pub fog: FogUniforms,

    /// Tile imagery (assigned after decode by `upload_textures`).
    #[texture(1)]
    #[sampler(2)]
    pub tile_texture: Option<Handle<Image>>,

    /// Render flags (e.g. whether a real texture is bound).
    #[uniform(3)]
    pub flags: MaterialFlags,

    /// Terrain reconstruction uniforms. Disabled for ordinary flat tile quads.
    #[uniform(4)]
    pub terrain: TerrainUniforms,

    /// GPU height texture used by the shared-grid terrain path.
    #[texture(
        5,
        sample_type = "float",
        filterable = false,
        visibility(vertex, fragment)
    )]
    pub height_texture: Handle<Image>,
}

impl Material for TileFogMaterial {
    fn vertex_shader() -> ShaderRef {
        "embedded://rustial_renderer_bevy/shaders/tile_fog.wgsl".into()
    }

    fn fragment_shader() -> ShaderRef {
        "embedded://rustial_renderer_bevy/shaders/tile_fog.wgsl".into()
    }

    fn alpha_mode(&self) -> AlphaMode {
        if self.flags.fade_opacity < 1.0 {
            AlphaMode::Blend
        } else {
            AlphaMode::Opaque
        }
    }

    fn enable_prepass() -> bool {
        false
    }

    fn enable_shadows() -> bool {
        false
    }

    fn specialize(
        _pipeline: &bevy::pbr::MaterialPipeline,
        descriptor: &mut bevy::render::render_resource::RenderPipelineDescriptor,
        _layout: &bevy::mesh::MeshVertexBufferLayoutRef,
        _key: bevy::pbr::MaterialPipelineKey<Self>,
    ) -> Result<(), bevy::render::render_resource::SpecializedMeshPipelineError> {
        descriptor.primitive.cull_mode = None;
        Ok(())
    }
}