319 lines
11 KiB
Rust
319 lines
11 KiB
Rust
use crate::constants::{ITILE_SIZE, TILE_SIZE};
|
|
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileMap};
|
|
use crate::tiles::{
|
|
CurrentWorldSpriteState, FixtureTilePrefab, FloorTilePrefab, TerrainSpriteState,
|
|
};
|
|
use bevy::prelude::*;
|
|
use bevy_platform::collections::hash_map::HashMap;
|
|
use noise::{NoiseFn, Perlin};
|
|
|
|
pub const CHUNK_SIZE: i32 = 8;
|
|
|
|
pub const Z_BELOW: f32 = 10.0;
|
|
pub const Z_ABOVE: f32 = 5.0;
|
|
pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW; // MAX 255 DO NOT EXCEED
|
|
|
|
#[derive(Resource)]
|
|
pub struct ChunkMap {
|
|
pub loaded_chunks: HashMap<IVec2, bool>,
|
|
}
|
|
|
|
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<TileMap>,
|
|
mut query_set: ParamSet<(
|
|
Query<(&mut FloorTile, &Transform, &mut NeedsOccluded)>,
|
|
Query<(&mut FixtureTile, &Transform, &mut NeedsOccluded)>,
|
|
Query<(Entity, &mut NeedsOccluded)>,
|
|
)>,
|
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
|
) {
|
|
println!("updating tile occlusion");
|
|
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::<NeedsOccluded>();
|
|
}
|
|
}
|
|
cwss.state = TerrainSpriteState::WaitingForRender;
|
|
}
|
|
|
|
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
|
let mut visible_range = [0u32; 8];
|
|
|
|
for mut camera_z in -Z_BELOW as i32..=Z_ABOVE as i32 {
|
|
camera_z *= ITILE_SIZE;
|
|
let mut is_visible = false;
|
|
|
|
if pos.z > camera_z {
|
|
continue;
|
|
}
|
|
|
|
let mut is_occluded = false;
|
|
|
|
let v_check_height: i32 = Z_TOTAL as i32 - (camera_z / ITILE_SIZE) + Z_BELOW as i32 + 1;
|
|
'vertical_check: for z_offset in 1..v_check_height {
|
|
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;
|
|
}
|
|
}
|
|
} 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 is_visible {
|
|
let z2 = ((camera_z / ITILE_SIZE) + Z_BELOW as i32) 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 generate_chunks_from_algo(
|
|
mut commands: Commands,
|
|
mut events: EventReader<LoadChunkEvent>,
|
|
mut chunk_map: ResMut<ChunkMap>,
|
|
mut tilemap: ResMut<TileMap>,
|
|
) {
|
|
let cave_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 -Z_BELOW as isize..=Z_ABOVE as isize {
|
|
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 < -5 {
|
|
let noise_value = cave_noise.get([
|
|
world_x as f64 * 0.05,
|
|
world_y as f64 * 0.05,
|
|
z as f64 * (0.15 * -z as f64),
|
|
]);
|
|
if noise_value < -0.65 {
|
|
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<LoadChunkEvent>) {
|
|
println!("setup_initial_chunks");
|
|
for x in -3..=3 {
|
|
for y in -2..=2 {
|
|
event_writer.write(LoadChunkEvent {
|
|
chunk_position: IVec2::new(x, y),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn index_z_to_absolute_z(z: f32) -> f32 {
|
|
z + Z_BELOW
|
|
}
|
|
|
|
pub fn world_z_to_absolute_z(z: f32) -> f32 {
|
|
z + Z_BELOW * ITILE_SIZE as f32
|
|
}
|
|
|
|
pub fn absolute_z_to_index_z(z: f32) -> f32 {
|
|
z - Z_BELOW
|
|
}
|
|
|
|
pub fn absolute_z_to_world_z(z: f32) -> f32 {
|
|
z - Z_BELOW * ITILE_SIZE as f32
|
|
}
|
|
|
|
pub struct TilemapPlugin;
|
|
|
|
impl Plugin for TilemapPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_resource::<ChunkMap>()
|
|
.add_event::<LoadChunkEvent>()
|
|
.add_systems(Startup, (setup_chunk_system, setup_initial_chunks))
|
|
.add_systems(
|
|
PostStartup,
|
|
(generate_chunks_from_algo, handle_tile_occlusion_updates).chain(),
|
|
)
|
|
.add_systems(FixedUpdate, (generate_chunks_from_algo).chain());
|
|
}
|
|
}
|