Terrain quilting
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
pub const PIXEL_RATIO: f32 = 2.0;
|
pub const PIXEL_RATIO: f32 = 2.0;
|
||||||
pub const TILE_SIZE: f32 = 16.0 * PIXEL_RATIO;
|
pub const TILE_SIZE: f32 = 16.0;
|
||||||
pub const ITILE_SIZE: i32 = TILE_SIZE as i32;
|
pub const ITILE_SIZE: i32 = TILE_SIZE as i32;
|
||||||
pub const UTILE_SIZE: u32 = TILE_SIZE as u32;
|
pub const UTILE_SIZE: u32 = TILE_SIZE as u32;
|
||||||
|
|||||||
+3
-2
@@ -14,8 +14,9 @@ fn main() {
|
|||||||
App::new()
|
App::new()
|
||||||
.insert_resource(game::ZIndex(0.0))
|
.insert_resource(game::ZIndex(0.0))
|
||||||
.insert_resource(tiles::CurrentWorldSpriteState {
|
.insert_resource(tiles::CurrentWorldSpriteState {
|
||||||
state: tiles::WorldSpriteState::Inactive,
|
state: tiles::TerrainSpriteState::Inactive,
|
||||||
})
|
})
|
||||||
|
.insert_resource(tiles::QuiltCache::default())
|
||||||
.add_systems(PreStartup, tiles::initialize_textures)
|
.add_systems(PreStartup, tiles::initialize_textures)
|
||||||
.add_plugins(
|
.add_plugins(
|
||||||
DefaultPlugins
|
DefaultPlugins
|
||||||
@@ -44,7 +45,7 @@ fn main() {
|
|||||||
(
|
(
|
||||||
camera::camera_movement,
|
camera::camera_movement,
|
||||||
cursor::move_cursor,
|
cursor::move_cursor,
|
||||||
tiles::build_world_sprites,
|
tiles::build_quilted_terrain_sprites,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.init_resource::<tile::CameraMoved>()
|
.init_resource::<tile::CameraMoved>()
|
||||||
|
|||||||
+1
-1
@@ -88,7 +88,7 @@ pub fn update_tile_visibility(
|
|||||||
// Update visibility for all terrain sprites
|
// Update visibility for all terrain sprites
|
||||||
for (terrain_sprite, mut visibility) in query.iter_mut() {
|
for (terrain_sprite, mut visibility) in query.iter_mut() {
|
||||||
*visibility =
|
*visibility =
|
||||||
if terrain_sprite.z_index == ((current_z + tilemap::Z_BELOW as isize) as usize) {
|
if terrain_sprite.z_index <= ((current_z + tilemap::Z_BELOW as isize) as usize) {
|
||||||
Visibility::Visible
|
Visibility::Visible
|
||||||
} else {
|
} else {
|
||||||
Visibility::Hidden
|
Visibility::Hidden
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
use crate::constants::{ITILE_SIZE, TILE_SIZE};
|
use crate::constants::{ITILE_SIZE, TILE_SIZE};
|
||||||
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileMap};
|
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileMap};
|
||||||
use crate::tiles::{CurrentWorldSpriteState, FixtureTilePrefab, FloorTilePrefab, WorldSpriteState};
|
use crate::tiles::{
|
||||||
|
CurrentWorldSpriteState, FixtureTilePrefab, FloorTilePrefab, TerrainSpriteState,
|
||||||
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_platform::collections::hash_map::HashMap;
|
use bevy_platform::collections::hash_map::HashMap;
|
||||||
use noise::{NoiseFn, Perlin};
|
use noise::{NoiseFn, Perlin};
|
||||||
@@ -73,7 +75,7 @@ pub fn handle_tile_occlusion_updates(
|
|||||||
commands.entity(entity).remove::<NeedsOccluded>();
|
commands.entity(entity).remove::<NeedsOccluded>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cwss.state = WorldSpriteState::WaitingForRender;
|
cwss.state = TerrainSpriteState::WaitingForRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
||||||
|
|||||||
+196
-36
@@ -1,6 +1,8 @@
|
|||||||
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileState};
|
use crate::tile::{FixtureTile, FloorTile, NeedsOccluded, TileState};
|
||||||
use crate::{constants::*, game, tilemap};
|
use crate::{constants::*, game, tilemap};
|
||||||
|
use bevy::asset::RenderAssetUsages;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::render_resource;
|
||||||
use bevy_platform::collections::hash_map::HashMap;
|
use bevy_platform::collections::hash_map::HashMap;
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
@@ -77,7 +79,7 @@ pub fn initialize_textures(mut commands: Commands, asset_server: Res<AssetServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum WorldSpriteState {
|
pub enum TerrainSpriteState {
|
||||||
Inactive,
|
Inactive,
|
||||||
WaitingForRender,
|
WaitingForRender,
|
||||||
InProgress,
|
InProgress,
|
||||||
@@ -86,7 +88,7 @@ pub enum WorldSpriteState {
|
|||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct CurrentWorldSpriteState {
|
pub struct CurrentWorldSpriteState {
|
||||||
pub state: WorldSpriteState,
|
pub state: TerrainSpriteState,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -96,57 +98,215 @@ pub struct TerrainSprite {
|
|||||||
pub z_index: usize, // Store the z-index this sprite belongs to (as world_Z)
|
pub z_index: usize, // Store the z-index this sprite belongs to (as world_Z)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This system generates sprites for all z-indices and stores references to them
|
#[derive(Resource)]
|
||||||
pub fn build_world_sprites(
|
pub struct QuiltCache {
|
||||||
|
// Tracks the dimensions of the quilted texture for each z-index
|
||||||
|
pub dimensions: HashMap<usize, (u32, u32)>,
|
||||||
|
// Tracks if any z-index needs to be requilt
|
||||||
|
pub dirty_indices: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QuiltCache {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
dimensions: HashMap::new(),
|
||||||
|
dirty_indices: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This system generates a quilted sprite for each z-index
|
||||||
|
pub fn build_quilted_terrain_sprites(
|
||||||
query: Query<(&FloorTile, &Transform)>,
|
query: Query<(&FloorTile, &Transform)>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cwss: ResMut<CurrentWorldSpriteState>,
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||||
textures: Res<Textures>,
|
textures: Res<Textures>,
|
||||||
texture_ids: Res<TextureIDs>,
|
texture_ids: Res<TextureIDs>,
|
||||||
query_1: Query<Entity, With<TerrainSprite>>,
|
query_sprites: Query<Entity, With<TerrainSprite>>,
|
||||||
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
mut quilt_cache: ResMut<QuiltCache>,
|
||||||
) {
|
) {
|
||||||
if cwss.state != WorldSpriteState::WaitingForRender {
|
if cwss.state != TerrainSpriteState::WaitingForRender {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
cwss.state = WorldSpriteState::InProgress;
|
cwss.state = TerrainSpriteState::InProgress;
|
||||||
|
|
||||||
// despawn all existing terrain sprites
|
// despawn all existing quilted terrain sprites
|
||||||
for entity in query_1.iter() {
|
for entity in query_sprites.iter() {
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate sprites for all z-indices (0 to 150)
|
// Map to collect all visible tiles per z-index
|
||||||
|
let mut tiles_by_z: HashMap<usize, Vec<(Vec2, &FloorTile)>> = HashMap::new();
|
||||||
|
|
||||||
|
// First pass: Collect all visible tiles by z_index and compute bounds
|
||||||
for z_index in 0..=tilemap::Z_TOTAL as usize {
|
for z_index in 0..=tilemap::Z_TOTAL as usize {
|
||||||
for (floortile, transform) in query.iter() {
|
for (floortile, transform) in query.iter() {
|
||||||
let is_visible =
|
let is_visible =
|
||||||
(floortile.visible_range[z_index / 32] & (1 << (z_index % 32) as u32)) != 0;
|
(floortile.visible_range[z_index / 32] & (1 << (z_index % 32) as u32)) != 0;
|
||||||
if is_visible {
|
if is_visible {
|
||||||
let id = floortile.id;
|
let position = Vec2::new(transform.translation.x, transform.translation.y);
|
||||||
let texture_id = texture_ids.refs.get(&id).unwrap();
|
tiles_by_z
|
||||||
let texture = textures.handles.get(texture_id).unwrap();
|
.entry(z_index)
|
||||||
let sprite = Sprite {
|
.or_default()
|
||||||
image: texture.clone(),
|
.push((position, floortile));
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Spawn with hidden visibility initially - update_tile_visibility will handle showing/hiding
|
|
||||||
commands.spawn((
|
|
||||||
sprite,
|
|
||||||
*transform,
|
|
||||||
TerrainSprite { z_index },
|
|
||||||
Visibility::Hidden,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cwss.state = WorldSpriteState::RenderReady;
|
// For each z-index, create a quilted texture
|
||||||
|
for (z_index, tiles) in tiles_by_z.iter() {
|
||||||
|
if tiles.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
// Find bounds for the quilted texture
|
||||||
|
let min_x = tiles.iter().map(|(pos, _)| pos.x).reduce(f32::min).unwrap();
|
||||||
|
let max_x = tiles.iter().map(|(pos, _)| pos.x).reduce(f32::max).unwrap();
|
||||||
|
let min_y = tiles.iter().map(|(pos, _)| pos.y).reduce(f32::min).unwrap();
|
||||||
|
let max_y = tiles.iter().map(|(pos, _)| pos.y).reduce(f32::max).unwrap();
|
||||||
|
|
||||||
println!("World sprites built. Elapsed: {:.2?}", elapsed);
|
// Calculate texture dimensions in pixels
|
||||||
|
let width_tiles = ((max_x - min_x) / TILE_SIZE).ceil() as u32 + 1;
|
||||||
|
let height_tiles = ((max_y - min_y) / TILE_SIZE).ceil() as u32 + 1;
|
||||||
|
let width_px = width_tiles * UTILE_SIZE;
|
||||||
|
let height_px = height_tiles * UTILE_SIZE;
|
||||||
|
|
||||||
|
// Store the dimensions for later use (especially for adjusting the transform)
|
||||||
|
quilt_cache
|
||||||
|
.dimensions
|
||||||
|
.insert(*z_index, (width_px, height_px));
|
||||||
|
|
||||||
|
// Create a new texture
|
||||||
|
let mut texture_data = vec![0u8; (width_px * height_px * 4) as usize];
|
||||||
|
|
||||||
|
// Draw each tile into the texture
|
||||||
|
for (pos, floortile) in tiles {
|
||||||
|
let id = floortile.id;
|
||||||
|
let texture_id = texture_ids.refs.get(&id).unwrap();
|
||||||
|
let texture = textures.handles.get(texture_id).unwrap();
|
||||||
|
|
||||||
|
// Fetch the source texture data
|
||||||
|
if let Some(source_texture) = images.get(texture) {
|
||||||
|
// Calculate position within the quilted texture
|
||||||
|
let tile_x = ((pos.x - min_x) / TILE_SIZE).floor() as u32;
|
||||||
|
let tile_y = ((pos.y - min_y) / TILE_SIZE).floor() as u32;
|
||||||
|
|
||||||
|
// Copy the tile texture into the quilted texture
|
||||||
|
if let Some(data) = &source_texture.data {
|
||||||
|
blit_texture(
|
||||||
|
data,
|
||||||
|
&mut texture_data,
|
||||||
|
source_texture.size().x as u32,
|
||||||
|
source_texture.size().y as u32,
|
||||||
|
width_px,
|
||||||
|
height_px,
|
||||||
|
tile_x * UTILE_SIZE,
|
||||||
|
tile_y * UTILE_SIZE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new image asset
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
let texture_handle = images.add(quilted_texture);
|
||||||
|
|
||||||
|
// Calculate the center position of the quilted sprite
|
||||||
|
let center_x = min_x + (max_x - min_x) / 2.0;
|
||||||
|
let center_y = min_y + (max_y - min_y) / 2.0;
|
||||||
|
|
||||||
|
// Spawn the quilted sprite
|
||||||
|
commands.spawn((
|
||||||
|
Sprite {
|
||||||
|
image: texture_handle,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Transform::from_xyz(center_x, center_y, *z_index as f32)
|
||||||
|
.with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||||
|
Visibility::Hidden,
|
||||||
|
TerrainSprite { z_index: *z_index },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any dirty flags
|
||||||
|
quilt_cache.dirty_indices.clear();
|
||||||
|
|
||||||
|
cwss.state = TerrainSpriteState::RenderReady;
|
||||||
|
println!(
|
||||||
|
"Quilted world sprites built. Elapsed: {:.2?}",
|
||||||
|
now.elapsed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to blit a texture onto another texture
|
||||||
|
fn blit_texture(
|
||||||
|
source: &[u8],
|
||||||
|
target: &mut [u8],
|
||||||
|
source_width: u32,
|
||||||
|
source_height: u32,
|
||||||
|
target_width: u32,
|
||||||
|
target_height: u32,
|
||||||
|
offset_x: u32,
|
||||||
|
offset_y: u32,
|
||||||
|
) {
|
||||||
|
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_idx = ((y * source_width) + x) as usize * 4;
|
||||||
|
let target_idx = (((y + offset_y) * target_width) + (x + offset_x)) as usize * 4;
|
||||||
|
|
||||||
|
// Only copy non-transparent pixels
|
||||||
|
if source.len() > source_idx + 3 && source[source_idx + 3] > 0 {
|
||||||
|
target[target_idx] = source[source_idx]; // R
|
||||||
|
target[target_idx + 1] = source[source_idx + 1]; // G
|
||||||
|
target[target_idx + 2] = source[source_idx + 2]; // B
|
||||||
|
target[target_idx + 3] = source[source_idx + 3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This system handles updating quilted sprites when the world changes
|
||||||
|
pub fn update_quilts_on_world_change(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut quilt_cache: ResMut<QuiltCache>,
|
||||||
|
mut cwss: ResMut<CurrentWorldSpriteState>,
|
||||||
|
// Add any resources or queries that indicate world changes
|
||||||
|
// For example, if you have a WorldChangeEvent:
|
||||||
|
// mut world_changes: EventReader<WorldChangeEvent>,
|
||||||
|
) {
|
||||||
|
// Example: Check for world changes
|
||||||
|
// if !world_changes.is_empty() {
|
||||||
|
// for event in world_changes.iter() {
|
||||||
|
// quilt_cache.dirty_indices.push(event.z_index);
|
||||||
|
// }
|
||||||
|
// cwss.state = WorldSpriteState::WaitingForRender;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Alternatively, if you have specific systems that modify the world,
|
||||||
|
// you could have them set cwss.state = WorldSpriteState::WaitingForRender
|
||||||
|
// and add affected z-indices to quilt_cache.dirty_indices
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
@@ -161,7 +321,7 @@ pub struct FloorTilePrefab {
|
|||||||
impl FloorTilePrefab {
|
impl FloorTilePrefab {
|
||||||
pub fn grass(position: Vec3) -> Self {
|
pub fn grass(position: Vec3) -> Self {
|
||||||
FloorTilePrefab {
|
FloorTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FloorTile {
|
tile: FloorTile {
|
||||||
id: 1,
|
id: 1,
|
||||||
astar_weight: 100,
|
astar_weight: 100,
|
||||||
@@ -179,7 +339,7 @@ impl FloorTilePrefab {
|
|||||||
|
|
||||||
pub fn dirt(position: Vec3) -> Self {
|
pub fn dirt(position: Vec3) -> Self {
|
||||||
FloorTilePrefab {
|
FloorTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FloorTile {
|
tile: FloorTile {
|
||||||
id: 2,
|
id: 2,
|
||||||
astar_weight: 85,
|
astar_weight: 85,
|
||||||
@@ -197,7 +357,7 @@ impl FloorTilePrefab {
|
|||||||
|
|
||||||
pub fn rock(position: Vec3) -> Self {
|
pub fn rock(position: Vec3) -> Self {
|
||||||
FloorTilePrefab {
|
FloorTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FloorTile {
|
tile: FloorTile {
|
||||||
id: 3,
|
id: 3,
|
||||||
astar_weight: 50,
|
astar_weight: 50,
|
||||||
@@ -215,7 +375,7 @@ impl FloorTilePrefab {
|
|||||||
|
|
||||||
pub fn air(position: Vec3) -> Self {
|
pub fn air(position: Vec3) -> Self {
|
||||||
FloorTilePrefab {
|
FloorTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FloorTile {
|
tile: FloorTile {
|
||||||
id: 0,
|
id: 0,
|
||||||
opaque: false,
|
opaque: false,
|
||||||
@@ -234,7 +394,7 @@ impl FloorTilePrefab {
|
|||||||
|
|
||||||
pub fn bedrock(position: Vec3) -> Self {
|
pub fn bedrock(position: Vec3) -> Self {
|
||||||
FloorTilePrefab {
|
FloorTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FloorTile {
|
tile: FloorTile {
|
||||||
id: 4,
|
id: 4,
|
||||||
astar_weight: 150,
|
astar_weight: 150,
|
||||||
@@ -272,7 +432,7 @@ pub struct FixtureTilePrefab {
|
|||||||
impl FixtureTilePrefab {
|
impl FixtureTilePrefab {
|
||||||
pub fn dirt_wall(position: Vec3) -> Self {
|
pub fn dirt_wall(position: Vec3) -> Self {
|
||||||
FixtureTilePrefab {
|
FixtureTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FixtureTile {
|
tile: FixtureTile {
|
||||||
id: FIXTURE_ID_OFFSET + 1,
|
id: FIXTURE_ID_OFFSET + 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -286,7 +446,7 @@ impl FixtureTilePrefab {
|
|||||||
|
|
||||||
pub fn rock_wall(position: Vec3) -> Self {
|
pub fn rock_wall(position: Vec3) -> Self {
|
||||||
FixtureTilePrefab {
|
FixtureTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FixtureTile {
|
tile: FixtureTile {
|
||||||
id: 2,
|
id: 2,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -300,7 +460,7 @@ impl FixtureTilePrefab {
|
|||||||
|
|
||||||
pub fn bedrock_wall(position: Vec3) -> Self {
|
pub fn bedrock_wall(position: Vec3) -> Self {
|
||||||
FixtureTilePrefab {
|
FixtureTilePrefab {
|
||||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
transform: Transform::from_translation(position),
|
||||||
tile: FixtureTile {
|
tile: FixtureTile {
|
||||||
id: 0,
|
id: 0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
Reference in New Issue
Block a user