use crate::constants::{ITILE_SIZE, TILE_SIZE}; use crate::tile::{self, FixtureTile, FloorTile, NeedsOccluded, TileMap}; use crate::tiles::{self, FixtureTilePrefab, FloorTilePrefab, Textures}; use bevy::prelude::*; use noise::{NoiseFn, Perlin}; use std::collections::HashMap; pub const CHUNK_SIZE: i32 = 8; #[derive(Resource)] pub struct ChunkMap { pub loaded_chunks: HashMap, } impl Default for ChunkMap { fn default() -> Self { Self { loaded_chunks: HashMap::new(), } } } #[derive(Event)] pub struct LoadChunkEvent { pub chunk_position: IVec2, } fn setup_chunk_system(mut commands: Commands) { commands.insert_resource(TileMap::default()); commands.insert_resource(ChunkMap::default()); } pub fn handle_tile_occlusion_updates( mut commands: Commands, tilemap: Res, mut query_set: ParamSet<( Query<(&mut FloorTile, &Transform, &mut NeedsOccluded)>, Query<(&mut FixtureTile, &Transform, &mut NeedsOccluded)>, Query<(Entity, &mut NeedsOccluded)>, )>, mut cwss: ResMut, ) { query_set .p0() .par_iter_mut() .for_each(|(mut tile, transform, mut needs_occluded)| { if needs_occluded.has_been_occluded { return; } tile.visible_range = calculate_visibility(transform.translation.as_ivec3(), &tilemap); needs_occluded.has_been_occluded = true; }); query_set .p1() .par_iter_mut() .for_each(|(mut fixture, transform, mut needs_occluded)| { if needs_occluded.has_been_occluded { return; } fixture.visible_range = calculate_visibility( transform.translation.as_ivec3() - IVec3::new(0, 0, 1), &tilemap, ); needs_occluded.has_been_occluded = true; }); for (entity, occ) in query_set.p2().iter_mut() { if occ.has_been_occluded { commands.entity(entity).remove::(); } } cwss.state = tiles::WorldSpriteState::WaitingForRender; } pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] { let mut visible_range = [0u32; 8]; for mut camera_z in -15..=5 { camera_z *= ITILE_SIZE; let mut is_visible = false; if pos.z > camera_z { continue; } let mut is_occluded = false; 'vertical_check: for z_offset in 1..10 { let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * ITILE_SIZE)); if above_pos.z <= camera_z { if let Some(&(_, opaque, _, _, _)) = tilemap.floor_tiles.get(&above_pos) { if opaque { is_occluded = true; break 'vertical_check; } } // if z_offset >= 4 { // is_occluded = true; // break 'vertical_check; // } // if let Some(&(_, solid, _)) = tilemap.fixtures.get(&above_pos) { // if solid { // is_occluded = true; // break 'vertical_check; // } // } } else { break 'vertical_check; } } if !is_occluded { 'neighbor_check: for x_offset in -1..=1 { for y_offset in -1..=1 { for z_offset in 0..=1 { if x_offset == 0 && y_offset == 0 && z_offset == 0 { continue; } let neighbor_pos = IVec3::new( pos.x + x_offset * ITILE_SIZE, pos.y + y_offset * ITILE_SIZE, pos.z + z_offset * ITILE_SIZE, ); if let Some(&(id, _, _, _, _)) = tilemap.floor_tiles.get(&neighbor_pos) { if id == 0 { is_visible = true; break 'neighbor_check; } } // if let Some(&(id, _, _)) = tilemap.fixtures.get(&neighbor_pos) { // if id == 0 { // is_visible = true; // break 'neighbor_check; // } // } } } } } if is_visible { let z2 = ((camera_z / ITILE_SIZE) + 150) as usize; visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32); } } visible_range } pub fn generate_surface_noise(x: i32, y: i32) -> f32 { let noise = Perlin::new(0); let mut noise_value = 0.0; let mut amplitude = 1.0; let mut frequency = 0.008; for _ in 0..6 { noise_value += noise.get([x as f64 * frequency, y as f64 * frequency]) * amplitude; amplitude *= 0.6; frequency *= 1.8; } (noise_value * 2.5) as f32 } fn handle_chunk_loading( mut commands: Commands, textures: Res, mut events: EventReader, mut chunk_map: ResMut, mut tilemap: ResMut, ) { let noise = Perlin::new(0); for event in events.read() { println!("Loading chunk {}", event.chunk_position); let chunk_pos = event.chunk_position; if chunk_map.loaded_chunks.contains_key(&chunk_pos) { continue; } chunk_map.loaded_chunks.insert(chunk_pos, true); let start_x = chunk_pos.x * CHUNK_SIZE; let start_y = chunk_pos.y * CHUNK_SIZE; let mut floor_positions = Vec::new(); // Generate tiles for this chunk for local_y in 0..CHUNK_SIZE { for local_x in 0..CHUNK_SIZE { let world_x = start_x + local_x; let world_y = start_y + local_y; let noise_position = Vec3::new( (world_x as f32 * TILE_SIZE).round(), (world_y as f32 * TILE_SIZE).round(), (generate_surface_noise(world_x, world_y) * TILE_SIZE).round(), ); // Spawn tiles and add them to tilemap for z in -10..=5 { let position = Vec3::new( (world_x as f32 * TILE_SIZE).round(), (world_y as f32 * TILE_SIZE).round(), (z as f32 * TILE_SIZE).round(), ); let pos_ivec = position.as_ivec3(); if z < -8 { let noise_value = noise.get([ world_x as f64 * 0.05, world_y as f64 * 0.05, z as f64 * 0.05, ]); if noise_value < -0.5 { FloorTilePrefab::air(position).spawn(&mut commands); tilemap .floor_tiles .insert(pos_ivec, (0, false, true, 0, [0; 8])); // Air tile floor_positions.push((position, "air")); } else if noise_value < 0.8 { FloorTilePrefab::rock(position).spawn(&mut commands); tilemap .floor_tiles .insert(pos_ivec, (2, true, false, 50, [0; 8])); // Rock tile floor_positions.push((position, "rock")); } else { FloorTilePrefab::dirt(position).spawn(&mut commands); tilemap .floor_tiles .insert(pos_ivec, (1, true, true, 85, [0; 8])); // Dirt tile floor_positions.push((position, "dirt")); } } else if noise_position.z > position.z { if (generate_surface_noise(world_x, world_y) * TILE_SIZE).round() <= position.z + TILE_SIZE { FloorTilePrefab::grass(position).spawn(&mut commands); tilemap .floor_tiles .insert(pos_ivec, (1, true, true, 100, [0; 8])); // Dirt tile (grass) floor_positions.push((position, "dirt")); } else { FloorTilePrefab::dirt(position).spawn(&mut commands); tilemap .floor_tiles .insert(pos_ivec, (1, true, true, 85, [0; 8])); // Dirt tile floor_positions.push((position, "dirt")); } } else { FloorTilePrefab::air(position).spawn(&mut commands); tilemap .floor_tiles .insert(pos_ivec, (0, false, true, 0, [0; 8])); // Air tile floor_positions.push((position, "air")); } } } } // Generate fixtures and add them to tilemap for (position, floor_type) in floor_positions.iter() { let above_pos = *position + Vec3::new(0.0, 0.0, TILE_SIZE); let above_ivec = above_pos.as_ivec3(); match *floor_type { "dirt" => { FixtureTilePrefab::dirt_wall(above_pos).spawn(&mut commands); tilemap.fixture_tiles.insert(above_ivec, (1, true, [0; 8])); // Dirt wall } "rock" => { FixtureTilePrefab::rock_wall(above_pos).spawn(&mut commands); tilemap.fixture_tiles.insert(above_ivec, (2, true, [0; 8])); // Rock wall } "bedrock" => { FixtureTilePrefab::bedrock_wall(above_pos).spawn(&mut commands); tilemap.fixture_tiles.insert(above_ivec, (3, true, [0; 8])); // Bedrock wall } _ => {} } } } } fn setup_initial_chunks(mut event_writer: EventWriter) { println!("setup_initial_chunks"); for x in -7..=7 { for y in -7..=7 { event_writer.send(LoadChunkEvent { chunk_position: IVec2::new(x, y), }); } } } pub struct TilemapPlugin; impl Plugin for TilemapPlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_event::() .add_systems(Startup, (setup_chunk_system, setup_initial_chunks)) .add_systems( PostStartup, ( handle_chunk_loading, handle_tile_occlusion_updates, tile::update_tile_visibility, ) .chain(), ) .add_systems(FixedUpdate, (handle_chunk_loading).chain()); } }