Skip to content

Commit

Permalink
fix!: create IntGrid white-image on asset load and minimize its size (#…
Browse files Browse the repository at this point in the history
…183)

Closes #172, and resolves an issue brought up by @Mattincho in #176 

# Summary
The white image generated for levels is now only used for intgrid
coloring. This means it no longer needs to be the size of the level - it
can just be the size of the maximum-sized intgrid layer. The
maximum-sized intgrid tile is easy to determine during asset loading.
This PR creates a small white-image for the maximum-sized intgrid tile
once for the entire asset, rather than a large one once per level. This
should improve performance, and also hopefully resolve issues with
hitting hardware limits on larger levels.

BREAKING CHANGE: Most likely won't affect users - `LdtkAsset` has gained
a `int_grid_image_handle` field, breaking any manual construction of it.
  • Loading branch information
Trouv authored Apr 24, 2023
1 parent a8dba24 commit 23fd924
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 19 deletions.
146 changes: 146 additions & 0 deletions src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use bevy::{
asset::{AssetLoader, AssetPath, LoadContext, LoadedAsset},
prelude::*,
reflect::TypeUuid,
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
utils::BoxedFuture,
};
use std::{collections::HashMap, path::Path};
Expand Down Expand Up @@ -32,6 +33,36 @@ pub struct LdtkAsset {
pub project: ldtk::LdtkJson,
pub tileset_map: TilesetMap,
pub level_map: LevelMap,
/// Image used for rendering int grid colors.
pub int_grid_image_handle: Option<Handle<Image>>,
}

