optimize visibility calcs
This commit is contained in:
@@ -146,8 +146,18 @@ pub fn generate_chunk_terrain(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
for (pos, data) in tilemap_updates.into_inner().unwrap() {
|
let new_positions: Vec<IVec3> = tilemap_updates
|
||||||
|
.into_inner()
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(pos, data)| {
|
||||||
tilemap.floor_tiles.insert(pos, data);
|
tilemap.floor_tiles.insert(pos, data);
|
||||||
|
pos
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// All tiles now in tilemap — safe to calculate visibility
|
||||||
|
for pos in new_positions {
|
||||||
occlusion_event_writer.write(TileOcclusionEvent { tile_position: pos });
|
occlusion_event_writer.write(TileOcclusionEvent { tile_position: pos });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -209,39 +209,42 @@ pub fn build_quilted_terrain_sprites(
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bounds are clamped to the chunk grid so textures are always exactly
|
// Actual tile extent within this chunk, padded by one tile on each side
|
||||||
// CHUNK_TILES wide/tall (or smaller at world edges), keeping sizes
|
// so the rendered quad always shows one tile of context beyond the edge
|
||||||
// predictable and buffer reuse rates high.
|
// (prevents the hard black cutoff at chunk/world boundaries).
|
||||||
let chunk_world_min_x = key.chunk_x as f32 * TILE_SIZE * CHUNK_TILES as f32;
|
|
||||||
let chunk_world_min_y = key.chunk_y as f32 * TILE_SIZE * CHUNK_TILES as f32;
|
|
||||||
|
|
||||||
// Actual tile extent within this chunk (may be smaller than full chunk
|
|
||||||
// at world edges or sparse z-levels).
|
|
||||||
let min_x_aligned: f32 = ((tiles.iter().map(|(p, _)| p.x).reduce(f32::min).unwrap()
|
let min_x_aligned: f32 = ((tiles.iter().map(|(p, _)| p.x).reduce(f32::min).unwrap()
|
||||||
- TILE_SIZE / 2.0)
|
- TILE_SIZE / 2.0)
|
||||||
/ TILE_SIZE)
|
/ TILE_SIZE)
|
||||||
.floor()
|
.floor()
|
||||||
* TILE_SIZE;
|
* TILE_SIZE
|
||||||
|
- TILE_SIZE;
|
||||||
let min_y_aligned: f32 = ((tiles.iter().map(|(p, _)| p.y).reduce(f32::min).unwrap()
|
let min_y_aligned: f32 = ((tiles.iter().map(|(p, _)| p.y).reduce(f32::min).unwrap()
|
||||||
- TILE_SIZE / 2.0)
|
- TILE_SIZE / 2.0)
|
||||||
/ TILE_SIZE)
|
/ TILE_SIZE)
|
||||||
.floor()
|
.floor()
|
||||||
* TILE_SIZE;
|
* TILE_SIZE
|
||||||
|
- TILE_SIZE;
|
||||||
let max_x_aligned: f32 = ((tiles.iter().map(|(p, _)| p.x).reduce(f32::max).unwrap()
|
let max_x_aligned: f32 = ((tiles.iter().map(|(p, _)| p.x).reduce(f32::max).unwrap()
|
||||||
+ TILE_SIZE / 2.0)
|
+ TILE_SIZE / 2.0)
|
||||||
/ TILE_SIZE)
|
/ TILE_SIZE)
|
||||||
.ceil()
|
.ceil()
|
||||||
* TILE_SIZE;
|
* TILE_SIZE
|
||||||
|
+ TILE_SIZE;
|
||||||
let max_y_aligned: f32 = ((tiles.iter().map(|(p, _)| p.y).reduce(f32::max).unwrap()
|
let max_y_aligned: f32 = ((tiles.iter().map(|(p, _)| p.y).reduce(f32::max).unwrap()
|
||||||
+ TILE_SIZE / 2.0)
|
+ TILE_SIZE / 2.0)
|
||||||
/ TILE_SIZE)
|
/ TILE_SIZE)
|
||||||
.ceil()
|
.ceil()
|
||||||
* TILE_SIZE;
|
* TILE_SIZE
|
||||||
|
+ TILE_SIZE;
|
||||||
|
|
||||||
let width_tiles = ((max_x_aligned - min_x_aligned) / TILE_SIZE) as u32;
|
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 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;
|
// Add 2px bleed (1px each side) so adjacent chunk quads overlap by 1px
|
||||||
|
// at any zoom level. Without this, sub-pixel gaps appear between chunks
|
||||||
|
// at fractional orthographic scales.
|
||||||
|
let width_px = width_tiles * TILE_PIXELS + 2;
|
||||||
|
let height_px = height_tiles * TILE_PIXELS + 2;
|
||||||
let required_len = (width_px * height_px * 4) as usize;
|
let required_len = (width_px * height_px * 4) as usize;
|
||||||
|
|
||||||
dimensions_results
|
dimensions_results
|
||||||
@@ -281,8 +284,8 @@ pub fn build_quilted_terrain_sprites(
|
|||||||
src_height,
|
src_height,
|
||||||
width_px,
|
width_px,
|
||||||
height_px,
|
height_px,
|
||||||
tile_x * TILE_PIXELS,
|
tile_x * TILE_PIXELS + 1, // +1 to account for 1px bleed border
|
||||||
tile_y * TILE_PIXELS,
|
tile_y * TILE_PIXELS + 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,13 +331,13 @@ pub fn build_quilted_terrain_sprites(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Transform::from_xyz(
|
Transform::from_xyz(
|
||||||
center_x - TILE_SIZE / 2.0,
|
center_x - TILE_SIZE / 2.0 - PIXEL_RATIO,
|
||||||
center_y - TILE_SIZE / 2.0,
|
center_y - TILE_SIZE / 2.0 + PIXEL_RATIO,
|
||||||
-Z_BELOW * TILE_SIZE,
|
-Z_BELOW * TILE_SIZE,
|
||||||
)
|
)
|
||||||
.with_scale(Vec3::splat(PIXEL_RATIO)),
|
.with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||||
Visibility::Hidden,
|
Visibility::Hidden,
|
||||||
TerrainSprite { key },
|
TerrainSprite { key }, // access z_index via .key.z_index
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,34 +118,8 @@ pub fn handle_tile_occlusion_updates(
|
|||||||
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
||||||
let mut visible_range = [0u32; 8];
|
let mut visible_range = [0u32; 8];
|
||||||
|
|
||||||
for mut camera_z in -Z_BELOW as i32..=Z_ABOVE as i32 {
|
let touches_air = 'neighbor_check: {
|
||||||
camera_z *= ITILE_SIZE;
|
for x_offset in -1..=1 {
|
||||||
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 y_offset in -1..=1 {
|
||||||
for z_offset in 0..=1 {
|
for z_offset in 0..=1 {
|
||||||
if x_offset == 0 && y_offset == 0 && z_offset == 0 {
|
if x_offset == 0 && y_offset == 0 && z_offset == 0 {
|
||||||
@@ -156,23 +130,61 @@ pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
|||||||
pos.y + y_offset * ITILE_SIZE,
|
pos.y + y_offset * ITILE_SIZE,
|
||||||
pos.z + z_offset * ITILE_SIZE,
|
pos.z + z_offset * ITILE_SIZE,
|
||||||
);
|
);
|
||||||
|
match tilemap.floor_tiles.get(&neighbor_pos) {
|
||||||
|
Some(&(id, _, _, _, _, _)) if id == 0 => break 'neighbor_check true,
|
||||||
|
None => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(&(id, _, _, _, _, _)) = tilemap.floor_tiles.get(&neighbor_pos) {
|
if !touches_air {
|
||||||
if id == 0 {
|
return visible_range;
|
||||||
// id 0 = air tile
|
|
||||||
is_visible = true;
|
|
||||||
break 'neighbor_check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let base_camera_index = (pos.z / ITILE_SIZE) + Z_BELOW as i32;
|
||||||
|
|
||||||
|
if base_camera_index >= 0 && base_camera_index <= Z_TOTAL as i32 {
|
||||||
|
if touches_air {
|
||||||
|
let z2 = base_camera_index as usize;
|
||||||
|
visible_range[z2 / 32] |= 1 << (z2 % 32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_visible {
|
let mut occluded = false;
|
||||||
let z2 = ((camera_z / ITILE_SIZE) + Z_BELOW as i32) as usize;
|
|
||||||
|
for z_offset in 1..=(Z_TOTAL as i32 + Z_BELOW as i32 + 1) {
|
||||||
|
let check_z = pos.z + z_offset * ITILE_SIZE;
|
||||||
|
let camera_z_index = (check_z / ITILE_SIZE) + Z_BELOW as i32;
|
||||||
|
|
||||||
|
if camera_z_index < 0 {
|
||||||
|
match tilemap.floor_tiles.get(&IVec3::new(pos.x, pos.y, check_z)) {
|
||||||
|
Some(&(_, _, _, visibly_transparent, _, _)) => occluded = !visibly_transparent,
|
||||||
|
None => occluded = false,
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if camera_z_index > Z_TOTAL as i32 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !occluded {
|
||||||
|
let z2 = camera_z_index as usize;
|
||||||
visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32);
|
visible_range[z2 / 32] |= 1 << ((z2 % 32) as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tile = tilemap.floor_tiles.get(&IVec3::new(pos.x, pos.y, check_z));
|
||||||
|
|
||||||
|
match tile {
|
||||||
|
Some(&(_, _, _, visibly_transparent, _, _)) => {
|
||||||
|
occluded = !visibly_transparent;
|
||||||
|
}
|
||||||
|
None => occluded = false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visible_range
|
visible_range
|
||||||
|
|||||||
Reference in New Issue
Block a user