average composite on terrain sprites
This commit is contained in:
+80
-73
@@ -74,7 +74,6 @@ pub fn initialize_textures(mut commands: Commands, asset_server: Res<AssetServer
|
||||
asset_server.load(BEDROCK_WALL_PATH),
|
||||
);
|
||||
|
||||
// Insert the textures resource into the world
|
||||
commands.insert_resource(Textures { handles: textures });
|
||||
commands.insert_resource(TextureIDs { refs: texture_ids });
|
||||
}
|
||||
@@ -101,9 +100,7 @@ pub struct TerrainSprite {
|
||||
|
||||
#[derive(Resource)]
|
||||
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>,
|
||||
}
|
||||
|
||||
@@ -116,7 +113,6 @@ impl Default for QuiltCache {
|
||||
}
|
||||
}
|
||||
|
||||
// This system generates a quilted sprite for each z-index
|
||||
pub fn build_quilted_terrain_sprites(
|
||||
query: Query<(&FloorTile, &Transform)>,
|
||||
mut commands: Commands,
|
||||
@@ -130,19 +126,15 @@ pub fn build_quilted_terrain_sprites(
|
||||
if cwss.state != TerrainSpriteState::WaitingForRender {
|
||||
return;
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
cwss.state = TerrainSpriteState::InProgress;
|
||||
|
||||
// despawn all existing quilted terrain sprites
|
||||
for entity in query_sprites.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
|
||||
// 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
|
||||
// Calculate bounds for all visible tiles
|
||||
for z_index in 0..=tilemap::Z_TOTAL as usize {
|
||||
for (floortile, transform) in query.iter() {
|
||||
let is_visible =
|
||||
@@ -157,75 +149,83 @@ pub fn build_quilted_terrain_sprites(
|
||||
}
|
||||
}
|
||||
|
||||
// For each z-index, create a quilted texture
|
||||
// For each z-index, create a new quilted texture, baked and composited.
|
||||
for (z_index, tiles) in tiles_by_z.iter() {
|
||||
if tiles.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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();
|
||||
// Snap min/max values to tile grid - accounting for half tile on each edge
|
||||
// We need to extend the boundaries by half a tile in each direction to ensure full coverage
|
||||
// Don't ask, dragons.
|
||||
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;
|
||||
|
||||
// Snap min/max values to tile grid - ensure we're centered on tile centers
|
||||
// Our tiles are positioned at their centers (not corners)
|
||||
let min_x_aligned = (min_x / TILE_SIZE).floor() * TILE_SIZE;
|
||||
let min_y_aligned = (min_y / TILE_SIZE).floor() * TILE_SIZE;
|
||||
let max_x_aligned = (max_x / TILE_SIZE).ceil() * TILE_SIZE;
|
||||
let max_y_aligned = (max_y / TILE_SIZE).ceil() * TILE_SIZE;
|
||||
|
||||
// Calculate texture dimensions in pixels
|
||||
// We use TILE_PIXELS (16.0) as the base unit for the texture 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 as u32;
|
||||
let height_px = height_tiles * TILE_PIXELS as u32;
|
||||
let width_px = width_tiles * TILE_PIXELS;
|
||||
let height_px = height_tiles * TILE_PIXELS;
|
||||
|
||||
// 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();
|
||||
let texture = textures
|
||||
.handles
|
||||
.get(texture_ids.refs.get(&floortile.id).unwrap())
|
||||
.unwrap();
|
||||
|
||||
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![];
|
||||
|
||||
// Fetch the source texture data
|
||||
if let Some(source_texture) = images.get(texture) {
|
||||
// Calculate position within the quilted texture
|
||||
// We need to be precise with the mapping from world coordinates to texture pixels
|
||||
|
||||
// Calculate tile position relative to the quilted texture origin
|
||||
// This gives us the tile index (whole number of tiles from origin)
|
||||
let tile_x = ((pos.x - min_x_aligned) / TILE_SIZE).round() as u32;
|
||||
let tile_y =
|
||||
(height_tiles as f32 - 1.0 - ((pos.y - min_y_aligned) / TILE_SIZE).round())
|
||||
as u32;
|
||||
// Copy the tile texture into the quilted texture
|
||||
// Convert from tile indices to pixel coordinates in the output 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 * TILE_PIXELS as u32) + (TILE_PIXELS / 2.0) as u32,
|
||||
(tile_y * TILE_PIXELS as u32) + (TILE_PIXELS / 2.0) as u32,
|
||||
);
|
||||
if let Some(_data) = &source_texture.data {
|
||||
// replace this one texture push with loop
|
||||
// if the texture contains any transparent or semi-transparent pixels we should also add the tile below it (z-1)
|
||||
data.push(_data);
|
||||
}
|
||||
blit_texture(
|
||||
data, // tile
|
||||
&mut texture_data, // terrain
|
||||
source_texture.size().x as u32,
|
||||
source_texture.size().y as u32,
|
||||
width_px,
|
||||
height_px,
|
||||
target_x,
|
||||
target_y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new image asset
|
||||
let quilted_texture = Image::new_fill(
|
||||
render_resource::Extent3d {
|
||||
width: width_px,
|
||||
@@ -237,30 +237,28 @@ pub fn build_quilted_terrain_sprites(
|
||||
render_resource::TextureFormat::Rgba8UnormSrgb,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
);
|
||||
|
||||
let texture_handle = images.add(quilted_texture);
|
||||
|
||||
// Calculate the center position of the quilted sprite
|
||||
// Using the aligned coordinates to ensure proper centering
|
||||
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;
|
||||
|
||||
// Spawn the quilted sprite
|
||||
commands.spawn((
|
||||
Sprite {
|
||||
image: texture_handle,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_xyz(center_x, center_y, (*z_index as f32 - Z_BELOW) * TILE_SIZE)
|
||||
.with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||
Transform::from_xyz(
|
||||
center_x + TILE_SIZE / 2.0,
|
||||
center_y + TILE_SIZE / 2.0,
|
||||
(*z_index as f32 - Z_BELOW) * TILE_SIZE,
|
||||
)
|
||||
.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?}",
|
||||
@@ -270,7 +268,7 @@ pub fn build_quilted_terrain_sprites(
|
||||
|
||||
// Helper function to blit a texture onto another texture
|
||||
fn blit_texture(
|
||||
source: &[u8],
|
||||
sources: Vec<&[u8]>,
|
||||
target: &mut [u8],
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
@@ -283,22 +281,31 @@ fn blit_texture(
|
||||
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
|
||||
// New colour
|
||||
let mut r: u64 = 0;
|
||||
let mut g: u64 = 0;
|
||||
let mut b: u64 = 0;
|
||||
|
||||
for source in sources.iter() {
|
||||
r += source[source_idx] as u64;
|
||||
g += source[source_idx + 1] as u64;
|
||||
b += source[source_idx + 2] as u64;
|
||||
}
|
||||
r /= sources.len() as u64;
|
||||
g /= sources.len() as u64;
|
||||
b /= sources.len() as u64;
|
||||
|
||||
target[target_idx] = r as u8;
|
||||
target[target_idx + 1] = g as u8;
|
||||
target[target_idx + 2] = b as u8;
|
||||
target[target_idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user