#[allow(unused_imports)]
use crate::{
app::LdtkEntity,
components::{GridCoords, IntGridCell},
};
use crate::{components::TileGridBundle, ldtk::*};
use bevy::prelude::*;
use bevy_ecs_tilemap::{
map::{TilemapId, TilemapSize},
tiles::{TilePos, TileStorage},
};
use std::{collections::HashMap, hash::Hash};
pub fn int_grid_index_to_grid_coords(
index: usize,
layer_width_in_tiles: u32,
layer_height_in_tiles: u32,
) -> Option<GridCoords> {
if layer_width_in_tiles * layer_height_in_tiles == 0 {
return None;
}
let tile_x = index as u32 % layer_width_in_tiles;
let inverted_y = (index as u32 - tile_x) / layer_width_in_tiles;
if layer_height_in_tiles > inverted_y {
Some(ldtk_grid_coords_to_grid_coords(
IVec2::new(tile_x as i32, inverted_y as i32),
layer_height_in_tiles as i32,
))
} else {
None
}
}
pub fn create_entity_definition_map(
entity_definitions: &[EntityDefinition],
) -> HashMap<i32, &EntityDefinition> {
entity_definitions.iter().map(|e| (e.uid, e)).collect()
}
pub fn create_layer_definition_map(
layer_definitions: &[LayerDefinition],
) -> HashMap<i32, &LayerDefinition> {
layer_definitions.iter().map(|l| (l.uid, l)).collect()
}
pub fn calculate_transform_from_entity_instance(
entity_instance: &EntityInstance,
entity_definition_map: &HashMap<i32, &EntityDefinition>,
level_height: i32,
z_value: f32,
) -> Transform {
let entity_definition = entity_definition_map.get(&entity_instance.def_uid).unwrap();
let def_size = match &entity_instance.tile {
Some(tile) => IVec2::new(tile.w, tile.h),
None => IVec2::new(entity_definition.width, entity_definition.height),
};
let size = IVec2::new(entity_instance.width, entity_instance.height);
let translation = ldtk_pixel_coords_to_translation_pivoted(
entity_instance.px,
level_height as i32,
size,
entity_instance.pivot,
);
let scale = size.as_vec2() / def_size.as_vec2();
Transform::from_translation(translation.extend(z_value)).with_scale(scale.extend(1.))
}
fn ldtk_coord_conversion(coords: IVec2, height: i32) -> IVec2 {
IVec2::new(coords.x, height - coords.y)
}
fn ldtk_coord_conversion_origin_adjusted(coords: IVec2, height: i32) -> IVec2 {
IVec2::new(coords.x, height - coords.y - 1)
}
pub fn ldtk_pixel_coords_to_translation(ldtk_coords: IVec2, ldtk_pixel_height: i32) -> Vec2 {
ldtk_coord_conversion(ldtk_coords, ldtk_pixel_height).as_vec2()
}
pub fn translation_to_ldtk_pixel_coords(translation: Vec2, ldtk_pixel_height: i32) -> IVec2 {
ldtk_coord_conversion(translation.as_ivec2(), ldtk_pixel_height)
}
pub fn ldtk_grid_coords_to_grid_coords(ldtk_coords: IVec2, ldtk_grid_height: i32) -> GridCoords {
ldtk_coord_conversion_origin_adjusted(ldtk_coords, ldtk_grid_height).into()
}
pub fn grid_coords_to_ldtk_grid_coords(grid_coords: GridCoords, ldtk_grid_height: i32) -> IVec2 {
ldtk_coord_conversion_origin_adjusted(grid_coords.into(), ldtk_grid_height)
}
pub fn translation_to_grid_coords(translation: Vec2, grid_size: IVec2) -> GridCoords {
(translation / grid_size.as_vec2()).as_ivec2().into()
}
pub fn grid_coords_to_translation_relative_to_tile_layer(
grid_coords: GridCoords,
tile_size: IVec2,
) -> Vec2 {
let tile_coords: IVec2 = grid_coords.into();
let tile_size = tile_size.as_vec2();
tile_size * tile_coords.as_vec2()
}
pub fn grid_coords_to_translation(grid_coords: GridCoords, tile_size: IVec2) -> Vec2 {
grid_coords_to_translation_relative_to_tile_layer(grid_coords, tile_size)
+ (tile_size.as_vec2() / 2.)
}
pub fn ldtk_pixel_coords_to_grid_coords(
ldtk_coords: IVec2,
ldtk_grid_height: i32,
grid_size: IVec2,
) -> GridCoords {
ldtk_grid_coords_to_grid_coords(ldtk_coords / grid_size, ldtk_grid_height)
}
pub fn ldtk_grid_coords_to_translation_relative_to_tile_layer(
ldtk_coords: IVec2,
ldtk_grid_height: i32,
grid_size: IVec2,
) -> Vec2 {
ldtk_pixel_coords_to_translation(ldtk_coords * grid_size, ldtk_grid_height * grid_size.y)
+ Vec2::new(0., -grid_size.y as f32)
}
pub fn ldtk_grid_coords_to_translation(
ldtk_coords: IVec2,
ldtk_grid_height: i32,
grid_size: IVec2,
) -> Vec2 {
ldtk_grid_coords_to_translation_relative_to_tile_layer(ldtk_coords, ldtk_grid_height, grid_size)
+ (grid_size.as_vec2() / 2.)
}
pub fn ldtk_pixel_coords_to_translation_pivoted(
ldtk_coords: IVec2,
ldtk_pixel_height: i32,
entity_size: IVec2,
pivot: Vec2,
) -> Vec2 {
let pivot_point = ldtk_coord_conversion(ldtk_coords, ldtk_pixel_height).as_vec2();
let adjusted_pivot = Vec2::new(0.5 - pivot.x, pivot.y - 0.5);
let offset = entity_size.as_vec2() * adjusted_pivot;
pivot_point + offset
}
pub(crate) fn set_all_tiles_with_func(
commands: &mut Commands,
storage: &mut TileStorage,
size: TilemapSize,
tilemap_id: TilemapId,
mut func: impl FnMut(TilePos) -> Option<TileGridBundle>,
) {
for x in 0..size.x {
for y in 0..size.y {
let tile_pos = TilePos { x, y };
let tile_entity = func(tile_pos)
.map(|tile_bundle| commands.spawn(tile_bundle).insert(tilemap_id).id());
match tile_entity {
Some(tile_entity) => storage.set(&tile_pos, tile_entity),
None => storage.remove(&tile_pos),
}
}
}
}
pub(crate) fn try_each_optional_permutation<A, B, R>(
a: A,
b: B,
mut func: impl FnMut(Option<A>, Option<B>) -> Option<R>,
) -> Option<R>
where
A: Clone,
B: Clone,
{
func(Some(a.clone()), Some(b.clone()))
.or_else(|| func(None, Some(b)))
.or_else(|| func(Some(a), None))
.or_else(|| func(None, None))
}
pub(crate) fn ldtk_map_get_or_default<'a, A, B, L>(
a: A,
b: B,
default: &'a L,
map: &'a HashMap<(Option<A>, Option<B>), L>,
) -> &'a L
where
A: Hash + Eq + Clone,
B: Hash + Eq + Clone,
{
try_each_optional_permutation(a, b, |x, y| map.get(&(x, y))).unwrap_or(default)
}
pub fn sprite_sheet_bundle_from_entity_info(
entity_instance: &EntityInstance,
tileset: Option<&Handle<Image>>,
tileset_definition: Option<&TilesetDefinition>,
texture_atlases: &mut Assets<TextureAtlas>,
) -> SpriteSheetBundle {
match (tileset, &entity_instance.tile, tileset_definition) {
(Some(tileset), Some(tile), Some(tileset_definition)) => SpriteSheetBundle {
texture_atlas: texture_atlases.add(TextureAtlas::from_grid(
tileset.clone(),
Vec2::new(tile.w as f32, tile.h as f32),
tileset_definition.c_wid as usize,
tileset_definition.c_hei as usize,
Some(Vec2::splat(tileset_definition.spacing as f32)),
Some(Vec2::splat(tileset_definition.padding as f32)),
)),
sprite: TextureAtlasSprite {
index: (tile.y / (tile.h + tileset_definition.spacing)) as usize
* tileset_definition.c_wid as usize
+ (tile.x / (tile.w + tileset_definition.spacing)) as usize,
..Default::default()
},
..Default::default()
},
_ => {
warn!("EntityInstance needs a tile, an associated tileset, and an associated tileset definition to be bundled as a SpriteSheetBundle");
SpriteSheetBundle::default()
}
}
}
pub fn sprite_bundle_from_entity_info(tileset: Option<&Handle<Image>>) -> SpriteBundle {
let tileset = match tileset {
Some(tileset) => tileset.clone(),
None => {
warn!("EntityInstance needs a tileset to be bundled as a SpriteBundle");
return SpriteBundle::default();
}
};
SpriteBundle {
texture: tileset,
..Default::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int_grid_index_to_tile_pos() {
assert_eq!(
int_grid_index_to_grid_coords(3, 4, 5),
Some(GridCoords::new(3, 4))
);
assert_eq!(
int_grid_index_to_grid_coords(10, 5, 5),
Some(GridCoords::new(0, 2))
);
assert_eq!(
int_grid_index_to_grid_coords(49, 10, 5),
Some(GridCoords::new(9, 0))
);
assert_eq!(
int_grid_index_to_grid_coords(64, 100, 1),
Some(GridCoords::new(64, 0))
);
assert_eq!(
int_grid_index_to_grid_coords(35, 1, 100),
Some(GridCoords::new(0, 64))
);
}
#[test]
fn test_int_grid_index_out_of_range() {
assert_eq!(int_grid_index_to_grid_coords(3, 0, 5), None);
assert_eq!(int_grid_index_to_grid_coords(3, 5, 0), None);
assert_eq!(int_grid_index_to_grid_coords(25, 5, 5), None);
}
#[test]
fn test_calculate_transform_from_entity_instance() {
let entity_definitions = vec![
EntityDefinition {
uid: 0,
width: 32,
height: 32,
..Default::default()
},
EntityDefinition {
uid: 1,
width: 64,
height: 16,
..Default::default()
},
EntityDefinition {
uid: 2,
width: 10,
height: 25,
..Default::default()
},
];
let entity_definition_map = create_entity_definition_map(&entity_definitions);
let entity_instance = EntityInstance {
px: IVec2::new(256, 256),
def_uid: 0,
width: 32,
height: 32,
pivot: Vec2::new(0., 0.),
..Default::default()
};
let result = calculate_transform_from_entity_instance(
&entity_instance,
&entity_definition_map,
320,
0.,
);
assert_eq!(result, Transform::from_xyz(272., 48., 0.));
let entity_instance = EntityInstance {
px: IVec2::new(40, 50),
def_uid: 2,
width: 30,
height: 50,
pivot: Vec2::new(1., 1.),
..Default::default()
};
let result = calculate_transform_from_entity_instance(
&entity_instance,
&entity_definition_map,
100,
2.,
);
assert_eq!(
result,
Transform::from_xyz(25., 75., 2.).with_scale(Vec3::new(3., 2., 1.))
);
}
#[test]
fn test_calculate_transform_from_entity_instance_with_tile() {
let entity_definitions = vec![EntityDefinition {
uid: 0,
width: 32,
height: 32,
..Default::default()
}];
let entity_definition_map = create_entity_definition_map(&entity_definitions);
let entity_instance = EntityInstance {
px: IVec2::new(64, 64),
def_uid: 0,
width: 64,
height: 64,
pivot: Vec2::new(1., 1.),
tile: Some(TilesetRectangle {
x: 0,
y: 0,
w: 16,
h: 32,
..Default::default()
}),
..Default::default()
};
let result = calculate_transform_from_entity_instance(
&entity_instance,
&entity_definition_map,
100,
2.,
);
assert_eq!(
result,
Transform::from_xyz(32., 68., 2.).with_scale(Vec3::new(4., 2., 1.))
);
}
#[test]
fn test_translation_ldtk_pixel_coords_conversion() {
assert_eq!(
ldtk_pixel_coords_to_translation(IVec2::new(32, 64), 128),
Vec2::new(32., 64.)
);
assert_eq!(
ldtk_pixel_coords_to_translation(IVec2::new(0, 0), 100),
Vec2::new(0., 100.)
);
assert_eq!(
translation_to_ldtk_pixel_coords(Vec2::new(32., 64.), 128),
IVec2::new(32, 64)
);
assert_eq!(
translation_to_ldtk_pixel_coords(Vec2::new(0., 0.), 100),
IVec2::new(0, 100)
);
}
#[test]
fn test_ldtk_grid_coords_to_translation_relative_to_tile_layer() {
assert_eq!(
ldtk_grid_coords_to_translation_relative_to_tile_layer(
IVec2::new(1, 1),
4,
IVec2::splat(32)
),
Vec2::new(32., 64.)
);
assert_eq!(
ldtk_grid_coords_to_translation_relative_to_tile_layer(
IVec2::new(1, 1),
2,
IVec2::splat(100)
),
Vec2::new(100., 0.)
);
assert_eq!(
ldtk_grid_coords_to_translation_relative_to_tile_layer(
IVec2::new(0, 4),
10,
IVec2::splat(1)
),
Vec2::new(0., 5.)
);
}
#[test]
fn test_ldtk_grid_coords_to_translation() {
assert_eq!(
ldtk_grid_coords_to_translation(IVec2::new(1, 1), 4, IVec2::splat(32)),
Vec2::new(48., 80.)
);
assert_eq!(
ldtk_grid_coords_to_translation(IVec2::new(1, 1), 2, IVec2::splat(100)),
Vec2::new(150., 50.)
);
assert_eq!(
ldtk_grid_coords_to_translation(IVec2::new(0, 4), 10, IVec2::splat(1)),
Vec2::new(0.5, 5.5)
);
}
#[test]
fn test_grid_coords_to_translation_relative_to_tile_layer() {
assert_eq!(
grid_coords_to_translation_relative_to_tile_layer(
GridCoords::new(1, 2),
IVec2::splat(32)
),
Vec2::new(32., 64.)
);
assert_eq!(
grid_coords_to_translation_relative_to_tile_layer(
GridCoords::new(1, 0),
IVec2::splat(100)
),
Vec2::new(100., 0.)
);
assert_eq!(
grid_coords_to_translation_relative_to_tile_layer(
GridCoords::new(0, 5),
IVec2::splat(1)
),
Vec2::new(0.0, 5.0)
);
}
#[test]
fn test_grid_coords_to_translation() {
assert_eq!(
grid_coords_to_translation(GridCoords::new(1, 2), IVec2::splat(32)),
Vec2::new(48., 80.)
);
assert_eq!(
grid_coords_to_translation(GridCoords::new(1, 0), IVec2::splat(100)),
Vec2::new(150., 50.)
);
assert_eq!(
grid_coords_to_translation(GridCoords::new(0, 5), IVec2::splat(1)),
Vec2::new(0.5, 5.5)
);
}
#[test]
fn test_ldtk_pixel_coords_to_translation_pivoted() {
assert_eq!(
ldtk_pixel_coords_to_translation_pivoted(
IVec2::new(32, 64),
128,
IVec2::splat(32),
Vec2::ZERO
),
Vec2::new(48., 48.),
);
assert_eq!(
ldtk_pixel_coords_to_translation_pivoted(
IVec2::new(0, 0),
10,
IVec2::splat(1),
Vec2::new(1., 0.)
),
Vec2::new(-0.5, 9.5),
);
assert_eq!(
ldtk_pixel_coords_to_translation_pivoted(
IVec2::new(20, 20),
20,
IVec2::splat(5),
Vec2::new(0.5, 0.5)
),
Vec2::new(20., 0.),
);
}
#[test]
fn test_try_each_optional_permutation() {
fn test_func(a: Option<i32>, b: Option<i32>) -> Option<i32> {
match (a, b) {
(Some(a), Some(_)) if a == 1 => Some(1),
(Some(_), Some(_)) => None,
(Some(a), None) if a == 2 => Some(2),
(Some(_), None) => None,
(None, Some(b)) if b == 3 => Some(3),
(None, Some(_)) => None,
(None, None) => Some(4),
}
}
assert_eq!(try_each_optional_permutation(1, 1, test_func), Some(1));
assert_eq!(try_each_optional_permutation(2, 1, test_func), Some(2));
assert_eq!(try_each_optional_permutation(2, 2, test_func), Some(2));
assert_eq!(try_each_optional_permutation(2, 3, test_func), Some(3));
assert_eq!(try_each_optional_permutation(3, 3, test_func), Some(3));
assert_eq!(try_each_optional_permutation(4, 3, test_func), Some(3));
assert_eq!(try_each_optional_permutation(4, 4, test_func), Some(4));
assert_eq!(try_each_optional_permutation(5, 5, test_func), Some(4));
}
}