Tilemap chunking part 2
This commit is contained in:
+131
-185
@@ -1,5 +1,5 @@
|
||||
use crate::constants::TILE_SIZE;
|
||||
use crate::tile::{FixtureTile, FloorTile, TileMap};
|
||||
use crate::tile::{self, FixtureTile, FloorTile, NeedsOccluded, TileMap};
|
||||
use crate::tiles::{FixtureTilePrefab, FloorTilePrefab};
|
||||
use bevy::prelude::*;
|
||||
use noise::{NoiseFn, Perlin};
|
||||
@@ -30,139 +30,131 @@ fn setup_chunk_system(mut commands: Commands) {
|
||||
commands.insert_resource(ChunkMap::default());
|
||||
}
|
||||
|
||||
// Events
|
||||
#[derive(Event)]
|
||||
pub struct ChunkOcclusionEvent {
|
||||
pub chunk_pos: IVec2,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct TileOcclusionUpdateEvent {
|
||||
pub positions: Vec<IVec3>,
|
||||
}
|
||||
|
||||
|
||||
// System to handle individual tile occlusion updates
|
||||
fn handle_tile_occlusion_updates(
|
||||
mut events: EventReader<TileOcclusionUpdateEvent>,
|
||||
pub fn handle_tile_occlusion_updates(
|
||||
mut commands: Commands,
|
||||
tilemap: Res<TileMap>,
|
||||
mut query_set: ParamSet<(
|
||||
Query<(&mut FloorTile, &Transform)>,
|
||||
Query<(&mut FixtureTile, &Transform)>,
|
||||
Query<(&mut FloorTile, &Transform, &mut NeedsOccluded)>,
|
||||
Query<(&mut FixtureTile, &Transform, &mut NeedsOccluded)>,
|
||||
Query<(Entity, &mut NeedsOccluded)>,
|
||||
)>,
|
||||
) {
|
||||
let tile_size = TILE_SIZE as i32;
|
||||
|
||||
let calculate_visibility = |pos: IVec3, tilemap: &TileMap| {
|
||||
let mut visible_range = [0u32; 8];
|
||||
|
||||
for mut camera_z in -150..100 {
|
||||
camera_z *= tile_size;
|
||||
let mut is_visible = false;
|
||||
|
||||
if pos.z > camera_z {
|
||||
continue;
|
||||
query_set
|
||||
.p0()
|
||||
.par_iter_mut()
|
||||
.for_each(|(mut tile, transform, mut needs_occluded)| {
|
||||
if needs_occluded.has_been_occluded {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut is_occluded = false;
|
||||
|
||||
'vertical_check: for z_offset in 1..=35 {
|
||||
let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * tile_size));
|
||||
if above_pos.z <= camera_z {
|
||||
// Check both floors and fixtures for occlusion
|
||||
if let Some(&(_, opaque, _, _, _)) = tilemap.floors.get(&above_pos) {
|
||||
if opaque {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 * tile_size,
|
||||
pos.y + y_offset * tile_size,
|
||||
pos.z + z_offset * tile_size,
|
||||
);
|
||||
fixture.visible_range = calculate_visibility(
|
||||
transform.translation.as_ivec3() - IVec3::new(0, 0, 1),
|
||||
&tilemap,
|
||||
);
|
||||
needs_occluded.has_been_occluded = true;
|
||||
});
|
||||
|
||||
// Check both floors and fixtures for visibility
|
||||
if let Some(&(id, _, _, _, _)) = tilemap.floors.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 {
|
||||
print!("#");
|
||||
let z2 = ((camera_z / tile_size) + 150) as usize;
|
||||
visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32);
|
||||
}
|
||||
}
|
||||
|
||||
visible_range
|
||||
};
|
||||
|
||||
for event in events.read() {
|
||||
print!("event2");
|
||||
// Process floor tiles
|
||||
for (mut tile, transform) in query_set.p0().iter_mut() {
|
||||
let pos = transform.translation.as_ivec3();
|
||||
if event.positions.contains(&pos) {
|
||||
// this never calls
|
||||
print!("floor");
|
||||
tile.visible_range = calculate_visibility(pos, &tilemap);
|
||||
}
|
||||
}
|
||||
|
||||
// Process fixture tiles
|
||||
for (mut fixture, transform) in query_set.p1().iter_mut() {
|
||||
let pos = transform.translation.as_ivec3();
|
||||
if event.positions.contains(&pos) {
|
||||
// this never calls
|
||||
print!("fixture");
|
||||
let adjusted_pos = pos - IVec3::new(0, 0, 1);
|
||||
fixture.visible_range = calculate_visibility(adjusted_pos, &tilemap);
|
||||
}
|
||||
for (entity, occ) in query_set.p2().iter_mut() {
|
||||
if occ.has_been_occluded {
|
||||
commands.entity(entity).remove::<NeedsOccluded>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
||||
let tile_size = TILE_SIZE as i32;
|
||||
|
||||
let mut visible_range = [0u32; 8];
|
||||
|
||||
for mut camera_z in -150..100 {
|
||||
camera_z *= tile_size;
|
||||
let mut is_visible = false;
|
||||
|
||||
if pos.z > camera_z {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut is_occluded = false;
|
||||
|
||||
'vertical_check: for z_offset in 1..=35 {
|
||||
let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * tile_size));
|
||||
if above_pos.z <= camera_z {
|
||||
if let Some(&(_, opaque, _, _, _)) = tilemap.floors.get(&above_pos) {
|
||||
if opaque {
|
||||
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 * tile_size,
|
||||
pos.y + y_offset * tile_size,
|
||||
pos.z + z_offset * tile_size,
|
||||
);
|
||||
|
||||
if let Some(&(id, _, _, _, _)) = tilemap.floors.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 / tile_size) + 150) as usize;
|
||||
visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32);
|
||||
}
|
||||
}
|
||||
|
||||
visible_range
|
||||
}
|
||||
|
||||
fn handle_chunk_loading(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut events: EventReader<LoadChunkEvent>,
|
||||
mut chunk_map: ResMut<ChunkMap>,
|
||||
mut tilemap: ResMut<TileMap>,
|
||||
mut occlusion_events: EventWriter<ChunkOcclusionEvent>,
|
||||
) {
|
||||
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;
|
||||
@@ -181,8 +173,10 @@ fn handle_chunk_loading(
|
||||
let world_x = start_x + local_x;
|
||||
let world_y = start_y + local_y;
|
||||
|
||||
let noise_value_a = noise.get([world_x as f64 * 0.01, world_y as f64 * 0.01]) * 1.25;
|
||||
let noise_value_b = noise.get([world_x as f64 * 0.05, world_y as f64 * 0.05]) * 0.25;
|
||||
let noise_value_a =
|
||||
noise.get([world_x as f64 * 0.01, world_y as f64 * 0.01]) * 1.25;
|
||||
let noise_value_b =
|
||||
noise.get([world_x as f64 * 0.05, world_y as f64 * 0.05]) * 0.25;
|
||||
let combined_noise_value = noise_value_a + noise_value_b;
|
||||
|
||||
let noise_position = Vec3::new(
|
||||
@@ -201,33 +195,39 @@ fn handle_chunk_loading(
|
||||
let pos_ivec = position.as_ivec3();
|
||||
|
||||
if noise_position.z > position.z {
|
||||
if z < -15 {
|
||||
let noise_value = noise.get([world_x as f64 * 0.05, world_y as f64 * 0.05, z as f64 * 0.05]);
|
||||
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 {
|
||||
commands.spawn(FloorTilePrefab::air(position, &asset_server));
|
||||
FloorTilePrefab::air(position, &asset_server).spawn(&mut commands);
|
||||
tilemap.floors.insert(pos_ivec, (0, false, true, 1, [0; 8])); // Air tile
|
||||
floor_positions.push((position, "air"));
|
||||
} else if noise_value < 0.8 {
|
||||
commands.spawn(FloorTilePrefab::rock(position, &asset_server));
|
||||
tilemap.floors.insert(pos_ivec, (2, true, false, 255, [0; 8])); // Rock tile
|
||||
FloorTilePrefab::rock(position, &asset_server).spawn(&mut commands);
|
||||
tilemap
|
||||
.floors
|
||||
.insert(pos_ivec, (2, true, false, 255, [0; 8])); // Rock tile
|
||||
floor_positions.push((position, "rock"));
|
||||
} else {
|
||||
commands.spawn(FloorTilePrefab::dirt(position, &asset_server));
|
||||
FloorTilePrefab::dirt(position, &asset_server).spawn(&mut commands);
|
||||
tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Dirt tile
|
||||
floor_positions.push((position, "dirt"));
|
||||
}
|
||||
} else {
|
||||
commands.spawn(FloorTilePrefab::dirt(position, &asset_server));
|
||||
FloorTilePrefab::dirt(position, &asset_server).spawn(&mut commands);
|
||||
tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Dirt tile
|
||||
floor_positions.push((position, "dirt"));
|
||||
}
|
||||
} else if noise_position.z < position.z {
|
||||
commands.spawn(FloorTilePrefab::air(position, &asset_server));
|
||||
FloorTilePrefab::air(position, &asset_server).spawn(&mut commands);
|
||||
tilemap.floors.insert(pos_ivec, (0, false, true, 1, [0; 8])); // Air tile
|
||||
floor_positions.push((position, "air"));
|
||||
} else {
|
||||
commands.spawn(FloorTilePrefab::dirt(position, &asset_server));
|
||||
tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Dirt tile
|
||||
FloorTilePrefab::grass(position, &asset_server).spawn(&mut commands);
|
||||
tilemap.floors.insert(pos_ivec, (1, true, true, 1, [0; 8])); // Grass tile
|
||||
floor_positions.push((position, "dirt"));
|
||||
}
|
||||
}
|
||||
@@ -241,62 +241,20 @@ fn handle_chunk_loading(
|
||||
|
||||
match *floor_type {
|
||||
"dirt" => {
|
||||
commands.spawn(FixtureTilePrefab::dirt_wall(above_pos, &asset_server));
|
||||
FixtureTilePrefab::dirt_wall(above_pos, &asset_server).spawn(&mut commands);
|
||||
tilemap.fixtures.insert(above_ivec, (1, true, [0; 8])); // Dirt wall
|
||||
}
|
||||
"rock" => {
|
||||
commands.spawn(FixtureTilePrefab::rock_wall(above_pos, &asset_server));
|
||||
FixtureTilePrefab::rock_wall(above_pos, &asset_server).spawn(&mut commands);
|
||||
tilemap.fixtures.insert(above_ivec, (2, true, [0; 8])); // Rock wall
|
||||
}
|
||||
"bedrock" => {
|
||||
commands.spawn(FixtureTilePrefab::bedrock_wall(above_pos, &asset_server));
|
||||
FixtureTilePrefab::bedrock_wall(above_pos, &asset_server).spawn(&mut commands);
|
||||
tilemap.fixtures.insert(above_ivec, (3, true, [0; 8])); // Bedrock wall
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Emit occlusion event for this chunk
|
||||
occlusion_events.send(ChunkOcclusionEvent {
|
||||
chunk_pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// System to handle chunk occlusion events
|
||||
fn handle_chunk_occlusion_events(
|
||||
mut events: EventReader<ChunkOcclusionEvent>,
|
||||
tilemap: Res<TileMap>,
|
||||
mut tile_update_events: EventWriter<TileOcclusionUpdateEvent>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let chunk_pos = event.chunk_pos;
|
||||
let tile_size = TILE_SIZE as i32;
|
||||
let start_x = chunk_pos.x * CHUNK_SIZE * tile_size;
|
||||
let end_x = start_x + (CHUNK_SIZE * tile_size);
|
||||
let start_y = chunk_pos.y * CHUNK_SIZE * tile_size;
|
||||
let end_y = start_y + (CHUNK_SIZE * tile_size);
|
||||
|
||||
// Collect positions that need visibility updates
|
||||
let mut positions = Vec::new();
|
||||
|
||||
// Add positions from the current chunk
|
||||
for x in (start_x..end_x).step_by(tile_size as usize) {
|
||||
for y in (start_y..end_y).step_by(tile_size as usize) {
|
||||
for z in -150..50 {
|
||||
let pos = IVec3::new(y, x, z * tile_size);
|
||||
if tilemap.floors.contains_key(&pos) || tilemap.fixtures.contains_key(&pos) {
|
||||
positions.push(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Send update event if we have positions to process
|
||||
if !positions.is_empty() {
|
||||
tile_update_events.send(TileOcclusionUpdateEvent { positions });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,21 +265,9 @@ impl Plugin for TilemapPlugin {
|
||||
app.init_resource::<ChunkMap>()
|
||||
.add_event::<LoadChunkEvent>()
|
||||
.add_systems(Startup, setup_chunk_system)
|
||||
.add_systems(Update, handle_chunk_loading);
|
||||
.add_systems(
|
||||
FixedUpdate,
|
||||
(handle_chunk_loading, handle_tile_occlusion_updates).chain(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin to handle occlusion systems
|
||||
pub struct OcclusionPlugin;
|
||||
|
||||
|
||||
impl Plugin for OcclusionPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<ChunkOcclusionEvent>()
|
||||
.add_event::<TileOcclusionUpdateEvent>()
|
||||
.add_systems(Update, (
|
||||
handle_chunk_occlusion_events,
|
||||
handle_tile_occlusion_updates,
|
||||
).chain());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user