impl ldtk::Definitions {
/// Creates image that will be used for rendering IntGrid colors.
///
/// The resulting image is completely white and can be thought of as a single tile of the grid.
/// The image is only as big as the biggest int grid layer's grid size.
///
/// Can return `None` if there are no IntGrid layers that will be rendered by color.
/// IntGrid layers that have a tileset are excluded since they will not be rendered by color.
fn create_int_grid_image(&self) -> Option<Image> {
self.layers
.iter()
.filter(|l| l.purple_type == ldtk::Type::IntGrid && l.tileset_def_uid.is_none())
.max_by(|layer_a, layer_b| layer_a.grid_size.cmp(&layer_b.grid_size))
.map(|l| {
Image::new_fill(
Extent3d {
width: l.grid_size as u32,
height: l.grid_size as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&[255, 255, 255, 255],
TextureFormat::Rgba8UnormSrgb,
)
})
}
}

impl ldtk::LdtkJson {
Expand Down Expand Up @@ -141,10 +172,15 @@ impl AssetLoader for LdtkLoader {
}
}

let int_grid_image_handle = project.defs.create_int_grid_image().map(|image| {
load_context.set_labeled_asset("int_grid_image", LoadedAsset::new(image))
});

let ldtk_asset = LdtkAsset {
project,
tileset_map,
level_map,
int_grid_image_handle,
};
load_context.set_default_asset(
LoadedAsset::new(ldtk_asset)
Expand Down Expand Up @@ -215,3 +251,113 @@ impl AssetLoader for LdtkLevelLoader {
&["ldtkl"]
}
}

#[cfg(test)]
mod tests {
use crate::ldtk::{Definitions, LayerDefinition, Type};

use super::*;

#[test]
fn int_grid_image_is_white() {
let definitions = Definitions {
layers: vec![LayerDefinition {
purple_type: Type::IntGrid,
grid_size: 16,
..default()
}],
..default()
};

let image = definitions.create_int_grid_image().unwrap();

for byte in image.data.iter() {
assert_eq!(*byte, 255);
}
}

#[test]
fn int_grid_image_is_size_of_max_int_grid_layer() {
let definitions = Definitions {
layers: vec![
LayerDefinition {
purple_type: Type::IntGrid,
grid_size: 16,
..default()
},
LayerDefinition {
purple_type: Type::IntGrid,
grid_size: 32,
..default()
},
LayerDefinition {
purple_type: Type::IntGrid,
grid_size: 2,
..default()
},
// Excludes non-intgrid layers
LayerDefinition {
purple_type: Type::AutoLayer,
grid_size: 64,
..default()
},
LayerDefinition {
purple_type: Type::Tiles,
grid_size: 64,
..default()
},
LayerDefinition {
purple_type: Type::Entities,
grid_size: 64,
..default()
},
// Excludes intgrid layers w/ tileset
LayerDefinition {
purple_type: Type::IntGrid,
grid_size: 64,
tileset_def_uid: Some(1),
..default()
},
],
..default()
};

let image = definitions.create_int_grid_image().unwrap();

assert_eq!(image.size(), Vec2::splat(32.));
}

#[test]
fn no_int_grid_image_for_no_elligible_int_grid_layers() {
let definitions = Definitions {
layers: vec![
// Excludes non-intgrid layers
LayerDefinition {
purple_type: Type::AutoLayer,
grid_size: 64,
..default()
},
LayerDefinition {
purple_type: Type::Tiles,
grid_size: 64,
..default()
},
LayerDefinition {
purple_type: Type::Entities,
grid_size: 64,
..default()
},
// Excludes intgrid layers w/ tileset
LayerDefinition {
purple_type: Type::IntGrid,
grid_size: 64,
tileset_def_uid: Some(1),
..default()
},
],
..default()
};

assert!(definitions.create_int_grid_image().is_none());
}
}
2 changes: 1 addition & 1 deletion src/ldtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ pub struct EnumValueDefinition {
pub tile_id: Option<i32>,
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[derive(PartialEq, Debug, Default, Clone, Serialize, Deserialize)]
pub struct LayerDefinition {
/// Type of the layer (*IntGrid, Entities, Tiles or AutoLayer*)
#[serde(rename = "__type")]
Expand Down
27 changes: 9 additions & 18 deletions src/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
utils::*,
};

use bevy::{prelude::*, render::render_resource::*};
use bevy::prelude::*;
use bevy_ecs_tilemap::{
map::{
TilemapGridSize, TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTileSize,
Expand Down Expand Up @@ -212,6 +212,7 @@ pub fn spawn_level(
layer_definition_map: &HashMap<i32, &LayerDefinition>,
tileset_map: &TilesetMap,
tileset_definition_map: &HashMap<i32, &TilesetDefinition>,
int_grid_image_handle: &Option<Handle<Image>>,
worldly_set: HashSet<Worldly>,
ldtk_entity: Entity,
ldtk_settings: &LdtkSettings,
Expand All @@ -221,20 +222,6 @@ pub fn spawn_level(
if let Some(layer_instances) = &level.layer_instances {
let mut layer_z = 0;

// creating an image to use for intgrid colors
let white_image = Image::new_fill(
Extent3d {
width: level.px_wid as u32,
height: level.px_hei as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&[255, 255, 255, 255],
TextureFormat::Rgba8UnormSrgb,
);

let white_image_handle = images.add(white_image);

if ldtk_settings.level_background == LevelBackground::Rendered {
let translation = Vec3::new(level.px_wid as f32, level.px_hei as f32, 0.) / 2.;

Expand Down Expand Up @@ -402,11 +389,15 @@ pub fn spawn_level(
_ => TilemapSpacing::default(),
};

let texture = match tileset_definition {
Some(tileset_definition) => TilemapTexture::Single(
let texture = match (tileset_definition, int_grid_image_handle) {
(Some(tileset_definition), _) => TilemapTexture::Single(
tileset_map.get(&tileset_definition.uid).unwrap().clone(),
),
None => TilemapTexture::Single(white_image_handle.clone()),
(None, Some(handle)) => TilemapTexture::Single(handle.clone()),
_ => {
warn!("unable to render tilemap layer, it has no tileset and no intgrid layers were expected");
continue;
}
};

let metadata_map: HashMap<i32, TileMetadata> = tileset_definition
Expand Down
3 changes: 3 additions & 0 deletions src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ pub fn process_ldtk_levels(
let layer_definition_map =
create_layer_definition_map(&ldtk_asset.project.defs.layers);

let int_grid_image_handle = &ldtk_asset.int_grid_image_handle;

let worldly_set = worldly_query.iter().cloned().collect();

if let Some(level) = level_assets.get(level_handle) {
Expand All @@ -282,6 +284,7 @@ pub fn process_ldtk_levels(
&layer_definition_map,
&ldtk_asset.tileset_map,
&tileset_definition_map,
int_grid_image_handle,
worldly_set,
ldtk_entity,
&ldtk_settings,
Expand Down

0 comments on commit 23fd924

Please sign in to comment.