refactor file locations for readability
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
pub struct FloorTile {
|
||||
pub id: u32,
|
||||
pub opaque: bool,
|
||||
pub walkable: bool,
|
||||
pub astar_weight: u8,
|
||||
pub visible_range: [u32; 8],
|
||||
}
|
||||
|
||||
impl Default for FloorTile {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
opaque: true,
|
||||
walkable: true,
|
||||
astar_weight: 0,
|
||||
visible_range: [0; 8],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
pub struct FixtureTile {
|
||||
pub id: u32,
|
||||
pub solid: bool,
|
||||
pub visible_range: [u32; 8],
|
||||
}
|
||||
|
||||
impl Default for FixtureTile {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
solid: true,
|
||||
visible_range: [0; 8],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct TileState {
|
||||
pub timer: Timer,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
pub mod components;
|
||||
pub mod prefabs;
|
||||
pub mod rendering;
|
||||
pub mod tilemap;
|
||||
pub mod visibility;
|
||||
|
||||
pub use components::*;
|
||||
pub use prefabs::*;
|
||||
pub use rendering::*;
|
||||
pub use tilemap::*;
|
||||
pub use visibility::*;
|
||||
@@ -0,0 +1,166 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::world::{
|
||||
tiles::{FixtureTile, FloorTile, TileState},
|
||||
FIXTURE_ID_OFFSET,
|
||||
};
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct FloorTilePrefab {
|
||||
transform: Transform,
|
||||
tile: FloorTile,
|
||||
tile_state: TileState,
|
||||
visibility: Visibility,
|
||||
}
|
||||
|
||||
impl FloorTilePrefab {
|
||||
pub fn grass(position: Vec3) -> Self {
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 1,
|
||||
astar_weight: 100,
|
||||
..Default::default()
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dirt(position: Vec3) -> Self {
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 2,
|
||||
astar_weight: 85,
|
||||
..Default::default()
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rock(position: Vec3) -> Self {
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 3,
|
||||
astar_weight: 50,
|
||||
..Default::default()
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn air(position: Vec3) -> Self {
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 0,
|
||||
opaque: false,
|
||||
walkable: false,
|
||||
..Default::default()
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bedrock(position: Vec3) -> Self {
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: 4,
|
||||
astar_weight: 150,
|
||||
..Default::default()
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(self, commands: &mut Commands) {
|
||||
commands.spawn((self.tile, self.transform, self.tile_state, self.visibility));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct FixtureTilePrefab {
|
||||
transform: Transform,
|
||||
tile: FixtureTile,
|
||||
visibility: Visibility,
|
||||
}
|
||||
|
||||
impl FixtureTilePrefab {
|
||||
pub fn dirt_wall(position: Vec3) -> Self {
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 1,
|
||||
..Default::default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rock_wall(position: Vec3) -> Self {
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 2,
|
||||
..Default::default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(position: Vec3) -> Self {
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 4,
|
||||
..Default::default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leaves(position: Vec3) -> Self {
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + 5,
|
||||
..Default::default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bedrock_wall(position: Vec3) -> Self {
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: 0,
|
||||
..Default::default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(self, commands: &mut Commands) -> Entity {
|
||||
commands
|
||||
.spawn((self.tile, self.transform, self.visibility))
|
||||
.id()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
use bevy::{asset::RenderAssetUsages, prelude::*, render::render_resource};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform::sync::Mutex;
|
||||
use bevy_platform::time::Instant;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::{
|
||||
constants::{PIXEL_RATIO, TILE_PIXELS, TILE_SIZE},
|
||||
world::{tiles::FloorTile, TextureIDs, Textures, Z_BELOW, Z_TOTAL},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TerrainSpriteState {
|
||||
Inactive,
|
||||
WaitingForRender,
|
||||
InProgress,
|
||||
RenderReady,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct CurrentWorldSpriteState {
|
||||
pub state: TerrainSpriteState,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct TerrainSprite {
|
||||
pub z_index: usize, // Store the z-index this sprite belongs to (as world_Z)
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct QuiltCache {
|
||||
pub dimensions: HashMap<usize, (u32, u32)>,
|
||||
pub dirty_indices: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Default for QuiltCache {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dimensions: HashMap::new(),
|
||||
dirty_indices: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// here be dragons :(
|
||||
pub fn build_quilted_terrain_sprites(
|
||||
query_tiles: Query<(&FloorTile, &Transform)>,
|
||||
commands: ParallelCommands<'_, '_>,
|
||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||
textures: Res<Textures>,
|
||||
texture_ids: Res<TextureIDs>,
|
||||
query_terrain_sprites: Query<Entity, With<TerrainSprite>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
mut quilt_cache: ResMut<QuiltCache>,
|
||||
) {
|
||||
if cwss.state != TerrainSpriteState::WaitingForRender {
|
||||
return;
|
||||
}
|
||||
let now = Instant::now();
|
||||
cwss.state = TerrainSpriteState::InProgress;
|
||||
|
||||
// Despawn existing terrain sprites
|
||||
let despawn_entities: Vec<Entity> = query_terrain_sprites.iter().collect();
|
||||
for entity in despawn_entities {
|
||||
commands.command_scope(|mut cmd| {
|
||||
cmd.entity(entity).despawn();
|
||||
});
|
||||
}
|
||||
|
||||
// Collect tiles by z-index first
|
||||
let mut tiles_by_z: HashMap<usize, Vec<(Vec2, &FloorTile)>> = HashMap::new();
|
||||
|
||||
// Calculate bounds for all visible tiles
|
||||
for (floortile, transform) in query_tiles.iter() {
|
||||
let position = Vec2::new(transform.translation.x, transform.translation.y);
|
||||
|
||||
for z_index in 0..=Z_TOTAL as usize {
|
||||
if (floortile.visible_range[z_index / 32] & (1 << (z_index % 32) as u32)) != 0 {
|
||||
tiles_by_z
|
||||
.entry(z_index)
|
||||
.or_default()
|
||||
.push((position, floortile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe collections to store results
|
||||
let dimensions_mutex = Mutex::new(HashMap::new());
|
||||
let texture_handles_mutex = Mutex::new(HashMap::new());
|
||||
|
||||
// Process each z-level in parallel
|
||||
let z_indices: Vec<usize> = tiles_by_z.keys().cloned().collect();
|
||||
|
||||
z_indices.into_par_iter().for_each(|z_index| {
|
||||
let tiles = if let Some(tiles) = tiles_by_z.get(&z_index) {
|
||||
tiles
|
||||
} else {
|
||||
return; // Skip empty z-levels
|
||||
};
|
||||
|
||||
if tiles.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate bounds
|
||||
let min_x_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.x).reduce(f32::min).unwrap()
|
||||
- TILE_SIZE / 2.0)
|
||||
/ TILE_SIZE)
|
||||
.floor()
|
||||
* TILE_SIZE;
|
||||
let min_y_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.y).reduce(f32::min).unwrap()
|
||||
- TILE_SIZE / 2.0)
|
||||
/ TILE_SIZE)
|
||||
.floor()
|
||||
* TILE_SIZE;
|
||||
let max_x_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.x).reduce(f32::max).unwrap()
|
||||
+ TILE_SIZE / 2.0)
|
||||
/ TILE_SIZE)
|
||||
.ceil()
|
||||
* TILE_SIZE;
|
||||
let max_y_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.y).reduce(f32::max).unwrap()
|
||||
+ TILE_SIZE / 2.0)
|
||||
/ TILE_SIZE)
|
||||
.ceil()
|
||||
* TILE_SIZE;
|
||||
|
||||
// Calculate terrain texture dimensions in pixels
|
||||
let width_tiles = ((max_x_aligned - min_x_aligned) / TILE_SIZE) as u32;
|
||||
let height_tiles = ((max_y_aligned - min_y_aligned) / TILE_SIZE) as u32;
|
||||
let width_px = width_tiles * TILE_PIXELS;
|
||||
let height_px = height_tiles * TILE_PIXELS;
|
||||
|
||||
// Store dimensions in our thread-safe map
|
||||
dimensions_mutex
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(z_index, (width_px, height_px));
|
||||
|
||||
let mut texture_data = vec![0u8; (width_px * height_px * 4) as usize];
|
||||
|
||||
// Process tiles for this z-level
|
||||
// We can't parallelize this inner loop without more complex locking on texture_data
|
||||
for (pos, floortile) in tiles {
|
||||
if let Some(texture_id) = texture_ids.refs.get(&floortile.id) {
|
||||
if let Some(texture) = textures.handles.get(texture_id) {
|
||||
let rel_x = pos.x - min_x_aligned;
|
||||
let rel_y = pos.y - min_y_aligned;
|
||||
|
||||
let tile_x = (rel_x / TILE_SIZE).round() as u32;
|
||||
let tile_y = (height_tiles as f32 - 1.0 - (rel_y / TILE_SIZE).round()) as u32;
|
||||
|
||||
let target_x = tile_x * TILE_PIXELS;
|
||||
let target_y = tile_y * TILE_PIXELS;
|
||||
let mut data: Vec<&[u8]> = vec![];
|
||||
|
||||
let mut base_texture: &Image = &Default::default();
|
||||
|
||||
// Use a thread-safe approach to access images
|
||||
// In a full implementation, this would require a more sophisticated
|
||||
// thread-safe access pattern to Assets<Image>
|
||||
if let Some(_base_texture) = images.get(texture) {
|
||||
if let Some(_data) = &_base_texture.data {
|
||||
data.push(_data);
|
||||
base_texture = _base_texture;
|
||||
}
|
||||
}
|
||||
|
||||
blit_texture_with_alpha(
|
||||
data, // tile
|
||||
&mut texture_data, // terrain
|
||||
base_texture.size().x as u32,
|
||||
base_texture.size().y as u32,
|
||||
width_px,
|
||||
height_px,
|
||||
target_x,
|
||||
target_y,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the quilted texture
|
||||
let quilted_texture = Image::new_fill(
|
||||
render_resource::Extent3d {
|
||||
width: width_px,
|
||||
height: height_px,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
render_resource::TextureDimension::D2,
|
||||
&texture_data,
|
||||
render_resource::TextureFormat::Rgba8UnormSrgb,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
);
|
||||
|
||||
// In a real implementation, we would need thread-safe access to images
|
||||
// For now, we'll collect the textures and add them after parallel processing
|
||||
let center_x = min_x_aligned + (max_x_aligned - min_x_aligned) / 2.0;
|
||||
let center_y = min_y_aligned + (max_y_aligned - min_y_aligned) / 2.0;
|
||||
|
||||
// Store texture and position data for later spawning
|
||||
texture_handles_mutex
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(z_index, (quilted_texture, center_x, center_y));
|
||||
});
|
||||
|
||||
// Process collected textures and spawn entities
|
||||
let collected_dimensions = dimensions_mutex.into_inner().unwrap();
|
||||
let collected_textures = texture_handles_mutex.into_inner().unwrap();
|
||||
|
||||
// Update quilt_cache dimensions
|
||||
for (z_index, dimensions) in collected_dimensions {
|
||||
quilt_cache.dimensions.insert(z_index, dimensions);
|
||||
}
|
||||
|
||||
// Add images and spawn entities with the collected data
|
||||
for (z_index, (quilted_texture, center_x, center_y)) in collected_textures {
|
||||
let texture_handle = images.add(quilted_texture);
|
||||
|
||||
commands.command_scope(|mut cmd| {
|
||||
cmd.spawn((
|
||||
Sprite {
|
||||
image: texture_handle,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_xyz(
|
||||
center_x - TILE_SIZE / 2.0,
|
||||
center_y - TILE_SIZE / 2.0,
|
||||
-Z_BELOW * TILE_SIZE,
|
||||
)
|
||||
.with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||
Visibility::Hidden,
|
||||
TerrainSprite { z_index },
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
quilt_cache.dirty_indices.clear();
|
||||
cwss.state = TerrainSpriteState::RenderReady;
|
||||
println!("Terrain sprites baked in: {:.2?}", now.elapsed());
|
||||
}
|
||||
|
||||
// Helper function to blit a texture onto another texture
|
||||
fn blit_texture_with_alpha(
|
||||
sources: Vec<&[u8]>,
|
||||
target: &mut [u8],
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
target_width: u32,
|
||||
target_height: u32,
|
||||
offset_x: u32,
|
||||
offset_y: u32,
|
||||
) {
|
||||
for (_, source_data) in sources.iter().rev().enumerate() {
|
||||
for y in 0..source_height {
|
||||
if y + offset_y >= target_height {
|
||||
continue;
|
||||
}
|
||||
for x in 0..source_width {
|
||||
if x + offset_x >= target_width {
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_pixel_idx = ((y * source_width) + x) as usize * 4;
|
||||
let target_pixel_idx =
|
||||
(((y + offset_y) * target_width) + (x + offset_x)) as usize * 4;
|
||||
|
||||
let src_a = source_data[source_pixel_idx + 3];
|
||||
|
||||
if src_a == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let src_r = source_data[source_pixel_idx];
|
||||
let src_g = source_data[source_pixel_idx + 1];
|
||||
let src_b = source_data[source_pixel_idx + 2];
|
||||
|
||||
if src_a == 255 {
|
||||
target[target_pixel_idx] = src_r;
|
||||
target[target_pixel_idx + 1] = src_g;
|
||||
target[target_pixel_idx + 2] = src_b;
|
||||
target[target_pixel_idx + 3] = 255;
|
||||
} else {
|
||||
let dst_r = target[target_pixel_idx];
|
||||
let dst_g = target[target_pixel_idx + 1];
|
||||
let dst_b = target[target_pixel_idx + 2];
|
||||
|
||||
let alpha_factor = src_a as f32 / 255.0;
|
||||
let inv_alpha = 1.0 - alpha_factor;
|
||||
|
||||
target[target_pixel_idx] =
|
||||
(src_r as f32 * alpha_factor + dst_r as f32 * inv_alpha) as u8;
|
||||
target[target_pixel_idx + 1] =
|
||||
(src_g as f32 * alpha_factor + dst_g as f32 * inv_alpha) as u8;
|
||||
target[target_pixel_idx + 2] =
|
||||
(src_b as f32 * alpha_factor + dst_b as f32 * inv_alpha) as u8;
|
||||
target[target_pixel_idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_platform::collections::hash_map::HashMap;
|
||||
|
||||
#[derive(Resource, Default, Clone)]
|
||||
pub struct TileMap {
|
||||
pub floor_tiles: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])>, //id, canStandIn, canStandOn, visiblyTransparent, astar_weight, visible_range
|
||||
pub fixture_tiles: HashMap<IVec3, (i32, bool, bool, [u32; 8])>, // id, canStandIn, canStandOn, visible_range
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
use crate::{
|
||||
constants::{ITILE_SIZE, TILE_SIZE},
|
||||
game,
|
||||
world::{
|
||||
chunks::{Z_ABOVE, Z_BELOW, Z_TOTAL},
|
||||
tiles::{CurrentWorldSpriteState, FloorTile, TerrainSprite, TerrainSpriteState, TileMap},
|
||||
},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform::time::Instant;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct VisibleGameEntity;
|
||||
|
||||
pub fn compute_visibility_of_game_entities(
|
||||
mut query: Query<(&Transform, &mut Visibility, &mut Sprite), With<VisibleGameEntity>>,
|
||||
z_index: Res<game::ZIndex>,
|
||||
) {
|
||||
query
|
||||
.par_iter_mut()
|
||||
.for_each(|(transform, mut visibility, mut sprite)| {
|
||||
let entity_z = (transform.translation.z / TILE_SIZE) - 1.0;
|
||||
// if same z-level, always visible
|
||||
if z_index.0 == entity_z {
|
||||
sprite.color = Color::WHITE;
|
||||
sprite.color.set_alpha(1.0);
|
||||
*visibility = Visibility::Visible;
|
||||
return;
|
||||
};
|
||||
|
||||
// if entity too low, saturation does not matter
|
||||
if z_index.0 - entity_z > 8.0 {
|
||||
*visibility = Visibility::Hidden;
|
||||
return;
|
||||
}
|
||||
// if entity too high, saturation does not matter
|
||||
if entity_z > z_index.0 {
|
||||
*visibility = Visibility::Hidden;
|
||||
return;
|
||||
}
|
||||
|
||||
// if visible, calculate saturation
|
||||
*visibility = Visibility::Visible;
|
||||
let saturation =
|
||||
((z_index.0 - (transform.translation.z / TILE_SIZE) + 1.) / 8.0).clamp(0.0, 1.0);
|
||||
|
||||
sprite.color = Color::hsv(194.7, saturation, 1.0 - (saturation / 2.0));
|
||||
|
||||
sprite.color.set_alpha(1.0 - saturation);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct TileOcclusionEvent {
|
||||
pub tile_position: IVec3,
|
||||
}
|
||||
|
||||
pub fn handle_tile_occlusion_updates(
|
||||
mut tilemap: ResMut<TileMap>,
|
||||
mut floor_tiles: Query<(Entity, &mut FloorTile, &Transform)>,
|
||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||
mut events: EventReader<TileOcclusionEvent>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
|
||||
let count = events.len();
|
||||
|
||||
// Process events in parallel
|
||||
let updates: Vec<(IVec3, [u32; 8])> = events
|
||||
.par_read()
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
let pos = event.0.tile_position;
|
||||
let visibility = calculate_visibility(pos, &tilemap);
|
||||
(pos, visibility)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Map updates for efficient lookup
|
||||
let update_map: HashMap<IVec3, [u32; 8]> = updates.into_iter().collect();
|
||||
|
||||
// Update only the relevant FloorTile components
|
||||
for (_, mut tile, pos) in floor_tiles.iter_mut() {
|
||||
if let Some(visibility) = update_map.get(&pos.translation.as_ivec3()) {
|
||||
tile.visible_range = *visibility;
|
||||
if let Some(tile_data) = tilemap.floor_tiles.get_mut(&pos.translation.as_ivec3()) {
|
||||
tile_data.5 = *visibility;
|
||||
}
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
cwss.state = TerrainSpriteState::WaitingForRender;
|
||||
println!(
|
||||
"Tile occlusion calculated for {} tiles in {:.2?}",
|
||||
count,
|
||||
start.elapsed()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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(&(_, _, _, visibly_transparent, _, _)) =
|
||||
tilemap.floor_tiles.get(&above_pos)
|
||||
{
|
||||
if !visibly_transparent {
|
||||
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 {
|
||||
// id 0 = air tile
|
||||
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 update_tile_visibility(
|
||||
z_index: Res<game::ZIndex>,
|
||||
mut query: Query<(&TerrainSprite, &mut Visibility)>,
|
||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||
) {
|
||||
let now = Instant::now();
|
||||
|
||||
for (terrain_sprite, mut visibility) in query.iter_mut() {
|
||||
*visibility = if terrain_sprite.z_index == ((z_index.0 + Z_BELOW) as usize) {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
Visibility::Hidden
|
||||
};
|
||||
}
|
||||
cwss.state = TerrainSpriteState::Inactive;
|
||||
println!("Visibility update: {:.2?}", now.elapsed());
|
||||
}
|
||||
Reference in New Issue
Block a user