//! Chunk material, block textures, and the day/night sky. pub mod textures; use std::f32::consts::TAU; use bevy::asset::embedded_asset; use bevy::mesh::MeshVertexBufferLayoutRef; use bevy::pbr::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin}; use bevy::prelude::*; use bevy::render::render_resource::{ AsBindGroup, RenderPipelineDescriptor, ShaderType, SpecializedMeshPipelineError, }; use bevy::shader::ShaderRef; use crate::world::block::{BLOCKS, BLOCK_COUNT}; use crate::world::{VoxelWorld, ATTRIBUTE_VOXEL}; const SHADER: &str = "embedded://hieroxel/render/voxel.wesl"; pub struct VoxelRenderPlugin; impl Plugin for VoxelRenderPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "voxel.wesl"); app.add_plugins(MaterialPlugin::::default()) .init_resource::() .init_resource::() .add_systems(Startup, (setup_materials, setup_sky_bodies)) .add_systems(Update, update_sky); } } #[derive(ShaderType, Clone, Copy, Default, Debug)] pub struct VoxelParams { pub fog_color: Vec4, /// x: daylight, y: unused, z: fog start, w: fog end pub sky: Vec4, /// x: underwater, y: water pass pub misc: Vec4, } #[derive(Asset, TypePath, AsBindGroup, Clone, Debug)] #[bind_group_data(VoxelMaterialKey)] pub struct VoxelMaterial { #[uniform(0)] pub params: VoxelParams, #[texture(1, dimension = "2d_array")] #[sampler(2)] pub textures: Handle, pub water: bool, } #[repr(C)] #[derive(Copy, Clone, Hash, Eq, PartialEq)] pub struct VoxelMaterialKey { water: bool, } impl From<&VoxelMaterial> for VoxelMaterialKey { fn from(m: &VoxelMaterial) -> Self { Self { water: m.water } } } impl Material for VoxelMaterial { fn vertex_shader() -> ShaderRef { SHADER.into() } fn fragment_shader() -> ShaderRef { SHADER.into() } fn alpha_mode(&self) -> AlphaMode { if self.water { AlphaMode::Blend } else { AlphaMode::Opaque } } fn enable_prepass() -> bool { false } fn enable_shadows() -> bool { false } fn specialize( _pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayoutRef, key: MaterialPipelineKey, ) -> Result<(), SpecializedMeshPipelineError> { descriptor.vertex.buffers = vec![layout.0.get_layout(&[ATTRIBUTE_VOXEL.at_shader_location(0)])?]; if key.bind_group_data.water { // The surface must be visible from below too. descriptor.primitive.cull_mode = None; } Ok(()) } } #[derive(Resource)] pub struct VoxelMaterials { pub solid: Handle, pub water: Handle, /// Hotbar icon per block id (side texture). pub icons: Vec>, } #[derive(Resource)] pub struct DayCycle { /// 0 = midnight, 0.25 = sunrise, 0.5 = noon, 0.75 = sunset. pub time: f32, pub day_length: f32, pub speed: f32, } impl Default for DayCycle { fn default() -> Self { Self { time: 0.3, day_length: 720.0, speed: 1.0, } } } impl DayCycle { pub fn sun_elevation(&self) -> f32 { (TAU * (self.time - 0.25)).sin() } pub fn clock(&self) -> String { let minutes = (self.time * 24.0 * 60.0) as u32; format!("{:02}:{:02}", minutes / 60, minutes % 60) } } /// What the camera is inside; set by the player. #[derive(Resource, Default)] pub struct CameraEnv { pub underwater: bool, } fn setup_materials( mut commands: Commands, mut images: ResMut>, mut materials: ResMut>, ) { let textures = images.add(textures::build_array()); let solid = materials.add(VoxelMaterial { params: VoxelParams::default(), textures: textures.clone(), water: false, }); let water = materials.add(VoxelMaterial { params: VoxelParams { misc: Vec4::new(0.0, 1.0, 0.0, 0.0), ..default() }, textures, water: true, }); let icons = (0..BLOCK_COUNT) .map(|id| images.add(textures::icon(BLOCKS[id].tex[1]))) .collect(); commands.insert_resource(VoxelMaterials { solid, water, icons }); } #[derive(Component)] struct SkyBody { /// Offset of the body's angle from the sun's. phase: f32, } fn setup_sky_bodies( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let quad = meshes.add(Rectangle::new(60.0, 60.0)); for (phase, color) in [(0.0, Color::srgb(1.0, 0.93, 0.7)), (0.5, Color::srgb(0.82, 0.86, 0.95))] { commands.spawn(( SkyBody { phase }, Mesh3d(quad.clone()), MeshMaterial3d(materials.add(StandardMaterial { base_color: color, unlit: true, fog_enabled: false, cull_mode: None, ..default() })), Transform::default(), )); } } fn lerp3(a: Vec3, b: Vec3, t: f32) -> Vec3 { a + (b - a) * t.clamp(0.0, 1.0) } #[allow(clippy::too_many_arguments)] fn update_sky( time: Res