From c24a2efa76333b0366b147d5c7e68d3af4d80a42 Mon Sep 17 00:00:00 2001 From: Alex Bentley Date: Sat, 21 Jun 2025 01:03:44 -0400 Subject: [PATCH 1/5] Wrote test suite; ran rustfmt --- Cargo.toml | 3 + src/anchor.rs | 62 +++++- src/array_texture_preload.rs | 37 ++-- src/helpers/filling.rs | 86 ++++++-- src/helpers/geometry.rs | 12 +- src/helpers/hex_grid/axial.rs | 125 ++++++++--- src/helpers/hex_grid/cube.rs | 49 ++++- src/helpers/hex_grid/neighbors.rs | 83 ++++--- src/helpers/hex_grid/offset.rs | 69 +++++- src/helpers/projection.rs | 133 ++++++++---- src/helpers/square_grid/diamond.rs | 46 +++- src/helpers/square_grid/mod.rs | 97 ++++++++- src/helpers/square_grid/neighbors.rs | 130 ++++++++--- src/helpers/square_grid/staggered.rs | 54 ++++- src/helpers/transform.rs | 69 +++++- src/lib.rs | 51 ++++- src/map.rs | 150 ++++++++----- src/render/chunk.rs | 95 ++++---- src/render/draw.rs | 80 +++---- src/render/extract.rs | 256 ++++++++++++---------- src/render/material.rs | 214 +++++++++--------- src/render/mod.rs | 151 ++++++------- src/render/pipeline.rs | 142 +++++++----- src/render/prepare.rs | 66 +++--- src/render/queue.rs | 21 +- src/render/texture_array_cache.rs | 264 +++++++++++------------ src/tiles/mod.rs | 47 +++- src/tiles/storage.rs | 97 +++++++++ tests/anchor.rs | 44 ++++ tests/helpers__filling.rs | 38 ++++ tests/helpers__hex_grid__axial.rs | 48 +++++ tests/helpers__hex_grid__cube.rs | 10 + tests/helpers__hex_grid__neighbors.rs | 30 +++ tests/helpers__projection.rs | 66 ++++++ tests/helpers__square_grid__diamond.rs | 27 +++ tests/helpers__square_grid__mod.rs | 27 +++ tests/helpers__square_grid__neighbors.rs | 98 +++++++++ tests/helpers__square_grid__staggered.rs | 39 ++++ tests/map1.rs | 29 +++ tests/map2.rs | 55 +++++ tests/tiles__mod.rs | 19 ++ tests/tiles__storage.rs | 37 ++++ 42 files changed, 2344 insertions(+), 912 deletions(-) create mode 100644 tests/anchor.rs create mode 100644 tests/helpers__filling.rs create mode 100644 tests/helpers__hex_grid__axial.rs create mode 100644 tests/helpers__hex_grid__cube.rs create mode 100644 tests/helpers__hex_grid__neighbors.rs create mode 100644 tests/helpers__projection.rs create mode 100644 tests/helpers__square_grid__diamond.rs create mode 100644 tests/helpers__square_grid__mod.rs create mode 100644 tests/helpers__square_grid__neighbors.rs create mode 100644 tests/helpers__square_grid__staggered.rs create mode 100644 tests/map1.rs create mode 100644 tests/map2.rs create mode 100644 tests/tiles__mod.rs create mode 100644 tests/tiles__storage.rs diff --git a/Cargo.toml b/Cargo.toml index 62f95c30..b9b799d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,9 @@ rand = "0.8" serde_json = { version = "1.0" } tiled = { version = "0.14.0", default-features = false } thiserror = { version = "2.0" } +approx = { version = "0.5" } +proptest = "1.7" +glam = "0.30" [dev-dependencies.bevy] version = "0.16.0" diff --git a/src/anchor.rs b/src/anchor.rs index 65fef7a4..c343dcc6 100644 --- a/src/anchor.rs +++ b/src/anchor.rs @@ -1,4 +1,4 @@ -use crate::{TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType, prelude::chunk_aabb}; +use crate::{ TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType, prelude::chunk_aabb }; use bevy::prelude::*; /// How a tilemap is positioned relative to its [`Transform`]. It defaults to @@ -45,13 +45,13 @@ impl TilemapAnchor { map_size: &TilemapSize, grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, - map_type: &TilemapType, + map_type: &TilemapType ) -> Vec2 { let aabb = chunk_aabb( UVec2::new(map_size.x - 1, map_size.y - 1), grid_size, tile_size, - map_type, + map_type ); let min = aabb.min(); let max = aabb.max(); @@ -66,10 +66,58 @@ impl TilemapAnchor { TilemapAnchor::BottomRight => Vec2::new(-max.x, -min.y), TilemapAnchor::BottomCenter => Vec2::new(-(max.x + min.x) / 2.0, -min.y), TilemapAnchor::Center => Vec2::new(-(max.x + min.x) / 2.0, -(max.y + min.y) / 2.0), - TilemapAnchor::Custom(v) => Vec2::new( - (-0.5 - v.x) * (max.x - min.x) - min.x, - (-0.5 - v.y) * (max.y - min.y) - min.y, - ), + TilemapAnchor::Custom(v) => + Vec2::new( + (-0.5 - v.x) * (max.x - min.x) - min.x, + (-0.5 - v.y) * (max.y - min.y) - min.y + ), } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn fixed_params() -> (TilemapSize, TilemapGridSize, TilemapTileSize, TilemapType) { + ( + TilemapSize { x: 4, y: 3 }, + TilemapGridSize { x: 1.0, y: 1.0 }, + TilemapTileSize { x: 1.0, y: 1.0 }, + TilemapType::Square, + ) + } + + #[test] + fn none_anchor_is_zero() { + let (map, grid, tile, ty) = fixed_params(); + assert_eq!(TilemapAnchor::None.as_offset(&map, &grid, &tile, &ty), Vec2::ZERO); + } + + #[test] + fn center_equals_custom_zero() { + let (map, grid, tile, ty) = fixed_params(); + assert_eq!( + TilemapAnchor::Center.as_offset(&map, &grid, &tile, &ty), + TilemapAnchor::Custom(Vec2::ZERO).as_offset(&map, &grid, &tile, &ty) + ); + } + + #[test] + fn top_left_equals_custom_top_left() { + let (map, grid, tile, ty) = fixed_params(); + assert_eq!( + TilemapAnchor::TopLeft.as_offset(&map, &grid, &tile, &ty), + TilemapAnchor::Custom(Vec2::new(-0.5, 0.5)).as_offset(&map, &grid, &tile, &ty) + ); + } + + #[test] + fn top_right_equals_custom_top_right() { + let (map, grid, tile, ty) = fixed_params(); + assert_eq!( + TilemapAnchor::TopRight.as_offset(&map, &grid, &tile, &ty), + TilemapAnchor::Custom(Vec2::new(0.5, 0.5)).as_offset(&map, &grid, &tile, &ty) + ); + } +} diff --git a/src/array_texture_preload.rs b/src/array_texture_preload.rs index ad0011ae..f6a680e1 100644 --- a/src/array_texture_preload.rs +++ b/src/array_texture_preload.rs @@ -1,15 +1,12 @@ -use crate::render::{DefaultSampler, TextureArrayCache}; -use crate::{ - TilemapTexture, - prelude::{TilemapSpacing, TilemapTileSize}, -}; -use bevy::render::render_resource::{FilterMode, TextureFormat}; +use crate::render::{ DefaultSampler, TextureArrayCache }; +use crate::{ TilemapTexture, prelude::{ TilemapSpacing, TilemapTileSize } }; +use bevy::render::render_resource::{ FilterMode, TextureFormat }; use bevy::{ image::BevyDefault, - prelude::{Assets, Image, Res, ResMut, Resource}, + prelude::{ Assets, Image, Res, ResMut, Resource }, render::Extract, }; -use std::sync::{Arc, RwLock}; +use std::sync::{ Arc, RwLock }; #[derive(Debug, Clone)] pub struct TilemapArrayTexture { @@ -60,13 +57,11 @@ pub(crate) fn extract( images: Extract>>, array_texture_loader: Extract>, mut texture_array_cache: ResMut, - default_image_settings: Res, + default_image_settings: Res ) { for mut array_texture in array_texture_loader.drain() { if array_texture.filter.is_none() { - array_texture - .filter - .replace(default_image_settings.mag_filter.into()); + array_texture.filter.replace(default_image_settings.mag_filter.into()); } if array_texture.texture.verify_ready(&images) { texture_array_cache.add_texture( @@ -75,7 +70,7 @@ pub(crate) fn extract( array_texture.tile_spacing, default_image_settings.min_filter.into(), array_texture.format, - &images, + &images ); } else { // Image hasn't loaded yet punt to next frame. @@ -83,3 +78,19 @@ pub(crate) fn extract( } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tilemap_array_texture_default_is_sane() { + // Act + let tex = TilemapArrayTexture::default(); + + assert!(tex.filter.is_none(), "Filter should start unset (None)"); + assert_eq!(tex.texture, TilemapTexture::default(), "Texture default mismatch"); + assert_eq!(tex.tile_size, TilemapTileSize::default(), "Tile size default mismatch"); + assert_eq!(tex.tile_spacing, TilemapSpacing::default(), "Tile spacing default mismatch"); + } +} diff --git a/src/helpers/filling.rs b/src/helpers/filling.rs index f2c59415..b5e2b86f 100644 --- a/src/helpers/filling.rs +++ b/src/helpers/filling.rs @@ -1,11 +1,11 @@ use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::neighbors::{HEX_DIRECTIONS, HexDirection}; +use crate::helpers::hex_grid::neighbors::{ HEX_DIRECTIONS, HexDirection }; use crate::map::TilemapId; use crate::prelude::HexCoordSystem; -use crate::tiles::{TileBundle, TileColor, TilePos, TileTextureIndex}; -use crate::{TileStorage, TilemapSize}; +use crate::tiles::{ TileBundle, TileColor, TilePos, TileTextureIndex }; +use crate::{ TileStorage, TilemapSize }; -use bevy::prelude::{Color, Commands}; +use bevy::prelude::{ Color, Commands }; /// Fills an entire tile storage with the given tile. pub fn fill_tilemap( @@ -13,7 +13,7 @@ pub fn fill_tilemap( size: TilemapSize, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage, + tile_storage: &mut TileStorage ) { commands.entity(tilemap_id.0).with_children(|parent| { for x in 0..size.x { @@ -36,14 +36,14 @@ pub fn fill_tilemap( /// Fills a rectangular region with the given tile. /// /// The rectangular region is defined by an `origin` in [`TilePos`], and a -/// `size` in tiles ([`TilemapSize`]). +/// `size` in tiles ([`TilemapSize`]). pub fn fill_tilemap_rect( texture_index: TileTextureIndex, origin: TilePos, size: TilemapSize, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage, + tile_storage: &mut TileStorage ) { commands.entity(tilemap_id.0).with_children(|parent| { for x in 0..size.x { @@ -70,7 +70,7 @@ pub fn fill_tilemap_rect( /// Fills a rectangular region with colored versions of the given tile. /// /// The rectangular region is defined by an `origin` in [`TilePos`], and a -/// `size` in tiles ([`TilemapSize`]). +/// `size` in tiles ([`TilemapSize`]). pub fn fill_tilemap_rect_color( texture_index: TileTextureIndex, origin: TilePos, @@ -78,7 +78,7 @@ pub fn fill_tilemap_rect_color( color: Color, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage, + tile_storage: &mut TileStorage ) { commands.entity(tilemap_id.0).with_children(|parent| { for x in 0..size.x { @@ -112,8 +112,7 @@ pub fn generate_hex_ring(origin: AxialPos, radius: u32) -> Vec { vec![origin] } else { let mut ring = Vec::with_capacity((radius * 6) as usize); - let corners = HEX_DIRECTIONS - .iter() + let corners = HEX_DIRECTIONS.iter() .map(|direction| origin + radius * AxialPos::from(direction)) .collect::>(); // The "tangent" is the direction we must travel in to reach the next corner @@ -134,8 +133,8 @@ pub fn generate_hex_ring(origin: AxialPos, radius: u32) -> Vec { /// Generates a vector of hex positions that form a hexagon of given `radius` around the specified /// `origin`. pub fn generate_hexagon(origin: AxialPos, radius: u32) -> Vec { - let mut hexagon = Vec::with_capacity(1 + (6 * radius * (radius + 1) / 2) as usize); - for r in 0..(radius + 1) { + let mut hexagon = Vec::with_capacity(1 + (((6 * radius * (radius + 1)) / 2) as usize)); + for r in 0..radius + 1 { hexagon.extend(generate_hex_ring(origin, r)); } hexagon @@ -154,15 +153,15 @@ pub fn fill_tilemap_hexagon( hex_coord_system: HexCoordSystem, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage, + tile_storage: &mut TileStorage ) { let tile_positions = generate_hexagon( AxialPos::from_tile_pos_given_coord_system(&origin, hex_coord_system), - radius, + radius ) - .into_iter() - .map(|axial_pos| axial_pos.as_tile_pos_given_coord_system(hex_coord_system)) - .collect::>(); + .into_iter() + .map(|axial_pos| axial_pos.as_tile_pos_given_coord_system(hex_coord_system)) + .collect::>(); commands.entity(tilemap_id.0).with_children(|parent| { for tile_pos in tile_positions { @@ -174,7 +173,56 @@ pub fn fill_tilemap_hexagon( ..Default::default() }) .id(); - tile_storage.checked_set(&tile_pos, tile_entity) + tile_storage.checked_set(&tile_pos, tile_entity); } }); } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + fn axial_distance(a: AxialPos, b: AxialPos) -> u32 { + let dq = (a.q - b.q).abs() as u32; + let dr = (a.r - b.r).abs() as u32; + let ds = (a.q + a.r - (b.q + b.r)).abs() as u32; + (dq + dr + ds) / 2 + } + + #[test] + fn ring_radius_zero_is_origin_only() { + let origin = AxialPos::new(0, 0); + let ring = generate_hex_ring(origin, 0); + assert_eq!(ring, vec![origin]); + } + + #[test] + fn ring_has_correct_length_and_radius() { + let origin = AxialPos::new(0, 0); + for r in 1..=4 { + let ring = generate_hex_ring(origin, r); + assert_eq!(ring.len() as u32, r * 6, "radius {r}"); + // no duplicates & all exactly r away + let uniq: HashSet<_> = ring.iter().cloned().collect(); + assert_eq!(uniq.len(), ring.len(), "radius {r} contains duplicates"); + assert!(ring.iter().all(|p| axial_distance(*p, origin) == r)); + } + } + + #[test] + fn hexagon_area_matches_formula_and_contains_rings() { + let origin = AxialPos::new(0, 0); + for r in 0..=4 { + let hex = generate_hexagon(origin, r); + let expected = 1 + 3 * r * (r + 1); + assert_eq!(hex.len() as u32, expected, "radius {r}"); + // make sure every inner ring element is inside the hexagon + for r_inner in 0..=r { + for p in generate_hex_ring(origin, r_inner) { + assert!(hex.contains(&p), "ring {r_inner} not fully inside hex {r}"); + } + } + } + } +} diff --git a/src/helpers/geometry.rs b/src/helpers/geometry.rs index 5cde1b05..be33a2ce 100644 --- a/src/helpers/geometry.rs +++ b/src/helpers/geometry.rs @@ -1,6 +1,8 @@ use crate::map::TilemapType; use crate::tiles::TilePos; -use crate::{TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, Transform}; +use crate::{ TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, Transform }; + +// Deprecated. Skipping tests. /// Calculates a [`Transform`] for a tilemap that places it so that its center is at /// `(0.0, 0.0, z)` in world space. @@ -9,7 +11,7 @@ pub fn get_tilemap_center_transform( map_size: &TilemapSize, grid_size: &TilemapGridSize, map_type: &TilemapType, - z: f32, + z: f32 ) -> Transform { let tile_size = TilemapTileSize::new(grid_size.x, grid_size.y); let low = TilePos::new(0, 0).center_in_world( @@ -17,17 +19,17 @@ pub fn get_tilemap_center_transform( grid_size, &tile_size, map_type, - &TilemapAnchor::None, + &TilemapAnchor::None ); let high = TilePos::new(map_size.x - 1, map_size.y - 1).center_in_world( map_size, grid_size, &tile_size, map_type, - &TilemapAnchor::None, + &TilemapAnchor::None ); let diff = high - low; - Transform::from_xyz(-diff.x / 2., -diff.y / 2., z) + Transform::from_xyz(-diff.x / 2.0, -diff.y / 2.0, z) } diff --git a/src/helpers/hex_grid/axial.rs b/src/helpers/hex_grid/axial.rs index 241e76da..3864a1fd 100644 --- a/src/helpers/hex_grid/axial.rs +++ b/src/helpers/hex_grid/axial.rs @@ -1,16 +1,19 @@ //! Code for the axial coordinate system. -use crate::helpers::hex_grid::consts::{DOUBLE_INV_SQRT_3, HALF_SQRT_3, INV_SQRT_3}; -use crate::helpers::hex_grid::cube::{CubePos, FractionalCubePos}; +use crate::helpers::hex_grid::consts::{ DOUBLE_INV_SQRT_3, HALF_SQRT_3, INV_SQRT_3 }; +use crate::helpers::hex_grid::cube::{ CubePos, FractionalCubePos }; use crate::helpers::hex_grid::neighbors::{ - HEX_OFFSETS, HexColDirection, HexDirection, HexRowDirection, + HEX_OFFSETS, + HexColDirection, + HexDirection, + HexRowDirection, }; -use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; +use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; use crate::map::HexCoordSystem; use crate::tiles::TilePos; -use crate::{TilemapGridSize, TilemapSize}; -use bevy::math::{Mat2, Vec2}; -use std::ops::{Add, Mul, Sub}; +use crate::{ TilemapGridSize, TilemapSize }; +use bevy::math::{ Mat2, Vec2 }; +use std::ops::{ Add, Mul, Sub }; /// A position in a hex grid labelled according to [`HexCoordSystem::Row`] or /// [`HexCoordSystem::Column`]. It is composed of a pair of `i32` digits named `q` and `r`. When @@ -191,7 +194,7 @@ pub const ROW_BASIS: Mat2 = Mat2::from_cols(Vec2::new(1.0, 0.0), Vec2::new(0.5, /// The inverse of [`ROW_BASIS`]. pub const INV_ROW_BASIS: Mat2 = Mat2::from_cols( Vec2::new(1.0, 0.0), - Vec2::new(-1.0 * INV_SQRT_3, DOUBLE_INV_SQRT_3), + Vec2::new(-1.0 * INV_SQRT_3, DOUBLE_INV_SQRT_3) ); /// The matrix for mapping from [`AxialPos`], to world position when hexes are arranged @@ -205,7 +208,7 @@ pub const COL_BASIS: Mat2 = Mat2::from_cols(Vec2::new(HALF_SQRT_3, 0.5), Vec2::n /// The inverse of [`COL_BASIS`]. pub const INV_COL_BASIS: Mat2 = Mat2::from_cols( Vec2::new(DOUBLE_INV_SQRT_3, -1.0 * INV_SQRT_3), - Vec2::new(0.0, 1.0), + Vec2::new(0.0, 1.0) ); pub const UNIT_Q: AxialPos = AxialPos { q: 1, r: 0 }; @@ -239,10 +242,7 @@ impl AxialPos { #[inline] pub fn project_row(axial_pos: Vec2, grid_size: &TilemapGridSize) -> Vec2 { let unscaled_pos = ROW_BASIS * axial_pos; - Vec2::new( - grid_size.x * unscaled_pos.x, - ROW_BASIS.y_axis.y * grid_size.y * unscaled_pos.y, - ) + Vec2::new(grid_size.x * unscaled_pos.x, ROW_BASIS.y_axis.y * grid_size.y * unscaled_pos.y) } /// Returns the center of a hex tile world space, assuming that: @@ -259,7 +259,7 @@ impl AxialPos { #[inline] pub fn corner_offset_in_world_row( corner_direction: HexRowDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let corner_offset = AxialPos::from(HexDirection::from(corner_direction)); let corner_pos = 0.5 * Vec2::new(corner_offset.q as f32, corner_offset.r as f32); @@ -275,7 +275,7 @@ impl AxialPos { pub fn corner_in_world_row( &self, corner_direction: HexRowDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let center = Vec2::new(self.q as f32, self.r as f32); @@ -294,10 +294,7 @@ impl AxialPos { #[inline] pub fn project_col(axial_pos: Vec2, grid_size: &TilemapGridSize) -> Vec2 { let unscaled_pos = COL_BASIS * axial_pos; - Vec2::new( - COL_BASIS.x_axis.x * grid_size.x * unscaled_pos.x, - grid_size.y * unscaled_pos.y, - ) + Vec2::new(COL_BASIS.x_axis.x * grid_size.x * unscaled_pos.x, grid_size.y * unscaled_pos.y) } /// Returns the center of a hex tile world space, assuming that: @@ -314,7 +311,7 @@ impl AxialPos { #[inline] pub fn corner_offset_in_world_col( corner_direction: HexColDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let corner_offset = AxialPos::from(HexDirection::from(corner_direction)); let corner_pos = 0.5 * Vec2::new(corner_offset.q as f32, corner_offset.r as f32); @@ -330,7 +327,7 @@ impl AxialPos { pub fn corner_in_world_col( &self, corner_direction: HexColDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let center = Vec2::new(self.q as f32, self.r as f32); @@ -348,7 +345,7 @@ impl AxialPos { pub fn from_world_pos_row(world_pos: &Vec2, grid_size: &TilemapGridSize) -> AxialPos { let normalized_world_pos = Vec2::new( world_pos.x / grid_size.x, - world_pos.y / (ROW_BASIS.y_axis.y * grid_size.y), + world_pos.y / (ROW_BASIS.y_axis.y * grid_size.y) ); let frac_pos = FractionalAxialPos::from(INV_ROW_BASIS * normalized_world_pos); frac_pos.round() @@ -362,7 +359,7 @@ impl AxialPos { pub fn from_world_pos_col(world_pos: &Vec2, grid_size: &TilemapGridSize) -> AxialPos { let normalized_world_pos = Vec2::new( world_pos.x / (COL_BASIS.x_axis.x * grid_size.x), - world_pos.y / grid_size.y, + world_pos.y / grid_size.y ); let frac_pos = FractionalAxialPos::from(INV_COL_BASIS * normalized_world_pos); frac_pos.round() @@ -416,7 +413,7 @@ impl AxialPos { pub fn as_tile_pos_given_coord_system_and_map_size( &self, hex_coord_sys: HexCoordSystem, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> Option { match hex_coord_sys { HexCoordSystem::RowEven => RowEvenPos::from(*self).as_tile_pos_given_map_size(map_size), @@ -442,7 +439,7 @@ impl AxialPos { #[inline] pub fn from_tile_pos_given_coord_system( tile_pos: &TilePos, - hex_coord_sys: HexCoordSystem, + hex_coord_sys: HexCoordSystem ) -> AxialPos { match hex_coord_sys { HexCoordSystem::RowEven => RowEvenPos::from(tile_pos).into(), @@ -509,3 +506,81 @@ impl From for FractionalAxialPos { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; + + // ---------- small private helper ----------------------------------------------------------- + #[test] + fn ceiled_div_by_2_behaves_as_expected() { + // negative inputs + assert_eq!(super::ceiled_division_by_2(-3), -2); + assert_eq!(super::ceiled_division_by_2(-2), -1); + assert_eq!(super::ceiled_division_by_2(-1), -1); + + // zero & positives + assert_eq!(super::ceiled_division_by_2(0), 0); + assert_eq!(super::ceiled_division_by_2(1), 1); + assert_eq!(super::ceiled_division_by_2(2), 1); + assert_eq!(super::ceiled_division_by_2(3), 2); + } + + // ---------- axial <-> offset round-trips --------------------------------------------------- + const SAMPLE_AXIALS: &[AxialPos] = &[ + AxialPos::new(0, 0), + AxialPos::new(2, -1), + AxialPos::new(-4, 5), + AxialPos::new(7, -3), + ]; + + #[test] + fn round_trip_row_odd() { + for &ax in SAMPLE_AXIALS { + let back: AxialPos = RowOddPos::from(ax).into(); + assert_eq!(ax, back, "failed on {ax:?}"); + } + } + + #[test] + fn round_trip_row_even() { + for &ax in SAMPLE_AXIALS { + let back: AxialPos = RowEvenPos::from(ax).into(); + assert_eq!(ax, back, "failed on {ax:?}"); + } + } + + #[test] + fn round_trip_col_odd() { + for &ax in SAMPLE_AXIALS { + let back: AxialPos = ColOddPos::from(ax).into(); + assert_eq!(ax, back, "failed on {ax:?}"); + } + } + + #[test] + fn round_trip_col_even() { + for &ax in SAMPLE_AXIALS { + let back: AxialPos = ColEvenPos::from(ax).into(); + assert_eq!(ax, back, "failed on {ax:?}"); + } + } + + // ---------- magnitude & distance ----------------------------------------------------------- + #[test] + fn magnitude_matches_cube_formula() { + assert_eq!(AxialPos::new(0, 0).magnitude(), 0); + assert_eq!(AxialPos::new(1, 0).magnitude(), 1); + assert_eq!(AxialPos::new(1, -1).magnitude(), 1); + assert_eq!(AxialPos::new(2, -1).magnitude(), 2); // (2,-1,-1) → (2+1+1)/2 = 2 + } + + #[test] + fn distance_is_symmetric_and_correct() { + let a = AxialPos::new(2, -1); + let b = AxialPos::new(-1, 3); + assert_eq!(a.distance_from(&b), b.distance_from(&a)); + assert_eq!(a.distance_from(&b), 4); + } +} diff --git a/src/helpers/hex_grid/cube.rs b/src/helpers/hex_grid/cube.rs index dbae7e31..b847e3d2 100644 --- a/src/helpers/hex_grid/cube.rs +++ b/src/helpers/hex_grid/cube.rs @@ -1,10 +1,7 @@ //! Code for the cube coordinate system -use crate::{ - helpers::hex_grid::axial::{AxialPos, FractionalAxialPos}, - tiles::TilePos, -}; -use std::ops::{Add, Mul, Sub}; +use crate::{ helpers::hex_grid::axial::{ AxialPos, FractionalAxialPos }, tiles::TilePos }; +use std::ops::{ Add, Mul, Sub }; /// Identical to [`AxialPos`], but has an extra component `s`. Together, `q`, `r`, `s` /// satisfy the identity: `q + r + s = 0`. @@ -175,3 +172,45 @@ impl FractionalCubePos { CubePos { q, r, s } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::hex_grid::axial::{ AxialPos }; + + #[test] + fn axial_to_cube_preserves_identity() { + let axial = AxialPos { q: 2, r: -3 }; + let cube: CubePos = axial.into(); + assert_eq!(cube.q + cube.r + cube.s, 0, "q + r + s must be 0"); + } + + #[test] + fn cube_add_sub_behave_like_vectors() { + let a = CubePos::new(1, -2, 1); + let b = CubePos::new(2, 1, -3); + + assert_eq!(a + b, CubePos::new(3, -1, -2)); + assert_eq!(a - b, CubePos::new(-1, -3, 4)); + // Add with reference + assert_eq!(a + &b, CubePos::new(3, -1, -2)); + } + + #[test] + fn scalar_multiplication_works_for_i32_and_u32() { + let v = CubePos::new(1, -2, 1); + assert_eq!(3_i32 * v, CubePos::new(3, -6, 3)); + assert_eq!(3_u32 * v, CubePos::new(3, -6, 3)); + } + + #[test] + fn fractional_rounding_gives_containing_hex() { + // Point very close to (1,-1,0) but with largest delta on q + let frac = FractionalCubePos::new(1.49, -1.1, -0.39); + assert_eq!(frac.round(), CubePos::new(1, -1, 0)); + + // Point inside origin hex + let frac2 = FractionalCubePos::new(0.2, -0.1, -0.1); + assert_eq!(frac2.round(), CubePos::new(0, 0, 0)); + } +} diff --git a/src/helpers/hex_grid/neighbors.rs b/src/helpers/hex_grid/neighbors.rs index 1a43fca2..a09cb721 100644 --- a/src/helpers/hex_grid/neighbors.rs +++ b/src/helpers/hex_grid/neighbors.rs @@ -1,10 +1,10 @@ use crate::TilePos; use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; -use crate::map::{HexCoordSystem, TilemapSize}; +use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; +use crate::map::{ HexCoordSystem, TilemapSize }; use crate::prelude::TileStorage; use bevy::prelude::Entity; -use std::ops::{Add, Sub}; +use std::ops::{ Add, Sub }; /// Neighbors of a hexagonal tile. `Zero` corresponds with `East` for row-oriented tiles, and /// `North` for column-oriented tiles. It might also correspond with a custom direction for @@ -98,7 +98,7 @@ impl Add for HexDirection { type Output = HexDirection; fn add(self, rhs: u32) -> Self::Output { - ((self as usize) + rhs as usize).into() + ((self as usize) + (rhs as usize)).into() } } @@ -130,7 +130,7 @@ impl Sub for HexDirection { type Output = HexDirection; fn sub(self, rhs: u32) -> Self::Output { - ((self as usize) - rhs as usize).into() + ((self as usize) - (rhs as usize)).into() } } @@ -322,18 +322,13 @@ impl HexNeighbors { /// If a neighbor is `None`, this iterator will skip it. #[inline] pub fn iter(&self) -> impl Iterator + '_ { - HEX_DIRECTIONS - .into_iter() - .filter_map(|direction| self.get(direction)) + HEX_DIRECTIONS.into_iter().filter_map(|direction| self.get(direction)) } /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by value. #[inline] - pub fn and_then(self, f: F) -> HexNeighbors - where - F: Fn(T) -> Option, - { + pub fn and_then(self, f: F) -> HexNeighbors where F: Fn(T) -> Option { HexNeighbors { zero: self.zero.and_then(&f), one: self.one.and_then(&f), @@ -347,10 +342,7 @@ impl HexNeighbors { /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by reference. #[inline] - pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors - where - F: Fn(&'a T) -> Option, - { + pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors where F: Fn(&'a T) -> Option { HexNeighbors { zero: self.zero.as_ref().and_then(&f), one: self.one.as_ref().and_then(&f), @@ -364,10 +356,7 @@ impl HexNeighbors { /// Applies the supplied closure `f` with a [`map`](std::option::Option::map) to each /// neighbor element, where `f` takes `T` by reference. #[inline] - pub fn map_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors - where - F: Fn(&'a T) -> U, - { + pub fn map_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors where F: Fn(&'a T) -> U { HexNeighbors { zero: self.zero.as_ref().map(&f), one: self.one.as_ref().map(&f), @@ -382,8 +371,7 @@ impl HexNeighbors { /// `Option`. #[inline] pub fn from_directional_closure(f: F) -> HexNeighbors - where - F: Fn(HexDirection) -> Option, + where F: Fn(HexDirection) -> Option { use HexDirection::*; HexNeighbors { @@ -414,7 +402,7 @@ impl HexNeighbors { pub fn get_neighboring_positions( tile_pos: &TilePos, map_size: &TilemapSize, - hex_coord_sys: &HexCoordSystem, + hex_coord_sys: &HexCoordSystem ) -> HexNeighbors { match hex_coord_sys { HexCoordSystem::RowEven => { @@ -452,14 +440,10 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_standard( tile_pos: &TilePos, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> HexNeighbors { let axial_pos = AxialPos::from(tile_pos); - let f = |direction| { - axial_pos - .offset(direction) - .as_tile_pos_given_map_size(map_size) - }; + let f = |direction| { axial_pos.offset(direction).as_tile_pos_given_map_size(map_size) }; HexNeighbors::from_directional_closure(f) } @@ -470,7 +454,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_row_even( tile_pos: &TilePos, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> HexNeighbors { let axial_pos = AxialPos::from(RowEvenPos::from(tile_pos)); let f = |direction| { @@ -486,7 +470,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_row_odd( tile_pos: &TilePos, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> HexNeighbors { let axial_pos = AxialPos::from(RowOddPos::from(tile_pos)); let f = |direction| { @@ -502,7 +486,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_col_even( tile_pos: &TilePos, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> HexNeighbors { let axial_pos = AxialPos::from(ColEvenPos::from(tile_pos)); let f = |direction| { @@ -518,7 +502,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_col_odd( tile_pos: &TilePos, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> HexNeighbors { let axial_pos = AxialPos::from(ColOddPos::from(tile_pos)); let f = |direction| { @@ -534,3 +518,36 @@ impl HexNeighbors { self.and_then_ref(f) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn axial_conversion_matches_const_offsets() { + for (idx, expected) in HEX_OFFSETS.iter().enumerate() { + let dir = HEX_DIRECTIONS[idx]; + assert_eq!(AxialPos::from(dir), *expected); + assert_eq!(AxialPos::from(&dir), *expected); + } + } + + #[test] + fn hex_neighbors_get_set_iter_work() { + let mut neigh: HexNeighbors = HexNeighbors::default(); + // Initially everything is None + assert!(neigh.iter().next().is_none()); + + // Set two directions + neigh.set(HexDirection::One, 10); + neigh.set(HexDirection::Four, 20); + + assert_eq!(neigh.get(HexDirection::One), Some(&10)); + assert_eq!(neigh.get(HexDirection::Four), Some(&20)); + assert_eq!(neigh.get(HexDirection::Zero), None); + + // Iter should yield exactly the two we inserted, order guaranteed by HEX_DIRECTIONS + let collected: Vec = neigh.iter().copied().collect(); + assert_eq!(collected, vec![10, 20]); + } +} diff --git a/src/helpers/hex_grid/offset.rs b/src/helpers/hex_grid/offset.rs index e8cb3f6b..0c7050ba 100644 --- a/src/helpers/hex_grid/offset.rs +++ b/src/helpers/hex_grid/offset.rs @@ -1,9 +1,9 @@ //! Code for the offset coordinate system. use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::neighbors::{HexColDirection, HexDirection, HexRowDirection}; +use crate::helpers::hex_grid::neighbors::{ HexColDirection, HexDirection, HexRowDirection }; use crate::tiles::TilePos; -use crate::{TilemapGridSize, TilemapSize}; +use crate::{ TilemapGridSize, TilemapSize }; use bevy::math::Vec2; #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] @@ -30,7 +30,7 @@ impl RowOddPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexRowDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { AxialPos::corner_offset_in_world_row(corner_direction, grid_size) } @@ -41,7 +41,7 @@ impl RowOddPos { pub fn corner_in_world( &self, corner_direction: HexRowDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_row(corner_direction, grid_size) @@ -121,7 +121,7 @@ impl RowEvenPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexRowDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { AxialPos::corner_offset_in_world_row(corner_direction, grid_size) } @@ -132,7 +132,7 @@ impl RowEvenPos { pub fn corner_in_world( &self, corner_direction: HexRowDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_row(corner_direction, grid_size) @@ -212,7 +212,7 @@ impl ColOddPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexColDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { AxialPos::corner_offset_in_world_col(corner_direction, grid_size) } @@ -223,7 +223,7 @@ impl ColOddPos { pub fn corner_in_world( &self, corner_direction: HexColDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_col(corner_direction, grid_size) @@ -303,7 +303,7 @@ impl ColEvenPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexColDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { AxialPos::corner_offset_in_world_col(corner_direction, grid_size) } @@ -314,7 +314,7 @@ impl ColEvenPos { pub fn corner_in_world( &self, corner_direction: HexColDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_col(corner_direction, grid_size) @@ -369,3 +369,52 @@ impl From<&TilePos> for ColEvenPos { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ TilemapSize }; + + // Helper: a 10 × 10 map that fits all positive test coordinates. + fn test_map_size() -> TilemapSize { + TilemapSize { x: 10, y: 10 } + } + + #[test] + fn tilepos_round_trip_row_odd() { + let tp = TilePos { x: 3, y: 7 }; + let pos = RowOddPos::from(&tp); + assert_eq!(pos.as_tile_pos_unchecked(), tp); + assert_eq!(pos.as_tile_pos_given_map_size(&test_map_size()), Some(tp)); + } + + #[test] + fn tilepos_round_trip_row_even() { + let tp = TilePos { x: 4, y: 1 }; + let pos = RowEvenPos::from(&tp); + assert_eq!(pos.as_tile_pos_unchecked(), tp); + assert_eq!(pos.as_tile_pos_given_map_size(&test_map_size()), Some(tp)); + } + + #[test] + fn tilepos_round_trip_col_odd() { + let tp = TilePos { x: 2, y: 6 }; + let pos = ColOddPos::from(&tp); + assert_eq!(pos.as_tile_pos_unchecked(), tp); + assert_eq!(pos.as_tile_pos_given_map_size(&test_map_size()), Some(tp)); + } + + #[test] + fn tilepos_round_trip_col_even() { + let tp = TilePos { x: 9, y: 0 }; + let pos = ColEvenPos::from(&tp); + assert_eq!(pos.as_tile_pos_unchecked(), tp); + assert_eq!(pos.as_tile_pos_given_map_size(&test_map_size()), Some(tp)); + } + + #[test] + fn negative_coords_are_rejected() { + let bad = RowOddPos::new(-1, 4); + assert!(bad.as_tile_pos_given_map_size(&test_map_size()).is_none()); + } +} diff --git a/src/helpers/projection.rs b/src/helpers/projection.rs index 1caf1550..e924c0da 100644 --- a/src/helpers/projection.rs +++ b/src/helpers/projection.rs @@ -1,10 +1,10 @@ use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; +use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; use crate::helpers::square_grid::diamond::DiamondPos; use crate::helpers::square_grid::staggered::StaggeredPos; -use crate::map::{HexCoordSystem, IsoCoordSystem}; +use crate::map::{ HexCoordSystem, IsoCoordSystem }; use crate::tiles::TilePos; -use crate::{TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType}; +use crate::{ TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType }; use bevy::math::Vec2; impl TilePos { @@ -17,7 +17,7 @@ impl TilePos { grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, map_type: &TilemapType, - anchor: &TilemapAnchor, + anchor: &TilemapAnchor ) -> Vec2 { let offset = anchor.as_offset(map_size, grid_size, tile_size, map_type); offset + self.center_in_world_unanchored(grid_size, map_type) @@ -26,24 +26,27 @@ impl TilePos { pub(crate) fn center_in_world_unanchored( &self, grid_size: &TilemapGridSize, - map_type: &TilemapType, + map_type: &TilemapType ) -> Vec2 { match map_type { TilemapType::Square => { Vec2::new(grid_size.x * (self.x as f32), grid_size.y * (self.y as f32)) } - TilemapType::Hexagon(hex_coord_sys) => match hex_coord_sys { - HexCoordSystem::RowEven => RowEvenPos::from(self).center_in_world(grid_size), - HexCoordSystem::RowOdd => RowOddPos::from(self).center_in_world(grid_size), - HexCoordSystem::ColumnEven => ColEvenPos::from(self).center_in_world(grid_size), - HexCoordSystem::ColumnOdd => ColOddPos::from(self).center_in_world(grid_size), - HexCoordSystem::Row => AxialPos::from(self).center_in_world_row(grid_size), - HexCoordSystem::Column => AxialPos::from(self).center_in_world_col(grid_size), - }, - TilemapType::Isometric(coord_system) => match coord_system { - IsoCoordSystem::Diamond => DiamondPos::from(self).center_in_world(grid_size), - IsoCoordSystem::Staggered => StaggeredPos::from(self).center_in_world(grid_size), - }, + TilemapType::Hexagon(hex_coord_sys) => + match hex_coord_sys { + HexCoordSystem::RowEven => RowEvenPos::from(self).center_in_world(grid_size), + HexCoordSystem::RowOdd => RowOddPos::from(self).center_in_world(grid_size), + HexCoordSystem::ColumnEven => ColEvenPos::from(self).center_in_world(grid_size), + HexCoordSystem::ColumnOdd => ColOddPos::from(self).center_in_world(grid_size), + HexCoordSystem::Row => AxialPos::from(self).center_in_world_row(grid_size), + HexCoordSystem::Column => AxialPos::from(self).center_in_world_col(grid_size), + } + TilemapType::Isometric(coord_system) => + match coord_system { + IsoCoordSystem::Diamond => DiamondPos::from(self).center_in_world(grid_size), + IsoCoordSystem::Staggered => + StaggeredPos::from(self).center_in_world(grid_size), + } } } @@ -74,41 +77,93 @@ impl TilePos { grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, map_type: &TilemapType, - anchor: &TilemapAnchor, + anchor: &TilemapAnchor ) -> Option { let offset = anchor.as_offset(map_size, grid_size, tile_size, map_type); let pos = world_pos - offset; match map_type { TilemapType::Square => { - let x = ((pos.x / grid_size.x) + 0.5).floor() as i32; - let y = ((pos.y / grid_size.y) + 0.5).floor() as i32; + let x = (pos.x / grid_size.x + 0.5).floor() as i32; + let y = (pos.y / grid_size.y + 0.5).floor() as i32; TilePos::from_i32_pair(x, y, map_size) } TilemapType::Hexagon(hex_coord_sys) => { match hex_coord_sys { - HexCoordSystem::RowEven => RowEvenPos::from_world_pos(&pos, grid_size) - .as_tile_pos_given_map_size(map_size), - HexCoordSystem::RowOdd => RowOddPos::from_world_pos(&pos, grid_size) - .as_tile_pos_given_map_size(map_size), - HexCoordSystem::ColumnEven => ColEvenPos::from_world_pos(&pos, grid_size) - .as_tile_pos_given_map_size(map_size), - HexCoordSystem::ColumnOdd => ColOddPos::from_world_pos(&pos, grid_size) - .as_tile_pos_given_map_size(map_size), - HexCoordSystem::Row => AxialPos::from_world_pos_row(&pos, grid_size) - .as_tile_pos_given_map_size(map_size), - HexCoordSystem::Column => AxialPos::from_world_pos_col(&pos, grid_size) - .as_tile_pos_given_map_size(map_size), + HexCoordSystem::RowEven => + RowEvenPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( + map_size + ), + HexCoordSystem::RowOdd => + RowOddPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( + map_size + ), + HexCoordSystem::ColumnEven => + ColEvenPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( + map_size + ), + HexCoordSystem::ColumnOdd => + ColOddPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( + map_size + ), + HexCoordSystem::Row => + AxialPos::from_world_pos_row(&pos, grid_size).as_tile_pos_given_map_size( + map_size + ), + HexCoordSystem::Column => + AxialPos::from_world_pos_col(&pos, grid_size).as_tile_pos_given_map_size( + map_size + ), } } - TilemapType::Isometric(coord_system) => match coord_system { - IsoCoordSystem::Diamond => { - DiamondPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) + TilemapType::Isometric(coord_system) => + match coord_system { + IsoCoordSystem::Diamond => { + DiamondPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) + } + IsoCoordSystem::Staggered => { + StaggeredPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) + } } - IsoCoordSystem::Staggered => { - StaggeredPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) - } - }, } } } + +#[cfg(test)] +mod tests { + use super::*; + use bevy::math::Vec2; + + #[test] + fn from_i32_pair_negative_returns_none() { + let map_size = TilemapSize { x: 10, y: 10 }; + // negative coordinates are invalid + assert!(TilePos::from_i32_pair(-1, 0, &map_size).is_none()); + assert!(TilePos::from_i32_pair(0, -1, &map_size).is_none()); + } + + #[test] + fn from_i32_pair_out_of_bounds_returns_none() { + let map_size = TilemapSize { x: 10, y: 10 }; + // coordinates equal to or larger than map_size are out-of-bounds + assert!(TilePos::from_i32_pair(10, 0, &map_size).is_none()); + assert!(TilePos::from_i32_pair(0, 10, &map_size).is_none()); + } + + #[test] + fn from_i32_pair_valid_returns_some() { + let map_size = TilemapSize { x: 10, y: 10 }; + let pos = TilePos::from_i32_pair(3, 7, &map_size).unwrap(); + assert_eq!(pos, TilePos { x: 3, y: 7 }); + } + + #[test] + fn center_in_world_unanchored_square() { + // (3, 1) on a 32×32 square grid should be at (96, 32) + let tile_pos = TilePos { x: 3, y: 1 }; + let grid_size = TilemapGridSize { x: 32.0, y: 32.0 }; + let center = tile_pos.center_in_world_unanchored(&grid_size, &TilemapType::Square); + + assert_eq!(center, Vec2::new(96.0, 32.0)); + } +} diff --git a/src/helpers/square_grid/diamond.rs b/src/helpers/square_grid/diamond.rs index 51f67f4e..790f95c5 100644 --- a/src/helpers/square_grid/diamond.rs +++ b/src/helpers/square_grid/diamond.rs @@ -1,12 +1,12 @@ //! Code for the isometric diamond coordinate system. use crate::helpers::square_grid::SquarePos; -use crate::helpers::square_grid::neighbors::{SQUARE_OFFSETS, SquareDirection}; +use crate::helpers::square_grid::neighbors::{ SQUARE_OFFSETS, SquareDirection }; use crate::helpers::square_grid::staggered::StaggeredPos; use crate::tiles::TilePos; -use crate::{TilemapGridSize, TilemapSize}; -use bevy::math::{Mat2, Vec2}; -use std::ops::{Add, Mul, Sub}; +use crate::{ TilemapGridSize, TilemapSize }; +use bevy::math::{ Mat2, Vec2 }; +use std::ops::{ Add, Mul, Sub }; /// Position for tiles arranged in [`Diamond`](crate::map::IsoCoordSystem::Diamond) isometric /// coordinate system. @@ -151,7 +151,7 @@ impl DiamondPos { #[inline] pub fn corner_offset_in_world( corner_direction: SquareDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let corner_offset = DiamondPos::from(SquarePos::from(corner_direction)); let corner_pos = 0.5 * Vec2::new(corner_offset.x as f32, corner_offset.y as f32); @@ -164,7 +164,7 @@ impl DiamondPos { pub fn corner_in_world( &self, corner_direction: SquareDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let center = Vec2::new(self.x as f32, self.y as f32); @@ -208,10 +208,36 @@ impl TilePos { pub fn diamond_offset( &self, direction: &SquareDirection, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> Option { - DiamondPos::from(self) - .offset(direction) - .as_tile_pos(map_size) + DiamondPos::from(self).offset(direction).as_tile_pos(map_size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn grid32() -> TilemapGridSize { + TilemapGridSize { x: 32.0, y: 32.0 } + } + + #[test] + fn arithmetic_operators_work() { + let a = DiamondPos::new(2, -3); + let b = DiamondPos::new(-5, 7); + + assert_eq!(a + b, DiamondPos::new(-3, 4)); + assert_eq!(a - b, DiamondPos::new(7, -10)); + assert_eq!(3 * b, DiamondPos::new(-15, 21)); + } + + #[test] + fn world_roundtrip_through_center() { + let grid = grid32(); + let tile = DiamondPos::new(7, 4); + let world = tile.center_in_world(&grid); + let tile_back = DiamondPos::from_world_pos(&world, &grid); + assert_eq!(tile_back, tile); } } diff --git a/src/helpers/square_grid/mod.rs b/src/helpers/square_grid/mod.rs index 3f53fddf..2010cd61 100644 --- a/src/helpers/square_grid/mod.rs +++ b/src/helpers/square_grid/mod.rs @@ -3,12 +3,12 @@ pub mod neighbors; pub mod staggered; use crate::helpers::square_grid::diamond::DiamondPos; -use crate::helpers::square_grid::neighbors::{SQUARE_OFFSETS, SquareDirection}; +use crate::helpers::square_grid::neighbors::{ SQUARE_OFFSETS, SquareDirection }; use crate::helpers::square_grid::staggered::StaggeredPos; use crate::tiles::TilePos; -use crate::{TilemapGridSize, TilemapSize}; +use crate::{ TilemapGridSize, TilemapSize }; use bevy::math::Vec2; -use std::ops::{Add, Mul, Sub}; +use std::ops::{ Add, Mul, Sub }; /// Position for tiles arranged in a square coordinate system. /// @@ -128,7 +128,7 @@ impl SquarePos { #[inline] pub fn corner_offset_in_world( corner_direction: SquareDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let corner_offset = SquarePos::from(corner_direction); let corner_pos = 0.5 * Vec2::new(corner_offset.x as f32, corner_offset.y as f32); @@ -141,7 +141,7 @@ impl SquarePos { pub fn corner_in_world( &self, corner_direction: SquareDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let center = Vec2::new(self.x as f32, self.y as f32); @@ -185,10 +185,89 @@ impl TilePos { pub fn square_offset( &self, direction: &SquareDirection, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> Option { - SquarePos::from(self) - .offset(direction) - .as_tile_pos(map_size) + SquarePos::from(self).offset(direction).as_tile_pos(map_size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::square_grid::neighbors::SquareDirection; + use crate::{ TilemapGridSize, TilemapSize, tiles::TilePos }; + + fn gs() -> TilemapGridSize { + TilemapGridSize { x: 32.0, y: 32.0 } + } + + #[test] + fn add_sub_mul_work() { + let a = SquarePos::new(2, -3); + let b = SquarePos::new(-4, 8); + + assert_eq!(a + b, SquarePos::new(-2, 5)); + assert_eq!(a - b, SquarePos::new(6, -11)); + assert_eq!(3 * a, SquarePos::new(6, -9)); + } + + #[test] + fn conversions_are_correct() { + let tp = TilePos::new(4, 7); + assert_eq!(SquarePos::from(&tp), SquarePos::new(4, 7)); + + let dp = DiamondPos { x: -2, y: 5 }; + assert_eq!(SquarePos::from(dp), SquarePos::new(-2, 5)); + + let sp = StaggeredPos { x: 3, y: 1 }; + assert_eq!(SquarePos::from(sp), SquarePos::new(3, 4)); + } + + #[test] + fn project_and_unproject_roundtrip() { + let pos = SquarePos::new(5, 9); + let world = pos.center_in_world(&gs()); + assert_eq!(SquarePos::from_world_pos(&world, &gs()), pos); + } + + #[test] + fn corners_in_world_match_offset_helpers() { + let pos = SquarePos::new(0, 0); + for dir in [ + SquareDirection::NorthWest, + SquareDirection::NorthEast, + SquareDirection::SouthWest, + SquareDirection::SouthEast, + ] { + let by_center = pos.corner_in_world(dir, &gs()); + let by_offset = + pos.center_in_world(&gs()) + SquarePos::corner_offset_in_world(dir, &gs()); + assert_eq!(by_center, by_offset); + } + } + + #[test] + fn as_tile_pos_respects_bounds() { + let map_size = TilemapSize { x: 10, y: 10 }; + let inside = SquarePos::new(3, 9); + assert_eq!(inside.as_tile_pos(&map_size), Some(TilePos::new(3, 9))); + + let neg = SquarePos::new(-1, 0); + let out = SquarePos::new(10, 0); + assert!(neg.as_tile_pos(&map_size).is_none()); + assert!(out.as_tile_pos(&map_size).is_none()); + } + + #[test] + fn offset_matches_square_offsets_table() { + let origin = SquarePos::new(0, 0); + for dir in [ + SquareDirection::North, + SquareDirection::East, + SquareDirection::South, + SquareDirection::West, + ] { + assert_eq!(origin.offset(&dir), SQUARE_OFFSETS[dir as usize]); + } } } diff --git a/src/helpers/square_grid/neighbors.rs b/src/helpers/square_grid/neighbors.rs index 5903d283..b303c2aa 100644 --- a/src/helpers/square_grid/neighbors.rs +++ b/src/helpers/square_grid/neighbors.rs @@ -1,9 +1,9 @@ use crate::helpers::square_grid::SquarePos; use crate::helpers::square_grid::staggered::StaggeredPos; use crate::map::TilemapSize; -use crate::prelude::{TilePos, TileStorage}; +use crate::prelude::{ TilePos, TileStorage }; use bevy::prelude::Entity; -use std::ops::{Add, Sub}; +use std::ops::{ Add, Sub }; /// The eight directions in which a neighbor may lie, on a square-like grid. /// @@ -104,7 +104,7 @@ impl Add for SquareDirection { type Output = SquareDirection; fn add(self, rhs: u32) -> Self::Output { - ((self as usize) + rhs as usize).into() + ((self as usize) + (rhs as usize)).into() } } @@ -136,7 +136,7 @@ impl Sub for SquareDirection { type Output = SquareDirection; fn sub(self, rhs: u32) -> Self::Output { - ((self as usize) - rhs as usize).into() + ((self as usize) - (rhs as usize)).into() } } @@ -232,9 +232,7 @@ impl Neighbors { /// /// If a neighbor is `None`, this iterator will skip it. pub fn iter(&self) -> impl Iterator + '_ { - SQUARE_DIRECTIONS - .into_iter() - .filter_map(|direction| self.get(direction)) + SQUARE_DIRECTIONS.into_iter().filter_map(|direction| self.get(direction)) } /// Iterate over neighbors, in the order specified by [`SQUARE_DIRECTIONS`]. @@ -242,17 +240,14 @@ impl Neighbors { /// /// If a neighbor is `None`, this iterator will skip it. pub fn iter_with_direction(&self) -> impl Iterator + '_ { - SQUARE_DIRECTIONS - .into_iter() - .filter_map(|direction| self.get(direction).map(|value| (direction, value))) + SQUARE_DIRECTIONS.into_iter().filter_map(|direction| + self.get(direction).map(|value| (direction, value)) + ) } /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by value. - pub fn and_then(self, f: F) -> Neighbors - where - F: Fn(T) -> Option, - { + pub fn and_then(self, f: F) -> Neighbors where F: Fn(T) -> Option { Neighbors { east: self.east.and_then(&f), north_east: self.north_east.and_then(&f), @@ -267,10 +262,7 @@ impl Neighbors { /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by reference. - pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> Neighbors - where - F: Fn(&'a T) -> Option, - { + pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> Neighbors where F: Fn(&'a T) -> Option { Neighbors { east: self.east.as_ref().and_then(&f), north_east: self.north_east.as_ref().and_then(&f), @@ -285,10 +277,7 @@ impl Neighbors { /// Applies the supplied closure `f` with a [`map`](std::option::Option::map) to each /// neighbor element, where `f` takes `T` by reference. - pub fn map_ref<'a, U, F>(&'a self, f: F) -> Neighbors - where - F: Fn(&'a T) -> U, - { + pub fn map_ref<'a, U, F>(&'a self, f: F) -> Neighbors where F: Fn(&'a T) -> U { Neighbors { east: self.east.as_ref().map(&f), north_east: self.north_east.as_ref().map(&f), @@ -304,8 +293,7 @@ impl Neighbors { /// Generates `SquareNeighbors` from a closure that takes a hex direction and outputs /// `Option`. pub fn from_directional_closure(f: F) -> Neighbors - where - F: Fn(SquareDirection) -> Option, + where F: Fn(SquareDirection) -> Option { use SquareDirection::*; Neighbors { @@ -343,12 +331,12 @@ impl Neighbors { pub fn get_square_neighboring_positions( tile_pos: &TilePos, map_size: &TilemapSize, - include_diagonals: bool, + include_diagonals: bool ) -> Neighbors { let square_pos = SquarePos::from(tile_pos); if include_diagonals { - let f = - |direction: SquareDirection| square_pos.offset(&direction).as_tile_pos(map_size); + let f = |direction: SquareDirection| + square_pos.offset(&direction).as_tile_pos(map_size); Neighbors::from_directional_closure(f) } else { @@ -372,12 +360,12 @@ impl Neighbors { pub fn get_staggered_neighboring_positions( tile_pos: &TilePos, map_size: &TilemapSize, - include_diagonals: bool, + include_diagonals: bool ) -> Neighbors { let staggered_pos = StaggeredPos::from(tile_pos); if include_diagonals { - let f = - |direction: SquareDirection| staggered_pos.offset(&direction).as_tile_pos(map_size); + let f = |direction: SquareDirection| + staggered_pos.offset(&direction).as_tile_pos(map_size); Neighbors::from_directional_closure(f) } else { @@ -399,3 +387,85 @@ impl Neighbors { self.and_then_ref(f) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cardinal_and_diagonal_flags() { + for d in CARDINAL_SQUARE_DIRECTIONS { + assert!(d.is_cardinal()); + assert!(!d.is_diagonal()); + } + + for d in SQUARE_DIRECTIONS { + if !d.is_cardinal() { + assert!(d.is_diagonal()); + } + } + } + + #[test] + fn squarepos_from_direction_is_offset_lookup() { + for d in SQUARE_DIRECTIONS { + assert_eq!(SquarePos::from(d), SQUARE_OFFSETS[d as usize]); + } + } + + #[test] + fn set_get_get_mut_iter_variants() { + let mut n = Neighbors::::default(); + + // nothing set yet + assert!(n.east.is_none()); + assert_eq!(n.iter().count(), 0); + + // set a couple of values + n.set(SquareDirection::East, 10); + n.set(SquareDirection::SouthWest, 42); + + // get / get_mut + assert_eq!(n.get(SquareDirection::East), Some(&10)); + if let Some(x) = n.get_inner_mut(SquareDirection::East) { + *x += 1; + } + assert_eq!(n.get(SquareDirection::East), Some(&11)); + + // iterator order obeys SQUARE_DIRECTIONS + let items: Vec<_> = n.iter().copied().collect(); + assert_eq!(items, vec![11, 42]); + + // iter_with_direction gives the same ordering and pairs + let pairs: Vec<_> = n.iter_with_direction().collect(); + assert_eq!(pairs, vec![(SquareDirection::East, &11), (SquareDirection::SouthWest, &42)]); + } + + #[test] + fn map_and_and_then_variants() { + let n = Neighbors { + east: Some(1u8), + west: Some(2u8), + ..Default::default() + }; + + // map_ref + let doubled = n.map_ref(|x| x * 2); + assert_eq!(doubled.east, Some(2)); + assert_eq!(doubled.west, Some(4)); + assert!(doubled.north.is_none()); + + // and_then (by value) + let filtered = n.and_then(|x| if x % 2 == 0 { Some(x) } else { None }); + assert_eq!(filtered.east, None); + assert_eq!(filtered.west, Some(2)); + } + + #[test] + fn from_directional_closure_is_exhaustive() { + let n = Neighbors::::from_directional_closure(|d| Some(d as u8)); + for d in SQUARE_DIRECTIONS { + assert_eq!(n.get(d), Some(&(d as u8))); + } + } +} diff --git a/src/helpers/square_grid/staggered.rs b/src/helpers/square_grid/staggered.rs index 07d6cf79..4427113f 100644 --- a/src/helpers/square_grid/staggered.rs +++ b/src/helpers/square_grid/staggered.rs @@ -1,10 +1,10 @@ use crate::helpers::square_grid::SquarePos; use crate::helpers::square_grid::diamond::DiamondPos; -use crate::helpers::square_grid::neighbors::{SQUARE_OFFSETS, SquareDirection}; +use crate::helpers::square_grid::neighbors::{ SQUARE_OFFSETS, SquareDirection }; use crate::tiles::TilePos; -use crate::{TilemapGridSize, TilemapSize}; +use crate::{ TilemapGridSize, TilemapSize }; use bevy::math::Vec2; -use std::ops::{Add, Mul, Sub}; +use std::ops::{ Add, Mul, Sub }; /// Position for tiles arranged in [`Staggered`](crate::map::IsoCoordSystem::Diamond) isometric /// coordinate system. @@ -105,7 +105,7 @@ impl StaggeredPos { #[inline] pub fn corner_offset_in_world( corner_direction: SquareDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { DiamondPos::corner_offset_in_world(corner_direction, grid_size) } @@ -116,7 +116,7 @@ impl StaggeredPos { pub fn corner_in_world( &self, corner_direction: SquareDirection, - grid_size: &TilemapGridSize, + grid_size: &TilemapGridSize ) -> Vec2 { let diamond_pos = DiamondPos::from(self); @@ -157,10 +157,46 @@ impl TilePos { pub fn staggered_offset( &self, direction: &SquareDirection, - map_size: &TilemapSize, + map_size: &TilemapSize ) -> Option { - StaggeredPos::from(self) - .offset(direction) - .as_tile_pos(map_size) + StaggeredPos::from(self).offset(direction).as_tile_pos(map_size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::square_grid::diamond::DiamondPos; + use crate::helpers::square_grid::SquarePos; + use crate::helpers::square_grid::neighbors::SquareDirection::*; + + #[test] + fn conversion_diamond_to_staggered_roundtrip() { + let d = DiamondPos { x: 3, y: 7 }; + let s = StaggeredPos::from(d); + assert_eq!(s, StaggeredPos { x: 3, y: 4 }); + + // `DiamondPos` implements From<&StaggeredPos> elsewhere in the crate. + let back: DiamondPos = (&s).into(); + assert_eq!(back, d); + } + + #[test] + fn arithmetic_ops() { + let a = StaggeredPos { x: 2, y: 5 }; + let b = StaggeredPos { x: -1, y: 3 }; + + assert_eq!(a + b, StaggeredPos { x: 1, y: 8 }); + assert_eq!(a - b, StaggeredPos { x: 3, y: 2 }); + assert_eq!(2 * a, StaggeredPos { x: 4, y: 10 }); + } + + #[test] + fn offset_cardinals() { + let origin = StaggeredPos::new(0, 0); + for dir in [North, East, South, West] { + let expected = StaggeredPos::from(SquarePos::from(dir)); + assert_eq!(origin.offset(&dir), expected); + } } } diff --git a/src/helpers/transform.rs b/src/helpers/transform.rs index 28937498..945c3ab4 100644 --- a/src/helpers/transform.rs +++ b/src/helpers/transform.rs @@ -1,6 +1,6 @@ use crate::tiles::TilePos; -use crate::{TilemapGridSize, TilemapTileSize, TilemapType}; -use bevy::math::{UVec2, Vec2, Vec3}; +use crate::{ TilemapGridSize, TilemapTileSize, TilemapType }; +use bevy::math::{ UVec2, Vec2, Vec3 }; use bevy::render::primitives::Aabb; /// Calculates the world-space position of the bottom-left of the specified chunk. @@ -8,7 +8,7 @@ pub fn chunk_index_to_world_space( chunk_index: UVec2, chunk_size: UVec2, grid_size: &TilemapGridSize, - map_type: &TilemapType, + map_type: &TilemapType ) -> Vec2 { // Get the position of the bottom left tile of the chunk: the "anchor tile". let anchor_tile_pos = TilePos { @@ -30,7 +30,7 @@ pub fn chunk_aabb( chunk_size: UVec2, grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, - map_type: &TilemapType, + map_type: &TilemapType ) -> Aabb { // The AABB minimum and maximum have to be modified by -border and +border respectively. let border = Vec2::from(grid_size).max(tile_size.into()) / 2.0; @@ -50,3 +50,64 @@ pub fn chunk_aabb( let maximum = Vec3::from((c0.max(c1).max(c2).max(c3) + border, 1.0)); Aabb::from_min_max(minimum, maximum) } + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_relative_eq; + use bevy::math::{ UVec2, Vec3 }; + + #[test] + fn chunk_index_to_world_space_origin_square() { + let chunk_size = UVec2::new(4, 4); + let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; + + // Bottom-left chunk (index 0,0) should have its anchor-tile centre at (0.0, 0.0) + let ws = chunk_index_to_world_space(UVec2::ZERO, chunk_size, &grid_size, &TilemapType::Square); + assert_relative_eq!(ws.x, 0.0, epsilon = 1e-6); + assert_relative_eq!(ws.y, 0.0, epsilon = 1e-6); + } + + #[test] + fn chunk_aabb_square_unit_sizes() { + // 4 × 4 chunk, unit grid & tile sizes + let chunk_size = UVec2::new(4, 4); + let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; + let tile_size = TilemapTileSize { x: 1.0, y: 1.0 }; + + let aabb = chunk_aabb(chunk_size, &grid_size, &tile_size, &TilemapType::Square); + + let min = aabb.min(); + let max = aabb.max(); + + assert_relative_eq!(min.x, -0.5, epsilon = 1e-6); + assert_relative_eq!(min.y, -0.5, epsilon = 1e-6); + assert_relative_eq!(min.z, 0.0, epsilon = 1e-6); + + assert_relative_eq!(max.x, 4.5, epsilon = 1e-6); + assert_relative_eq!(max.y, 4.5, epsilon = 1e-6); + assert_relative_eq!(max.z, 1.0, epsilon = 1e-6); + } + + #[test] + fn chunk_aabb_tile_size_larger_than_grid_size() { + let chunk_size = UVec2::new(2, 2); + let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; + let tile_size = TilemapTileSize { x: 2.0, y: 2.0 }; + + let aabb = chunk_aabb(chunk_size, &grid_size, &tile_size, &TilemapType::Square); + + let expected_min = Vec3::new(-1.0, -1.0, 0.0); + let expected_max = Vec3::new(3.0, 3.0, 1.0); + + let min = aabb.min(); + let max = aabb.max(); + + assert_relative_eq!(min.x, expected_min.x, epsilon = 1e-6); + assert_relative_eq!(max.x, expected_max.x, epsilon = 1e-6); + assert_relative_eq!(min.y, expected_min.y, epsilon = 1e-6); + assert_relative_eq!(max.y, expected_max.y, epsilon = 1e-6); + assert_relative_eq!(min.z, expected_min.z, epsilon = 1e-6); + assert_relative_eq!(max.z, expected_max.z, epsilon = 1e-6); + } +} diff --git a/src/lib.rs b/src/lib.rs index 576c61a5..8fe64183 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,8 +18,21 @@ use bevy::{ ecs::schedule::IntoScheduleConfigs, prelude::{ - Bundle, Changed, Component, Deref, First, GlobalTransform, InheritedVisibility, Plugin, - Query, Reflect, ReflectComponent, SystemSet, Transform, ViewVisibility, Visibility, + Bundle, + Changed, + Component, + Deref, + First, + GlobalTransform, + InheritedVisibility, + Plugin, + Query, + Reflect, + ReflectComponent, + SystemSet, + Transform, + ViewVisibility, + Visibility, }, render::sync_world::SyncToRenderWorld, time::TimeSystem, @@ -30,19 +43,30 @@ use render::material::MaterialTilemapHandle; use anchor::TilemapAnchor; use map::{ - TilemapGridSize, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, - TilemapTileSize, TilemapType, + TilemapGridSize, + TilemapSize, + TilemapSpacing, + TilemapTexture, + TilemapTextureSize, + TilemapTileSize, + TilemapType, }; -use prelude::{TilemapId, TilemapRenderSettings}; +use prelude::{ TilemapId, TilemapRenderSettings }; #[cfg(feature = "render")] -use render::material::{MaterialTilemap, StandardTilemapMaterial}; +use render::material::{ MaterialTilemap, StandardTilemapMaterial }; use tiles::{ - AnimatedTile, TileColor, TileFlip, TilePos, TilePosOld, TileStorage, TileTextureIndex, + AnimatedTile, + TileColor, + TileFlip, + TilePos, + TilePosOld, + TileStorage, + TileTextureIndex, TileVisible, }; #[cfg(all(not(feature = "atlas"), feature = "render"))] -use bevy::render::{ExtractSchedule, RenderApp}; +use bevy::render::{ ExtractSchedule, RenderApp }; pub mod anchor; /// A module that allows pre-loading of atlases into array textures. @@ -200,3 +224,14 @@ fn update_changed_tile_positions(mut query: Query<(&TilePos, &mut TilePosOld), C tile_pos_old.0 = *tile_pos; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn frustum_culling_default_is_true() { + let fc = FrustumCulling::default(); + assert!(fc.0, "FrustumCulling::default() should be true"); + } +} diff --git a/src/map.rs b/src/map.rs index fe15f42f..ca8e09fc 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,17 +1,20 @@ use bevy::{ asset::Assets, - ecs::{ - entity::{EntityMapper, MapEntities}, - reflect::ReflectMapEntities, - }, - math::{UVec2, Vec2}, + ecs::{ entity::{ EntityMapper, MapEntities }, reflect::ReflectMapEntities }, + math::{ UVec2, Vec2 }, prelude::{ - Component, Deref, DerefMut, Entity, Handle, Image, Reflect, ReflectComponent, Res, ResMut, - }, - render::{ - render_resource::TextureUsages, - view::{VisibilityClass, add_visibility_class}, + Component, + Deref, + DerefMut, + Entity, + Handle, + Image, + Reflect, + ReflectComponent, + Res, + ResMut, }, + render::{ render_resource::TextureUsages, view::{ VisibilityClass, add_visibility_class } }, }; use std::ops::Add; @@ -178,16 +181,15 @@ impl TilemapTexture { } #[cfg(not(feature = "atlas"))] - self.image_handles().into_iter().all(|h| { - if let Some(image) = images.get(h) { - image - .texture_descriptor - .usage - .contains(TextureUsages::COPY_SRC) - } else { - false - } - }) + self.image_handles() + .into_iter() + .all(|h| { + if let Some(image) = images.get(h) { + image.texture_descriptor.usage.contains(TextureUsages::COPY_SRC) + } else { + false + } + }) } /// Sets images with the `COPY_SRC` flag. @@ -196,15 +198,12 @@ impl TilemapTexture { // NOTE: We retrieve it non-mutably first to avoid triggering an `AssetEvent::Modified` // if we didn't actually need to modify it if let Some(image) = images.get(handle) { - if !image - .texture_descriptor - .usage - .contains(TextureUsages::COPY_SRC) - { + if !image.texture_descriptor.usage.contains(TextureUsages::COPY_SRC) { if let Some(image) = images.get_mut(handle) { - image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING - | TextureUsages::COPY_SRC - | TextureUsages::COPY_DST; + image.texture_descriptor.usage = + TextureUsages::TEXTURE_BINDING | + TextureUsages::COPY_SRC | + TextureUsages::COPY_DST; }; } } @@ -216,7 +215,12 @@ impl TilemapTexture { TilemapTexture::Single(handle) => TilemapTexture::Single(handle.clone_weak()), #[cfg(not(feature = "atlas"))] TilemapTexture::Vector(handles) => { - TilemapTexture::Vector(handles.iter().map(|h| h.clone_weak()).collect()) + TilemapTexture::Vector( + handles + .iter() + .map(|h| h.clone_weak()) + .collect() + ) } #[cfg(not(feature = "atlas"))] TilemapTexture::TextureContainer(handle) => { @@ -510,49 +514,89 @@ mod tests { } #[test] fn add_tilemap_tile_size() { - let a = TilemapTileSize { x: 2., y: 2. }; - let b = TilemapTileSize { x: 3., y: 3. }; - assert_eq!(a + b, TilemapTileSize { x: 5., y: 5. }); + let a = TilemapTileSize { x: 2.0, y: 2.0 }; + let b = TilemapTileSize { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapTileSize { x: 5.0, y: 5.0 }); } #[test] fn add_tilemap_tile_size_vec2() { - let a = TilemapTileSize { x: 2., y: 2. }; - let b = Vec2 { x: 3., y: 3. }; - assert_eq!(a + b, TilemapTileSize { x: 5., y: 5. }); + let a = TilemapTileSize { x: 2.0, y: 2.0 }; + let b = Vec2 { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapTileSize { x: 5.0, y: 5.0 }); } #[test] fn add_tilemap_grid_size() { - let a = TilemapGridSize { x: 2., y: 2. }; - let b = TilemapGridSize { x: 3., y: 3. }; - assert_eq!(a + b, TilemapGridSize { x: 5., y: 5. }); + let a = TilemapGridSize { x: 2.0, y: 2.0 }; + let b = TilemapGridSize { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapGridSize { x: 5.0, y: 5.0 }); } fn add_tilemap_grid_size_vec2() { - let a = TilemapGridSize { x: 2., y: 2. }; - let b = Vec2 { x: 3., y: 3. }; - assert_eq!(a + b, TilemapGridSize { x: 5., y: 5. }); + let a = TilemapGridSize { x: 2.0, y: 2.0 }; + let b = Vec2 { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapGridSize { x: 5.0, y: 5.0 }); } #[test] fn add_tilemap_spacing() { - let a = TilemapSpacing { x: 2., y: 2. }; - let b = TilemapSpacing { x: 3., y: 3. }; - assert_eq!(a + b, TilemapSpacing { x: 5., y: 5. }); + let a = TilemapSpacing { x: 2.0, y: 2.0 }; + let b = TilemapSpacing { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapSpacing { x: 5.0, y: 5.0 }); } #[test] fn add_tilemap_spacing_vec2() { - let a = TilemapSpacing { x: 2., y: 2. }; - let b = Vec2 { x: 3., y: 3. }; - assert_eq!(a + b, TilemapSpacing { x: 5., y: 5. }); + let a = TilemapSpacing { x: 2.0, y: 2.0 }; + let b = Vec2 { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapSpacing { x: 5.0, y: 5.0 }); } #[test] fn add_tilemap_texture_size() { - let a = TilemapTextureSize { x: 2., y: 2. }; - let b = TilemapTextureSize { x: 3., y: 3. }; - assert_eq!(a + b, TilemapTextureSize { x: 5., y: 5. }); + let a = TilemapTextureSize { x: 2.0, y: 2.0 }; + let b = TilemapTextureSize { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapTextureSize { x: 5.0, y: 5.0 }); } #[test] fn add_tilemap_texture_size_vec2() { - let a = TilemapTextureSize { x: 2., y: 2. }; - let b = Vec2 { x: 3., y: 3. }; - assert_eq!(a + b, TilemapTextureSize { x: 5., y: 5. }); + let a = TilemapTextureSize { x: 2.0, y: 2.0 }; + let b = Vec2 { x: 3.0, y: 3.0 }; + assert_eq!(a + b, TilemapTextureSize { x: 5.0, y: 5.0 }); + } + #[test] + fn tilemap_size_count_and_conversions() { + let size = TilemapSize::new(4, 5); + assert_eq!(size.count(), 20); + + // TilemapSize ➜ Vec2 + let v: Vec2 = size.into(); + assert_eq!(v, Vec2::new(4.0, 5.0)); + + // TilemapSize ➜ UVec2 ➜ TilemapSize + let uv: UVec2 = size.into(); + assert_eq!(uv, UVec2::new(4, 5)); + let size2: TilemapSize = uv.into(); + assert_eq!(size2, size); + } + + #[test] + fn tilemap_tile_size_conversions() { + let ts = TilemapTileSize::new(16.0, 32.0); + + // TilemapTileSize ➜ Vec2 + let v: Vec2 = ts.into(); + assert_eq!(v, Vec2::new(16.0, 32.0)); + + // TilemapTileSize ➜ TilemapGridSize + let gs: TilemapGridSize = ts.into(); + assert_eq!(gs, TilemapGridSize::new(16.0, 32.0)); + } + + #[test] + fn tilemap_spacing_zero_is_zero() { + assert_eq!(TilemapSpacing::zero(), TilemapSpacing::new(0.0, 0.0)); + } + + #[test] + fn defaults_match_contract() { + let settings = TilemapRenderSettings::default(); + assert_eq!(settings.render_chunk_size, CHUNK_SIZE_2D); + assert!(!settings.y_sort); } } diff --git a/src/render/chunk.rs b/src/render/chunk.rs index f4b9aa66..f8484e4a 100644 --- a/src/render/chunk.rs +++ b/src/render/chunk.rs @@ -1,29 +1,31 @@ -use std::hash::{Hash, Hasher}; +use std::hash::{ Hash, Hasher }; use bevy::platform::collections::HashMap; use bevy::render::render_asset::RenderAssetUsages; use bevy::render::render_resource::Buffer; -use bevy::render::{mesh::BaseMeshPipelineKey, primitives::Aabb}; -use bevy::{math::Mat4, render::mesh::PrimitiveTopology}; +use bevy::render::{ mesh::BaseMeshPipelineKey, primitives::Aabb }; +use bevy::{ math::Mat4, render::mesh::PrimitiveTopology }; use bevy::{ - math::{UVec2, UVec3, UVec4, Vec2, Vec3Swizzles, Vec4, Vec4Swizzles}, - prelude::{Component, Entity, GlobalTransform, Mesh}, + math::{ UVec2, UVec3, UVec4, Vec2, Vec3Swizzles, Vec4, Vec4Swizzles }, + prelude::{ Component, Entity, GlobalTransform, Mesh }, render::{ - mesh::{Indices, RenderMesh, RenderMeshBufferInfo, VertexAttributeValues}, - render_resource::{BufferInitDescriptor, BufferUsages, ShaderType}, + mesh::{ Indices, RenderMesh, RenderMeshBufferInfo, VertexAttributeValues }, + render_resource::{ BufferInitDescriptor, BufferUsages, ShaderType }, renderer::RenderDevice, }, }; use bevy::{ - prelude::{InheritedVisibility, Resource, Transform}, + prelude::{ InheritedVisibility, Resource, Transform }, render::mesh::MeshVertexBufferLayouts, }; -use crate::prelude::helpers::transform::{chunk_aabb, chunk_index_to_world_space}; +use crate::prelude::helpers::transform::{ chunk_aabb, chunk_index_to_world_space }; use crate::render::extract::ExtractedFrustum; use crate::{ - FrustumCulling, TilemapGridSize, TilemapTileSize, - map::{TilemapSize, TilemapTexture, TilemapType}, + FrustumCulling, + TilemapGridSize, + TilemapTileSize, + map::{ TilemapSize, TilemapTexture, TilemapType }, tiles::TilePos, }; @@ -59,12 +61,11 @@ impl RenderChunk2dStorage { visibility: &InheritedVisibility, frustum_culling: &FrustumCulling, render_size: RenderChunkSize, - y_sort: bool, + y_sort: bool ) -> &mut RenderChunk2d { let pos = position.xyz(); - self.entity_to_chunk_tile - .insert(tile_entity, (position.w, pos, tile_pos)); + self.entity_to_chunk_tile.insert(tile_entity, (position.w, pos, tile_pos)); let chunk_storage = if self.chunks.contains_key(&position.w) { self.chunks.get_mut(&position.w).unwrap() @@ -96,7 +97,7 @@ impl RenderChunk2dStorage { visibility.get(), **frustum_culling, render_size, - y_sort, + y_sort ); self.entity_to_chunk.insert(chunk_entity, pos); chunk_storage.insert(pos, chunk); @@ -163,9 +164,7 @@ impl RenderChunk2dStorage { } pub fn iter_mut(&mut self) -> impl Iterator { - self.chunks - .iter_mut() - .flat_map(|(_, x)| x.iter_mut().map(|x| x.1)) + self.chunks.iter_mut().flat_map(|(_, x)| x.iter_mut().map(|x| x.1)) } pub fn remove_map(&mut self, entity: Entity) { @@ -243,7 +242,7 @@ impl RenderChunk2d { visible: bool, frustum_culling: bool, render_size: RenderChunkSize, - y_sort: bool, + y_sort: bool ) -> Self { let position = chunk_index_to_world_space(index.xy(), size_in_tiles, &grid_size, &map_type); let local_transform = Transform::from_translation(position.extend(0.0)); @@ -269,7 +268,7 @@ impl RenderChunk2d { transform_matrix, mesh: Mesh::new( bevy::render::render_resource::PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + RenderAssetUsages::default() ), vertex_buffer: None, index_buffer: None, @@ -324,7 +323,7 @@ impl RenderChunk2d { global_transform: Transform, grid_size: TilemapGridSize, tile_size: TilemapTileSize, - map_type: TilemapType, + map_type: TilemapType ) { let mut dirty_local_transform = false; @@ -337,7 +336,7 @@ impl RenderChunk2d { self.index.xy(), self.size_in_tiles, &self.grid_size, - &self.map_type, + &self.map_type ); self.local_transform = Transform::from_translation(self.position.extend(0.0)); @@ -347,7 +346,7 @@ impl RenderChunk2d { self.size_in_tiles, &self.grid_size, &self.tile_size, - &self.map_type, + &self.map_type ); } @@ -366,15 +365,16 @@ impl RenderChunk2d { pub fn prepare( &mut self, device: &RenderDevice, - mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, + mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts ) { if self.dirty_mesh { - let size = ((self.size_in_tiles.x * self.size_in_tiles.y) * 4) as usize; + let size = (self.size_in_tiles.x * self.size_in_tiles.y * 4) as usize; let mut positions: Vec<[f32; 4]> = Vec::with_capacity(size); let mut textures: Vec<[f32; 4]> = Vec::with_capacity(size); let mut colors: Vec<[f32; 4]> = Vec::with_capacity(size); - let mut indices: Vec = - Vec::with_capacity(((self.size_in_tiles.x * self.size_in_tiles.y) * 6) as usize); + let mut indices: Vec = Vec::with_capacity( + (self.size_in_tiles.x * self.size_in_tiles.y * 6) as usize + ); let mut i = 0; @@ -398,8 +398,7 @@ impl RenderChunk2d { // X + 1, Y //[tile_pos.x + 1.0, tile_pos.y, animation_speed], position, - ] - .into_iter(), + ].into_iter() ); colors.extend(std::iter::repeat_n(tile.color, 4)); @@ -422,46 +421,50 @@ impl RenderChunk2d { self.mesh.insert_attribute( crate::render::ATTRIBUTE_POSITION, - VertexAttributeValues::Float32x4(positions), + VertexAttributeValues::Float32x4(positions) ); self.mesh.insert_attribute( crate::render::ATTRIBUTE_TEXTURE, - VertexAttributeValues::Float32x4(textures), + VertexAttributeValues::Float32x4(textures) ); self.mesh.insert_attribute( crate::render::ATTRIBUTE_COLOR, - VertexAttributeValues::Float32x4(colors), + VertexAttributeValues::Float32x4(colors) ); self.mesh.insert_indices(Indices::U32(indices)); let vertex_buffer_data = self.mesh.create_packed_vertex_buffer_data(); - let vertex_buffer = device.create_buffer_with_data(&BufferInitDescriptor { - usage: BufferUsages::VERTEX, - label: Some("Mesh Vertex Buffer"), - contents: &vertex_buffer_data, - }); + let vertex_buffer = device.create_buffer_with_data( + &(BufferInitDescriptor { + usage: BufferUsages::VERTEX, + label: Some("Mesh Vertex Buffer"), + contents: &vertex_buffer_data, + }) + ); - let index_buffer = device.create_buffer_with_data(&BufferInitDescriptor { - usage: BufferUsages::INDEX, - contents: self.mesh.get_index_buffer_bytes().unwrap(), - label: Some("Mesh Index Buffer"), - }); + let index_buffer = device.create_buffer_with_data( + &(BufferInitDescriptor { + usage: BufferUsages::INDEX, + contents: self.mesh.get_index_buffer_bytes().unwrap(), + label: Some("Mesh Index Buffer"), + }) + ); let buffer_info = RenderMeshBufferInfo::Indexed { count: self.mesh.indices().unwrap().len() as u32, index_format: self.mesh.indices().unwrap().into(), }; - let mesh_vertex_buffer_layout = self - .mesh - .get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts); + let mesh_vertex_buffer_layout = self.mesh.get_mesh_vertex_buffer_layout( + mesh_vertex_buffer_layouts + ); self.render_mesh = Some(RenderMesh { vertex_count: self.mesh.count_vertices() as u32, buffer_info, morph_targets: None, layout: mesh_vertex_buffer_layout, key_bits: BaseMeshPipelineKey::from_primitive_topology( - PrimitiveTopology::TriangleList, + PrimitiveTopology::TriangleList ), }); self.vertex_buffer = Some(vertex_buffer); diff --git a/src/render/draw.rs b/src/render/draw.rs index a31bf9f3..005159a4 100644 --- a/src/render/draw.rs +++ b/src/render/draw.rs @@ -2,14 +2,11 @@ use std::marker::PhantomData; use bevy::{ core_pipeline::core_2d::Transparent2d, - ecs::system::{ - SystemParamItem, - lifetimeless::{Read, SQuery, SRes}, - }, + ecs::system::{ SystemParamItem, lifetimeless::{ Read, SQuery, SRes } }, math::UVec4, render::{ mesh::RenderMeshBufferInfo, - render_phase::{RenderCommand, RenderCommandResult, TrackedRenderPass}, + render_phase::{ RenderCommand, RenderCommandResult, TrackedRenderPass }, render_resource::PipelineCache, view::ViewUniformOffset, }, @@ -20,10 +17,10 @@ use crate::map::TilemapId; use super::{ DynamicUniformIndex, - chunk::{ChunkId, RenderChunk2dStorage, TilemapUniformData}, - material::{MaterialTilemap, MaterialTilemapHandle, RenderMaterialsTilemap}, + chunk::{ ChunkId, RenderChunk2dStorage, TilemapUniformData }, + material::{ MaterialTilemap, MaterialTilemapHandle, RenderMaterialsTilemap }, prepare::MeshUniform, - queue::{ImageBindGroups, TilemapViewBindGroup, TransformBindGroup}, + queue::{ ImageBindGroups, TilemapViewBindGroup, TransformBindGroup }, }; pub struct SetMeshViewBindGroup; @@ -37,7 +34,7 @@ impl RenderCommand for SetMeshViewBindGroup { (view_uniform, pbr_view_bind_group): (&'w ViewUniformOffset, &'w TilemapViewBindGroup), _entity: Option<()>, _param: (), - pass: &mut TrackedRenderPass<'w>, + pass: &mut TrackedRenderPass<'w> ) -> RenderCommandResult { pass.set_bind_group(I, &pbr_view_bind_group.value, &[view_uniform.offset]); @@ -57,12 +54,11 @@ impl RenderCommand for SetTransformBindGroup { fn render<'w>( _item: &Transparent2d, _view: (), - uniform_indices: Option<( - &'w DynamicUniformIndex, - &'w DynamicUniformIndex, - )>, + uniform_indices: Option< + (&'w DynamicUniformIndex, &'w DynamicUniformIndex) + >, transform_bind_group: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, + pass: &mut TrackedRenderPass<'w> ) -> RenderCommandResult { let Some((transform_index, tilemap_index)) = uniform_indices else { return RenderCommandResult::Skip; @@ -71,7 +67,7 @@ impl RenderCommand for SetTransformBindGroup { pass.set_bind_group( I, &transform_bind_group.into_inner().value, - &[transform_index.index(), tilemap_index.index()], + &[transform_index.index(), tilemap_index.index()] ); RenderCommandResult::Success @@ -89,7 +85,7 @@ impl RenderCommand for SetTextureBindGroup { _view: (), texture: Option<&'w TilemapTexture>, image_bind_groups: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, + pass: &mut TrackedRenderPass<'w> ) -> RenderCommandResult { let Some(texture) = texture else { return RenderCommandResult::Skip; @@ -113,12 +109,9 @@ impl RenderCommand for SetItemPipeline { _view: (), _entity: Option<()>, pipeline_cache: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, + pass: &mut TrackedRenderPass<'w> ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache - .into_inner() - .get_render_pipeline(item.pipeline) - { + if let Some(pipeline) = pipeline_cache.into_inner().get_render_pipeline(item.pipeline) { pass.set_render_pipeline(pipeline); RenderCommandResult::Success } else { @@ -146,12 +139,8 @@ pub type DrawTilemapMaterial = ( pub struct SetMaterialBindGroup(PhantomData); impl RenderCommand - for SetMaterialBindGroup -{ - type Param = ( - SRes>, - SQuery<&'static MaterialTilemapHandle>, - ); +for SetMaterialBindGroup { + type Param = (SRes>, SQuery<&'static MaterialTilemapHandle>); type ViewQuery = (); type ItemQuery = Read; #[inline] @@ -160,17 +149,14 @@ impl RenderCommand _view: (), id: Option<&'w TilemapId>, (material_bind_groups, material_handles): SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, + pass: &mut TrackedRenderPass<'w> ) -> RenderCommandResult { let Some(id) = id else { return RenderCommandResult::Skip; }; if let Ok(material_handle) = material_handles.get(id.0) { - let bind_group = material_bind_groups - .into_inner() - .get(&material_handle.id()) - .unwrap(); + let bind_group = material_bind_groups.into_inner().get(&material_handle.id()).unwrap(); pass.set_bind_group(I, &bind_group.bind_group, &[]); } @@ -189,33 +175,31 @@ impl RenderCommand for DrawMesh { _view: (), ids: Option<(&'w ChunkId, &'w TilemapId)>, chunk_storage: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, + pass: &mut TrackedRenderPass<'w> ) -> RenderCommandResult { let Some((chunk_id, tilemap_id)) = ids else { return RenderCommandResult::Skip; }; - if let Some(chunk) = chunk_storage.into_inner().get(&UVec4::new( - chunk_id.0.x, - chunk_id.0.y, - chunk_id.0.z, - tilemap_id.0.index(), - )) { - if let (Some(render_mesh), Some(vertex_buffer), Some(index_buffer)) = ( - &chunk.render_mesh, - &chunk.vertex_buffer, - &chunk.index_buffer, - ) { + if + let Some(chunk) = chunk_storage + .into_inner() + .get(&UVec4::new(chunk_id.0.x, chunk_id.0.y, chunk_id.0.z, tilemap_id.0.index())) + { + if + let (Some(render_mesh), Some(vertex_buffer), Some(index_buffer)) = ( + &chunk.render_mesh, + &chunk.vertex_buffer, + &chunk.index_buffer, + ) + { if render_mesh.vertex_count == 0 { return RenderCommandResult::Skip; } pass.set_vertex_buffer(0, vertex_buffer.slice(..)); match &render_mesh.buffer_info { - RenderMeshBufferInfo::Indexed { - index_format, - count, - } => { + RenderMeshBufferInfo::Indexed { index_format, count } => { pass.set_index_buffer(index_buffer.slice(..), 0, *index_format); pass.draw_indexed(0..*count, 0, 0..1); } diff --git a/src/render/extract.rs b/src/render/extract.rs index 3e2bdd92..d4660d79 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -3,8 +3,8 @@ use bevy::{ platform::collections::HashMap, prelude::*, render::Extract, - render::primitives::{Aabb, Frustum}, - render::render_resource::{FilterMode, TextureFormat}, + render::primitives::{ Aabb, Frustum }, + render::render_resource::{ FilterMode, TextureFormat }, render::sync_world::RenderEntity, }; @@ -17,10 +17,15 @@ use crate::tiles::TilePosOld; use crate::{ FrustumCulling, map::{ - TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, - TilemapTileSize, TilemapType, + TilemapId, + TilemapSize, + TilemapSpacing, + TilemapTexture, + TilemapTextureSize, + TilemapTileSize, + TilemapType, }, - tiles::{TileColor, TileFlip, TilePos, TileTextureIndex, TileVisible}, + tiles::{ TileColor, TileFlip, TilePos, TileTextureIndex, TileVisible }, }; use super::chunk::PackedTileData; @@ -79,17 +84,19 @@ impl ExtractedTilemapTexture { tile_size: TilemapTileSize, tile_spacing: TilemapSpacing, filtering: FilterMode, - image_assets: &Res>, + image_assets: &Res> ) -> ExtractedTilemapTexture { let (tile_count, texture_size, format) = match &texture { TilemapTexture::Single(handle) => { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); + let image = image_assets + .get(handle) + .expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!" + ); let texture_size: TilemapTextureSize = image.size_f32().into(); - let tile_count_x = ((texture_size.x) / (tile_size.x + tile_spacing.x)).floor(); - let tile_count_y = ((texture_size.y) / (tile_size.y + tile_spacing.y)).floor(); + let tile_count_x = (texture_size.x / (tile_size.x + tile_spacing.x)).floor(); + let tile_count_y = (texture_size.y / (tile_size.y + tile_spacing.y)).floor(); ( (tile_count_x * tile_count_y) as u32, texture_size, @@ -99,30 +106,31 @@ impl ExtractedTilemapTexture { #[cfg(not(feature = "atlas"))] TilemapTexture::Vector(handles) => { for handle in handles { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); + let image = image_assets + .get(handle) + .expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!" + ); let this_tile_size: TilemapTileSize = image.size_f32().into(); if this_tile_size != tile_size { panic!( "Expected all provided image assets to have size {tile_size:?}, \ - but found image with size: {this_tile_size:?}", + but found image with size: {this_tile_size:?}" ); } } let first_format = image_assets .get(handles.first().unwrap()) - .unwrap() - .texture_descriptor - .format; + .unwrap().texture_descriptor.format; for handle in handles { let image = image_assets.get(handle).unwrap(); if image.texture_descriptor.format != first_format { panic!( "Expected all provided image assets to have a format of: {:?} but found image with format: {:?}", - first_format, image.texture_descriptor.format + first_format, + image.texture_descriptor.format ); } } @@ -131,10 +139,12 @@ impl ExtractedTilemapTexture { } #[cfg(not(feature = "atlas"))] TilemapTexture::TextureContainer(image_handle) => { - let image = image_assets.get(image_handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); + let image = image_assets + .get(image_handle) + .expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!" + ); let tile_size: TilemapTileSize = image.size_f32().into(); ( image.texture_descriptor.array_layer_count(), @@ -170,8 +180,7 @@ pub struct ExtractedFrustum { impl ExtractedFrustum { pub fn intersects_obb(&self, aabb: &Aabb, transform_matrix: &Mat4) -> bool { - self.frustum - .intersects_obb(aabb, &Affine3A::from_mat4(*transform_matrix), true, false) + self.frustum.intersects_obb(aabb, &Affine3A::from_mat4(*transform_matrix), true, false) } } @@ -192,53 +201,59 @@ pub fn extract( &TileColor, Option<&AnimatedTile>, ), - Or<( - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - )>, - >, + Or< + ( + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + ) + > + > >, tilemap_query: Extract< - Query<( - &RenderEntity, - &GlobalTransform, - &TilemapTileSize, - &TilemapSpacing, - &TilemapGridSize, - &TilemapType, - &TilemapTexture, - &TilemapSize, - &InheritedVisibility, - &FrustumCulling, - &TilemapRenderSettings, - &TilemapAnchor, - )>, + Query< + ( + &RenderEntity, + &GlobalTransform, + &TilemapTileSize, + &TilemapSpacing, + &TilemapGridSize, + &TilemapType, + &TilemapTexture, + &TilemapSize, + &InheritedVisibility, + &FrustumCulling, + &TilemapRenderSettings, + &TilemapAnchor, + ) + > >, changed_tilemap_query: Extract< Query< Entity, - Or<( - Added, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - )>, - >, + Or< + ( + Added, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + ) + > + > >, camera_query: Extract>>, - images: Extract>>, + images: Extract>> ) { let mut extracted_tiles = Vec::new(); let mut extracted_tilemaps = >::default(); @@ -254,13 +269,12 @@ pub fn extract( flip, color, animated, - ) in changed_tiles_query.iter() - { + ) in changed_tiles_query.iter() { // flipping and rotation packed in bits // bit 0 : flip_x // bit 1 : flip_y // bit 2 : flip_d (anti diagonal) - let tile_flip_bits = flip.x as i32 | ((flip.y as i32) << 1) | ((flip.d as i32) << 2); + let tile_flip_bits = (flip.x as i32) | ((flip.y as i32) << 1) | ((flip.d as i32) << 2); let mut position = Vec4::new(tile_pos.x as f32, tile_pos.y as f32, 0.0, 0.0); let mut texture = Vec4::new(tile_texture.0 as f32, tile_flip_bits as f32, 0.0, 0.0); @@ -282,27 +296,24 @@ pub fn extract( let data = tilemap_query.get(tilemap_id.0).unwrap(); - extracted_tilemaps.insert( + extracted_tilemaps.insert(data.0.id(), ( data.0.id(), - ( - data.0.id(), - ExtractedTilemapBundle { - transform: *data.1, - tile_size: *data.2, - texture_size: TilemapTextureSize::default(), - spacing: *data.3, - grid_size: *data.4, - map_type: *data.5, - texture: data.6.clone_weak(), - map_size: *data.7, - visibility: *data.8, - frustum_culling: *data.9, - render_settings: *data.10, - changed: ChangedInMainWorld, - anchor: *data.11, - }, - ), - ); + ExtractedTilemapBundle { + transform: *data.1, + tile_size: *data.2, + texture_size: TilemapTextureSize::default(), + spacing: *data.3, + grid_size: *data.4, + map_type: *data.5, + texture: data.6.clone_weak(), + map_size: *data.7, + visibility: *data.8, + frustum_culling: *data.9, + render_settings: *data.10, + changed: ChangedInMainWorld, + anchor: *data.11, + }, + )); extracted_tiles.push(( render_entity.id(), ExtractedTileBundle { @@ -320,36 +331,47 @@ pub fn extract( for tilemap_entity in changed_tilemap_query.iter() { if let Ok(data) = tilemap_query.get(tilemap_entity) { - extracted_tilemaps.insert( + extracted_tilemaps.insert(data.0.id(), ( data.0.id(), - ( - data.0.id(), - ExtractedTilemapBundle { - transform: *data.1, - tile_size: *data.2, - texture_size: TilemapTextureSize::default(), - spacing: *data.3, - grid_size: *data.4, - map_type: *data.5, - texture: data.6.clone_weak(), - map_size: *data.7, - visibility: *data.8, - frustum_culling: *data.9, - render_settings: *data.10, - changed: ChangedInMainWorld, - anchor: *data.11, - }, - ), - ); + ExtractedTilemapBundle { + transform: *data.1, + tile_size: *data.2, + texture_size: TilemapTextureSize::default(), + spacing: *data.3, + grid_size: *data.4, + map_type: *data.5, + texture: data.6.clone_weak(), + map_size: *data.7, + visibility: *data.8, + frustum_culling: *data.9, + render_settings: *data.10, + changed: ChangedInMainWorld, + anchor: *data.11, + }, + )); } } - let extracted_tilemaps: Vec<_> = extracted_tilemaps.drain().map(|(_, val)| val).collect(); + let extracted_tilemaps: Vec<_> = extracted_tilemaps + .drain() + .map(|(_, val)| val) + .collect(); // Extracts tilemap textures. - for (render_entity, _, tile_size, tile_spacing, _, _, texture, _, _, _, _, _) in - tilemap_query.iter() - { + for ( + render_entity, + _, + tile_size, + tile_spacing, + _, + _, + texture, + _, + _, + _, + _, + _, + ) in tilemap_query.iter() { if texture.verify_ready(&images) { extracted_tilemap_textures.push(( render_entity.id(), @@ -360,18 +382,16 @@ pub fn extract( *tile_size, *tile_spacing, default_image_settings.0.min_filter.into(), - &images, + &images ), changed: ChangedInMainWorld, }, - )) + )); } } for (render_entity, frustum) in camera_query.iter() { - commands - .entity(render_entity.id()) - .insert(ExtractedFrustum { frustum: *frustum }); + commands.entity(render_entity.id()).insert(ExtractedFrustum { frustum: *frustum }); } commands.insert_batch(extracted_tiles); diff --git a/src/render/material.rs b/src/render/material.rs index 38808e35..eb562c1f 100644 --- a/src/render/material.rs +++ b/src/render/material.rs @@ -1,42 +1,57 @@ -use crate::prelude::{TilemapId, TilemapRenderSettings}; +use crate::prelude::{ TilemapId, TilemapRenderSettings }; use bevy::log::error; #[cfg(not(feature = "atlas"))] use bevy::render::renderer::RenderQueue; use bevy::{ core_pipeline::core_2d::Transparent2d, - ecs::system::{StaticSystemParam, SystemParamItem}, + ecs::system::{ StaticSystemParam, SystemParamItem }, math::FloatOrd, - platform::collections::{HashMap, HashSet}, + platform::collections::{ HashMap, HashSet }, prelude::*, reflect::TypePath, render::{ - Extract, Render, RenderApp, RenderSet, - extract_component::{ExtractComponent, ExtractComponentPlugin}, + Extract, + Render, + RenderApp, + RenderSet, + extract_component::{ ExtractComponent, ExtractComponentPlugin }, globals::GlobalsBuffer, render_asset::RenderAssets, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, ViewSortedRenderPhases, + AddRenderCommand, + DrawFunctions, + PhaseItemExtraIndex, + ViewSortedRenderPhases, }, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupEntry, BindGroupLayout, - BindingResource, OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, - ShaderRef, SpecializedRenderPipeline, SpecializedRenderPipelines, + AsBindGroup, + AsBindGroupError, + BindGroup, + BindGroupEntry, + BindGroupLayout, + BindingResource, + OwnedBindingResource, + PipelineCache, + RenderPipelineDescriptor, + ShaderRef, + SpecializedRenderPipeline, + SpecializedRenderPipelines, }, renderer::RenderDevice, texture::GpuImage, - view::{ExtractedView, RenderVisibleEntities, ViewUniforms}, + view::{ ExtractedView, RenderVisibleEntities, ViewUniforms }, }, }; -use std::{hash::Hash, marker::PhantomData}; +use std::{ hash::Hash, marker::PhantomData }; use super::{ ModifiedImageIds, - chunk::{ChunkId, RenderChunk2dStorage}, + chunk::{ ChunkId, RenderChunk2dStorage }, draw::DrawTilemapMaterial, - pipeline::{TilemapPipeline, TilemapPipelineKey}, + pipeline::{ TilemapPipeline, TilemapPipelineKey }, prepare, - queue::{ImageBindGroups, TilemapViewBindGroup}, + queue::{ ImageBindGroups, TilemapViewBindGroup }, }; #[cfg(not(feature = "atlas"))] @@ -68,20 +83,14 @@ pub struct MaterialTilemapKey { impl Eq for MaterialTilemapKey where M::Data: PartialEq {} -impl PartialEq for MaterialTilemapKey -where - M::Data: PartialEq, -{ +impl PartialEq for MaterialTilemapKey where M::Data: PartialEq { fn eq(&self, other: &Self) -> bool { - self.tilemap_pipeline_key == other.tilemap_pipeline_key - && self.bind_group_data == other.bind_group_data + self.tilemap_pipeline_key == other.tilemap_pipeline_key && + self.bind_group_data == other.bind_group_data } } -impl Clone for MaterialTilemapKey -where - M::Data: Clone, -{ +impl Clone for MaterialTilemapKey where M::Data: Clone { fn clone(&self) -> Self { Self { tilemap_pipeline_key: self.tilemap_pipeline_key, @@ -90,10 +99,7 @@ where } } -impl Hash for MaterialTilemapKey -where - M::Data: Hash, -{ +impl Hash for MaterialTilemapKey where M::Data: Hash { fn hash(&self, state: &mut H) { self.tilemap_pipeline_key.hash(state); self.bind_group_data.hash(state); @@ -136,13 +142,14 @@ impl Default for MaterialTilemapPlugin { } } -impl Plugin for MaterialTilemapPlugin -where - M::Data: PartialEq + Eq + Hash + Clone, +impl Plugin + for MaterialTilemapPlugin + where M::Data: PartialEq + Eq + Hash + Clone { fn build(&self, app: &mut App) { - app.init_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); + app.init_asset::().add_plugins( + ExtractComponentPlugin::>::extract_visible() + ); } fn finish(&self, app: &mut App) { @@ -156,22 +163,19 @@ where .add_systems(ExtractSchedule, extract_materials_tilemap::) .add_systems( Render, - prepare_materials_tilemap::.in_set(RenderSet::PrepareAssets), + prepare_materials_tilemap::.in_set(RenderSet::PrepareAssets) ) - .add_systems( - Render, - ( - // Ensure `queue_material_tilemap_meshes` runs after `prepare::prepare` because `prepare` calls `commands.spawn` with `ChunkId` - // and that data is then consumed by `queue_material_tilemap_mesh`. This is important because `prepare` is part of the `PrepareAssets` - // set. Bevy is loose on its expectation of when systems in the `PrepareAssets` set execute (for performance) and only needs them - // to run before the `Prepare` set (which is after Queue). This invites the possibility of an intermittent incorrect ordering dependent - // on the scheduler. - queue_material_tilemap_meshes:: - .in_set(RenderSet::Queue) - .after(prepare::prepare), - bind_material_tilemap_meshes::.in_set(RenderSet::PrepareBindGroups), - ), - ); + .add_systems(Render, ( + // Ensure `queue_material_tilemap_meshes` runs after `prepare::prepare` because `prepare` calls `commands.spawn` with `ChunkId` + // and that data is then consumed by `queue_material_tilemap_mesh`. This is important because `prepare` is part of the `PrepareAssets` + // set. Bevy is loose on its expectation of when systems in the `PrepareAssets` set execute (for performance) and only needs them + // to run before the `Prepare` set (which is after Queue). This invites the possibility of an intermittent incorrect ordering dependent + // on the scheduler. + queue_material_tilemap_meshes:: + .in_set(RenderSet::Queue) + .after(prepare::prepare), + bind_material_tilemap_meshes::.in_set(RenderSet::PrepareBindGroups), + )); } } } @@ -218,9 +222,9 @@ impl Clone for MaterialTilemapPipeline { } } -impl SpecializedRenderPipeline for MaterialTilemapPipeline -where - M::Data: PartialEq + Eq + Hash + Clone, +impl SpecializedRenderPipeline + for MaterialTilemapPipeline + where M::Data: PartialEq + Eq + Hash + Clone { type Key = MaterialTilemapKey; @@ -237,7 +241,7 @@ where self.tilemap_pipeline.view_layout.clone(), self.tilemap_pipeline.mesh_layout.clone(), self.tilemap_pipeline.material_layout.clone(), - self.material_tilemap_layout.clone(), + self.material_tilemap_layout.clone() ]; M::specialize(&mut descriptor, key); @@ -286,7 +290,7 @@ impl Default for RenderMaterialsTilemap { fn extract_materials_tilemap( mut commands: Commands, mut events: Extract>>, - assets: Extract>>, + assets: Extract>> ) { let mut changed_assets = >::default(); let mut removed = Vec::new(); @@ -299,7 +303,9 @@ fn extract_materials_tilemap( changed_assets.remove(id); removed.push(*id); } - _ => continue, + _ => { + continue; + } } } @@ -337,7 +343,7 @@ fn prepare_materials_tilemap( mut render_materials: ResMut>, render_device: Res, pipeline: Res>, - mut param: StaticSystemParam, + mut param: StaticSystemParam ) { let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (handle, material) in queued_assets { @@ -387,10 +393,9 @@ fn prepare_material_tilemap( material: &M, render_device: &RenderDevice, pipeline: &MaterialTilemapPipeline, - param: &mut SystemParamItem, + param: &mut SystemParamItem ) -> Result, AsBindGroupError> { - let prepared = - material.as_bind_group(&pipeline.material_tilemap_layout, render_device, param)?; + let prepared = material.as_bind_group(&pipeline.material_tilemap_layout, render_device, param)?; Ok(PreparedMaterialTilemap { bindings: prepared.bindings.0, bind_group: prepared.bind_group, @@ -421,9 +426,9 @@ pub fn queue_material_tilemap_meshes( ResMut, Res, ), - mut transparent_render_phases: ResMut>, -) where - M::Data: PartialEq + Eq + Hash + Clone, + mut transparent_render_phases: ResMut> +) + where M::Data: PartialEq + Eq + Hash + Clone { #[cfg(not(feature = "atlas"))] texture_array_cache.queue(&_render_device, &render_queue, &gpu_images); @@ -437,8 +442,9 @@ pub fn queue_material_tilemap_meshes( } for (view, msaa, visible_entities) in views.iter_mut() { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + let Some(transparent_phase) = transparent_render_phases.get_mut( + &view.retained_view_entity + ) else { continue; }; @@ -448,10 +454,11 @@ pub fn queue_material_tilemap_meshes( .unwrap(); for (entity, chunk_id, transform, tilemap_id) in standard_tilemap_meshes.iter() { - if !visible_entities - .get::() - .iter() - .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) + if + !visible_entities + .get::() + .iter() + .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) { continue; } @@ -463,12 +470,11 @@ pub fn queue_material_tilemap_meshes( continue; }; - if let Some(chunk) = chunk_storage.get(&UVec4::new( - chunk_id.0.x, - chunk_id.0.y, - chunk_id.0.z, - tilemap_id.0.index(), - )) { + if + let Some(chunk) = chunk_storage.get( + &UVec4::new(chunk_id.0.x, chunk_id.0.y, chunk_id.0.z, tilemap_id.0.index()) + ) + { #[cfg(not(feature = "atlas"))] if !texture_array_cache.contains(&chunk.texture) { continue; @@ -491,13 +497,13 @@ pub fn queue_material_tilemap_meshes( MaterialTilemapKey { tilemap_pipeline_key: key, bind_group_data: material.key.clone(), - }, + } ); let z = if chunk.y_sort { - transform.translation.z - + (1.0 - - (transform.translation.y - / (chunk.map_size.y as f32 * chunk.tile_size.y))) + transform.translation.z + + (1.0 - + transform.translation.y / + ((chunk.map_size.y as f32) * chunk.tile_size.y)) } else { transform.translation.z }; @@ -537,9 +543,9 @@ pub fn bind_material_tilemap_meshes( #[cfg(not(feature = "atlas"))] (mut texture_array_cache, render_queue): ( ResMut, Res, - ), -) where - M::Data: PartialEq + Eq + Hash + Clone, + ) +) + where M::Data: PartialEq + Eq + Hash + Clone { #[cfg(not(feature = "atlas"))] texture_array_cache.queue(&render_device, &render_queue, &gpu_images); @@ -548,10 +554,12 @@ pub fn bind_material_tilemap_meshes( return; } - if let (Some(view_binding), Some(globals)) = ( - view_uniforms.uniforms.binding(), - globals_buffer.buffer.binding(), - ) { + if + let (Some(view_binding), Some(globals)) = ( + view_uniforms.uniforms.binding(), + globals_buffer.buffer.binding(), + ) + { for (entity, visible_entities) in views.iter_mut() { let view_bind_group = render_device.create_bind_group( Some("tilemap_view_bind_group"), @@ -565,7 +573,7 @@ pub fn bind_material_tilemap_meshes( binding: 1, resource: globals.clone(), }, - ], + ] ); commands.entity(entity).insert(TilemapViewBindGroup { @@ -573,10 +581,11 @@ pub fn bind_material_tilemap_meshes( }); for (chunk_id, tilemap_id) in standard_tilemap_meshes.iter() { - if !visible_entities - .get::() - .iter() - .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) + if + !visible_entities + .get::() + .iter() + .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) { continue; } @@ -586,14 +595,13 @@ pub fn bind_material_tilemap_meshes( }; if render_materials.get(&material_handle.id()).is_none() { continue; - }; + } - if let Some(chunk) = chunk_storage.get(&UVec4::new( - chunk_id.0.x, - chunk_id.0.y, - chunk_id.0.z, - tilemap_id.0.index(), - )) { + if + let Some(chunk) = chunk_storage.get( + &UVec4::new(chunk_id.0.x, chunk_id.0.y, chunk_id.0.z, tilemap_id.0.index()) + ) + { #[cfg(not(feature = "atlas"))] if !texture_array_cache.contains(&chunk.texture) { continue; @@ -621,16 +629,16 @@ pub fn bind_material_tilemap_meshes( binding: 1, resource: BindingResource::Sampler(&gpu_image.sampler), }, - ], + ] ) }; if modified_image_ids.is_texture_modified(&chunk.texture) { - image_bind_groups - .values - .insert(chunk.texture.clone_weak(), create_bind_group()); + image_bind_groups.values.insert( + chunk.texture.clone_weak(), + create_bind_group() + ); } else { - image_bind_groups - .values + image_bind_groups.values .entry(chunk.texture.clone_weak()) .or_insert_with(create_bind_group); } diff --git a/src/render/mod.rs b/src/render/mod.rs index 4ea203c6..914a9e82 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,18 +1,20 @@ use std::marker::PhantomData; use bevy::{ - asset::{load_internal_asset, weak_handle}, + asset::{ load_internal_asset, weak_handle }, core_pipeline::core_2d::Transparent2d, image::ImageSamplerDescriptor, platform::collections::HashSet, prelude::*, render::{ - Render, RenderApp, RenderSet, - extract_component::{ExtractComponent, ExtractComponentPlugin}, - extract_resource::{ExtractResource, extract_resource}, + Render, + RenderApp, + RenderSet, + extract_component::{ ExtractComponent, ExtractComponentPlugin }, + extract_resource::{ ExtractResource, extract_resource }, mesh::MeshVertexAttribute, render_phase::AddRenderCommand, - render_resource::{FilterMode, SpecializedRenderPipelines, VertexFormat}, + render_resource::{ FilterMode, SpecializedRenderPipelines, VertexFormat }, sync_world::RenderEntity, }, }; @@ -23,22 +25,19 @@ use bevy::render::renderer::RenderDevice; use bevy::render::texture::GpuImage; use extract::remove_changed; -use crate::{ - TilemapFirstSet, - tiles::{TilePos, TileStorage}, -}; +use crate::{ TilemapFirstSet, tiles::{ TilePos, TileStorage } }; use crate::{ prelude::TilemapTexture, render::{ - material::{MaterialTilemapPlugin, StandardTilemapMaterial}, - prepare::{MeshUniformResource, TilemapUniformResource}, + material::{ MaterialTilemapPlugin, StandardTilemapMaterial }, + prepare::{ MeshUniformResource, TilemapUniformResource }, }, }; use self::{ chunk::RenderChunk2dStorage, draw::DrawTilemap, - pipeline::{TILEMAP_SHADER_FRAGMENT, TILEMAP_SHADER_VERTEX, TilemapPipeline}, + pipeline::{ TILEMAP_SHADER_FRAGMENT, TILEMAP_SHADER_VERTEX, TilemapPipeline }, queue::ImageBindGroups, }; @@ -87,7 +86,7 @@ impl RenderChunkSize { #[inline] pub fn map_tile_to_chunk_tile(&self, tile_position: &TilePos, chunk_position: &UVec2) -> UVec2 { let tile_pos: UVec2 = tile_position.into(); - tile_pos - (*chunk_position * self.0) + tile_pos - *chunk_position * self.0 } } @@ -104,8 +103,9 @@ pub const ROW_HEX: Handle = weak_handle!("04a9c819-45e0-42d3-9cea-8b9e54 pub const ROW_ODD_HEX: Handle = weak_handle!("9962f145-0937-44f4-98f5-0cd5deadd643"); pub const STAGGERED_ISO: Handle = weak_handle!("da349823-a307-44a5-ab78-6276c7cb582a"); pub const SQUARE: Handle = weak_handle!("6db56afb-a562-4e3c-b459-486a6d5c12ae"); -pub const TILEMAP_VERTEX_OUTPUT: Handle = - weak_handle!("49b568da-6c5a-4936-a3c8-d5dd6b894f92"); +pub const TILEMAP_VERTEX_OUTPUT: Handle = weak_handle!( + "49b568da-6c5a-4936-a3c8-d5dd6b894f92" +); impl Plugin for TilemapRenderingPlugin { fn build(&self, app: &mut App) { @@ -126,18 +126,23 @@ impl Plugin for TilemapRenderingPlugin { .resource_mut::>() .insert( Handle::::default().id(), - StandardTilemapMaterial::default(), + StandardTilemapMaterial::default() ); - app.init_resource::() - .add_systems(Update, collect_modified_image_asset_events); + app.init_resource::().add_systems( + Update, + collect_modified_image_asset_events + ); } fn finish(&self, app: &mut App) { - let sampler = app.get_added_plugins::().first().map_or_else( - || ImagePlugin::default_nearest().default_sampler, - |plugin| plugin.default_sampler.clone(), - ); + let sampler = app + .get_added_plugins::() + .first() + .map_or_else( + || ImagePlugin::default_nearest().default_sampler, + |plugin| plugin.default_sampler.clone() + ); load_internal_asset!( app, @@ -146,62 +151,27 @@ impl Plugin for TilemapRenderingPlugin { Shader::from_wgsl ); - load_internal_asset!( - app, - COLUMN_HEX, - "shaders/column_hex.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, COLUMN_HEX, "shaders/column_hex.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - COLUMN_ODD_HEX, - "shaders/column_odd_hex.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, COLUMN_ODD_HEX, "shaders/column_odd_hex.wgsl", Shader::from_wgsl); load_internal_asset!(app, COMMON, "shaders/common.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - DIAMOND_ISO, - "shaders/diamond_iso.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, DIAMOND_ISO, "shaders/diamond_iso.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - ROW_EVEN_HEX, - "shaders/row_even_hex.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, ROW_EVEN_HEX, "shaders/row_even_hex.wgsl", Shader::from_wgsl); load_internal_asset!(app, ROW_HEX, "shaders/row_hex.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - ROW_ODD_HEX, - "shaders/row_odd_hex.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, ROW_ODD_HEX, "shaders/row_odd_hex.wgsl", Shader::from_wgsl); load_internal_asset!(app, ROW_HEX, "shaders/row_hex.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - MESH_OUTPUT, - "shaders/mesh_output.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, MESH_OUTPUT, "shaders/mesh_output.wgsl", Shader::from_wgsl); load_internal_asset!(app, SQUARE, "shaders/square.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - STAGGERED_ISO, - "shaders/staggered_iso.wgsl", - Shader::from_wgsl - ); + load_internal_asset!(app, STAGGERED_ISO, "shaders/staggered_iso.wgsl", Shader::from_wgsl); load_internal_asset!( app, @@ -226,7 +196,9 @@ impl Plugin for TilemapRenderingPlugin { let render_app = match app.get_sub_app_mut(RenderApp) { Some(render_app) => render_app, - None => return, + None => { + return; + } }; render_app.init_resource::(); @@ -240,19 +212,16 @@ impl Plugin for TilemapRenderingPlugin { render_app .insert_resource(DefaultSampler(sampler)) .insert_resource(RenderChunk2dStorage::default()) - .add_systems( - ExtractSchedule, - (extract::extract, extract_resource::), - ) + .add_systems(ExtractSchedule, (extract::extract, extract_resource::)) .add_systems( Render, (prepare::prepare_removal, prepare::prepare) .chain() - .in_set(RenderSet::PrepareAssets), + .in_set(RenderSet::PrepareAssets) ) .add_systems( Render, - queue::queue_transform_bind_group.in_set(RenderSet::PrepareBindGroups), + queue::queue_transform_bind_group.in_set(RenderSet::PrepareBindGroups) ) .add_systems(Render, remove_changed.in_set(RenderSet::Cleanup)) .init_resource::() @@ -267,11 +236,11 @@ impl Plugin for TilemapRenderingPlugin { pub fn set_texture_to_copy_src( mut images: ResMut>, - texture_query: Query<&TilemapTexture>, + texture_query: Query<&TilemapTexture> ) { // quick and dirty, run this for all textures anytime a texture component is created. for texture in texture_query.iter() { - texture.set_images_to_copy_src(&mut images) + texture.set_images_to_copy_src(&mut images); } } @@ -289,15 +258,23 @@ impl DynamicUniformIndex { } } -pub const ATTRIBUTE_POSITION: MeshVertexAttribute = - MeshVertexAttribute::new("Position", 229221259, VertexFormat::Float32x4); -pub const ATTRIBUTE_TEXTURE: MeshVertexAttribute = - MeshVertexAttribute::new("Texture", 222922753, VertexFormat::Float32x4); -pub const ATTRIBUTE_COLOR: MeshVertexAttribute = - MeshVertexAttribute::new("Color", 231497124, VertexFormat::Float32x4); +pub const ATTRIBUTE_POSITION: MeshVertexAttribute = MeshVertexAttribute::new( + "Position", + 229221259, + VertexFormat::Float32x4 +); +pub const ATTRIBUTE_TEXTURE: MeshVertexAttribute = MeshVertexAttribute::new( + "Texture", + 222922753, + VertexFormat::Float32x4 +); +pub const ATTRIBUTE_COLOR: MeshVertexAttribute = MeshVertexAttribute::new( + "Color", + 231497124, + VertexFormat::Float32x4 +); #[derive(Component, ExtractComponent, Clone)] - pub struct RemovedTileEntity(pub RenderEntity); #[derive(Component, ExtractComponent, Clone)] @@ -306,7 +283,7 @@ pub struct RemovedMapEntity(pub RenderEntity); fn on_remove_tile( trigger: Trigger, mut commands: Commands, - query: Query<&RenderEntity>, + query: Query<&RenderEntity> ) { if let Ok(render_entity) = query.get(trigger.target()) { commands.spawn(RemovedTileEntity(*render_entity)); @@ -316,7 +293,7 @@ fn on_remove_tile( fn on_remove_tilemap( trigger: Trigger, mut commands: Commands, - query: Query<&RenderEntity>, + query: Query<&RenderEntity> ) { if let Ok(render_entity) = query.get(trigger.target()) { commands.spawn(RemovedMapEntity(*render_entity)); @@ -326,7 +303,7 @@ fn on_remove_tilemap( fn clear_removed( mut commands: Commands, removed_query: Query>, - removed_map_query: Query>, + removed_map_query: Query> ) { for entity in removed_query.iter() { commands.entity(entity).despawn(); @@ -342,7 +319,7 @@ fn prepare_textures( render_device: Res, mut texture_array_cache: ResMut, extracted_tilemap_textures: Query<&ExtractedTilemapTexture>, - render_images: Res>, + render_images: Res> ) { for extracted_texture in extracted_tilemap_textures.iter() { texture_array_cache.add_extracted_texture(extracted_texture); @@ -370,14 +347,16 @@ impl ModifiedImageIds { /// them up into a convenient resource which can be extracted for rendering. pub fn collect_modified_image_asset_events( mut asset_events: EventReader>, - mut modified_image_ids: ResMut, + mut modified_image_ids: ResMut ) { modified_image_ids.0.clear(); for asset_event in asset_events.read() { let id = match asset_event { AssetEvent::Modified { id } => id, - _ => continue, + _ => { + continue; + } }; modified_image_ids.0.insert(*id); } diff --git a/src/render/pipeline.rs b/src/render/pipeline.rs index 138f7704..633cccb2 100644 --- a/src/render/pipeline.rs +++ b/src/render/pipeline.rs @@ -2,32 +2,60 @@ use bevy::{ asset::weak_handle, core_pipeline::core_2d::CORE_2D_DEPTH_FORMAT, image::BevyDefault, - prelude::{Component, FromWorld, Handle, Resource, Shader, World}, + prelude::{ Component, FromWorld, Handle, Resource, Shader, World }, render::{ globals::GlobalsUniform, render_resource::{ - BindGroupLayout, BindGroupLayoutEntry, BindingType, BlendComponent, BlendFactor, - BlendOperation, BlendState, BufferBindingType, ColorTargetState, ColorWrites, - CompareFunction, DepthBiasState, DepthStencilState, Face, FragmentState, FrontFace, - MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, - RenderPipelineDescriptor, SamplerBindingType, ShaderStages, ShaderType, - SpecializedRenderPipeline, StencilFaceState, StencilState, TextureFormat, - TextureSampleType, TextureViewDimension, VertexBufferLayout, VertexFormat, VertexState, + BindGroupLayout, + BindGroupLayoutEntry, + BindingType, + BlendComponent, + BlendFactor, + BlendOperation, + BlendState, + BufferBindingType, + ColorTargetState, + ColorWrites, + CompareFunction, + DepthBiasState, + DepthStencilState, + Face, + FragmentState, + FrontFace, + MultisampleState, + PolygonMode, + PrimitiveState, + PrimitiveTopology, + RenderPipelineDescriptor, + SamplerBindingType, + ShaderStages, + ShaderType, + SpecializedRenderPipeline, + StencilFaceState, + StencilState, + TextureFormat, + TextureSampleType, + TextureViewDimension, + VertexBufferLayout, + VertexFormat, + VertexState, VertexStepMode, }, renderer::RenderDevice, - view::{ViewTarget, ViewUniform}, + view::{ ViewTarget, ViewUniform }, }, }; -use crate::map::{HexCoordSystem, IsoCoordSystem, TilemapType}; +use crate::map::{ HexCoordSystem, IsoCoordSystem, TilemapType }; -use super::{chunk::TilemapUniformData, prepare::MeshUniform}; +use super::{ chunk::TilemapUniformData, prepare::MeshUniform }; -pub const TILEMAP_SHADER_VERTEX: Handle = - weak_handle!("915ef471-58b4-4431-acae-f38b41969a9e"); -pub const TILEMAP_SHADER_FRAGMENT: Handle = - weak_handle!("bf34308e-69df-4da8-b0ff-816042c716c8"); +pub const TILEMAP_SHADER_VERTEX: Handle = weak_handle!( + "915ef471-58b4-4431-acae-f38b41969a9e" +); +pub const TILEMAP_SHADER_FRAGMENT: Handle = weak_handle!( + "bf34308e-69df-4da8-b0ff-816042c716c8" +); #[derive(Clone, Resource)] pub struct TilemapPipeline { @@ -64,7 +92,7 @@ impl FromWorld for TilemapPipeline { }, count: None, }, - ], + ] ); let mesh_layout = render_device.create_bind_group_layout( @@ -92,7 +120,7 @@ impl FromWorld for TilemapPipeline { }, count: None, }, - ], + ] ); #[cfg(not(feature = "atlas"))] @@ -115,7 +143,7 @@ impl FromWorld for TilemapPipeline { ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, - ], + ] ); #[cfg(feature = "atlas")] @@ -138,7 +166,7 @@ impl FromWorld for TilemapPipeline { ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, - ], + ] ); TilemapPipeline { @@ -166,18 +194,20 @@ impl SpecializedRenderPipeline for TilemapPipeline { let mesh_string = match key.map_type { TilemapType::Square => "SQUARE", - TilemapType::Isometric(coord_system) => match coord_system { - IsoCoordSystem::Diamond => "ISO_DIAMOND", - IsoCoordSystem::Staggered => "ISO_STAGGERED", - }, - TilemapType::Hexagon(coord_system) => match coord_system { - HexCoordSystem::Column => "COLUMN_HEX", - HexCoordSystem::ColumnEven => "COLUMN_EVEN_HEX", - HexCoordSystem::ColumnOdd => "COLUMN_ODD_HEX", - HexCoordSystem::Row => "ROW_HEX", - HexCoordSystem::RowEven => "ROW_EVEN_HEX", - HexCoordSystem::RowOdd => "ROW_ODD_HEX", - }, + TilemapType::Isometric(coord_system) => + match coord_system { + IsoCoordSystem::Diamond => "ISO_DIAMOND", + IsoCoordSystem::Staggered => "ISO_STAGGERED", + } + TilemapType::Hexagon(coord_system) => + match coord_system { + HexCoordSystem::Column => "COLUMN_HEX", + HexCoordSystem::ColumnEven => "COLUMN_EVEN_HEX", + HexCoordSystem::ColumnOdd => "COLUMN_ODD_HEX", + HexCoordSystem::Row => "ROW_HEX", + HexCoordSystem::RowEven => "ROW_EVEN_HEX", + HexCoordSystem::RowOdd => "ROW_ODD_HEX", + } }; shader_defs.push(mesh_string.into()); @@ -187,11 +217,13 @@ impl SpecializedRenderPipeline for TilemapPipeline { // Uv VertexFormat::Float32x4, // Color - VertexFormat::Float32x4, + VertexFormat::Float32x4 ]; - let vertex_layout = - VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); + let vertex_layout = VertexBufferLayout::from_vertex_formats( + VertexStepMode::Vertex, + formats + ); RenderPipelineDescriptor { vertex: VertexState { @@ -204,31 +236,33 @@ impl SpecializedRenderPipeline for TilemapPipeline { shader: TILEMAP_SHADER_FRAGMENT, shader_defs, entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, + targets: vec![ + Some(ColorTargetState { + format: if key.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() }, - }), - write_mask: ColorWrites::ALL, - })], + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrites::ALL, + }) + ], }), layout: vec![ self.view_layout.clone(), self.mesh_layout.clone(), - self.material_layout.clone(), + self.material_layout.clone() ], primitive: PrimitiveState { conservative: false, diff --git a/src/render/prepare.rs b/src/render/prepare.rs index 0d5a48d9..a85f1774 100644 --- a/src/render/prepare.rs +++ b/src/render/prepare.rs @@ -2,32 +2,37 @@ use std::marker::PhantomData; use crate::anchor::TilemapAnchor; use crate::map::{ - TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize, + TilemapId, + TilemapSize, + TilemapSpacing, + TilemapTexture, + TilemapTextureSize, + TilemapTileSize, TilemapType, }; use crate::prelude::TilemapRenderSettings; use crate::render::extract::ExtractedFrustum; -use crate::{FrustumCulling, prelude::TilemapGridSize, render::RenderChunkSize}; +use crate::{ FrustumCulling, prelude::TilemapGridSize, render::RenderChunkSize }; use bevy::log::trace; -use bevy::prelude::{InheritedVisibility, Resource, Transform, With}; +use bevy::prelude::{ InheritedVisibility, Resource, Transform, With }; use bevy::render::mesh::MeshVertexBufferLayouts; use bevy::render::sync_world::TemporaryRenderEntity; use bevy::{ - math::{Mat4, UVec4}, - prelude::{Commands, Component, Entity, GlobalTransform, Query, Res, ResMut, Vec2}, + math::{ Mat4, UVec4 }, + prelude::{ Commands, Component, Entity, GlobalTransform, Query, Res, ResMut, Vec2 }, render::{ - render_resource::{DynamicUniformBuffer, ShaderType}, - renderer::{RenderDevice, RenderQueue}, + render_resource::{ DynamicUniformBuffer, ShaderType }, + renderer::{ RenderDevice, RenderQueue }, }, }; use super::extract::ChangedInMainWorld; use super::{ DynamicUniformIndex, - chunk::{ChunkId, PackedTileData, RenderChunk2dStorage, TilemapUniformData}, - extract::{ExtractedTile, ExtractedTilemapTexture}, + chunk::{ ChunkId, PackedTileData, RenderChunk2dStorage, TilemapUniformData }, + extract::{ ExtractedTile, ExtractedTilemapTexture }, }; -use super::{RemovedMapEntity, RemovedTileEntity}; +use super::{ RemovedMapEntity, RemovedTileEntity }; #[derive(Resource, Default)] pub struct MeshUniformResource(pub DynamicUniformBuffer); @@ -63,13 +68,13 @@ pub(crate) fn prepare( &TilemapRenderSettings, &TilemapAnchor, ), - With, + With >, extracted_tilemap_textures: Query<&ExtractedTilemapTexture, With>, extracted_frustum_query: Query<&ExtractedFrustum>, render_device: Res, render_queue: Res, - mut mesh_vertex_buffer_layouts: ResMut, + mut mesh_vertex_buffer_layouts: ResMut ) { for tile in extracted_tiles.iter() { // First if the tile position has changed remove the tile from the old location. @@ -99,7 +104,7 @@ pub(crate) fn prepare( chunk_index.x, chunk_index.y, transform.translation().z as u32, - tile.tilemap_id.0.index(), + tile.tilemap_id.0.index() ); let in_chunk_tile_index = chunk_size.map_tile_to_chunk_tile(&tile.position, &chunk_index); @@ -120,7 +125,7 @@ pub(crate) fn prepare( visibility, frustum_culling, chunk_size, - tilemap_render_settings.y_sort, + tilemap_render_settings.y_sort ); chunk.set( &in_chunk_tile_index.into(), @@ -131,7 +136,7 @@ pub(crate) fn prepare( .extend(tile.tile.position.z) .extend(tile.tile.position.w), ..tile.tile - }), + }) ); } @@ -150,8 +155,7 @@ pub(crate) fn prepare( frustum_culling, _, anchor, - ) in extracted_tilemaps.iter() - { + ) in extracted_tilemaps.iter() { let chunks = chunk_storage.get_chunk_storage(&UVec4::new(0, 0, 0, entity.index())); for chunk in chunks.values_mut() { chunk.texture = texture.clone(); @@ -179,8 +183,9 @@ pub(crate) fn prepare( for tilemap in extracted_tilemap_textures.iter() { let texture_size: Vec2 = tilemap.texture_size.into(); - let chunks = - chunk_storage.get_chunk_storage(&UVec4::new(0, 0, 0, tilemap.tilemap_id.0.index())); + let chunks = chunk_storage.get_chunk_storage( + &UVec4::new(0, 0, 0, tilemap.tilemap_id.0.index()) + ); for chunk in chunks.values_mut() { chunk.texture_size = texture_size; } @@ -195,10 +200,9 @@ pub(crate) fn prepare( continue; } - if chunk.frustum_culling - && !extracted_frustum_query - .iter() - .any(|frustum| chunk.intersects_frustum(frustum)) + if + chunk.frustum_culling && + !extracted_frustum_query.iter().any(|frustum| chunk.intersects_frustum(frustum)) { trace!("Frustum culled chunk: {:?}", chunk.get_index()); continue; @@ -215,9 +219,11 @@ pub(crate) fn prepare( chunk.get_map_type(), TilemapId(Entity::from_bits(chunk.tilemap_id)), DynamicUniformIndex:: { - index: mesh_uniforms.0.push(&MeshUniform { - transform: chunk.get_transform_matrix(), - }), + index: mesh_uniforms.0.push( + &(MeshUniform { + transform: chunk.get_transform_matrix(), + }) + ), marker: PhantomData, }, DynamicUniformIndex:: { @@ -229,18 +235,16 @@ pub(crate) fn prepare( } mesh_uniforms.0.write_buffer(&render_device, &render_queue); - tilemap_uniforms - .0 - .write_buffer(&render_device, &render_queue); + tilemap_uniforms.0.write_buffer(&render_device, &render_queue); } pub fn prepare_removal( mut chunk_storage: ResMut, removed_tiles: Query<&RemovedTileEntity>, - removed_maps: Query<&RemovedMapEntity>, + removed_maps: Query<&RemovedMapEntity> ) { for removed_tile in removed_tiles.iter() { - chunk_storage.remove_tile_with_entity(removed_tile.0.id()) + chunk_storage.remove_tile_with_entity(removed_tile.0.id()); } for removed_map in removed_maps.iter() { diff --git a/src/render/queue.rs b/src/render/queue.rs index 346e9fcb..5168cf92 100644 --- a/src/render/queue.rs +++ b/src/render/queue.rs @@ -1,16 +1,10 @@ use bevy::{ platform::collections::HashMap, prelude::*, - render::{ - render_resource::{BindGroup, BindGroupEntry}, - renderer::RenderDevice, - }, + render::{ render_resource::{ BindGroup, BindGroupEntry }, renderer::RenderDevice }, }; -use super::{ - pipeline::TilemapPipeline, - prepare::{MeshUniformResource, TilemapUniformResource}, -}; +use super::{ pipeline::TilemapPipeline, prepare::{ MeshUniformResource, TilemapUniformResource } }; use crate::TilemapTexture; #[derive(Resource)] @@ -23,10 +17,13 @@ pub fn queue_transform_bind_group( tilemap_pipeline: Res, render_device: Res, transform_uniforms: Res, - tilemap_uniforms: Res, + tilemap_uniforms: Res ) { - if let (Some(binding1), Some(binding2)) = - (transform_uniforms.0.binding(), tilemap_uniforms.0.binding()) + if + let (Some(binding1), Some(binding2)) = ( + transform_uniforms.0.binding(), + tilemap_uniforms.0.binding(), + ) { commands.insert_resource(TransformBindGroup { value: render_device.create_bind_group( @@ -41,7 +38,7 @@ pub fn queue_transform_bind_group( binding: 1, resource: binding2, }, - ], + ] ), }); } diff --git a/src/render/texture_array_cache.rs b/src/render/texture_array_cache.rs index ba2bffb6..74034d51 100644 --- a/src/render/texture_array_cache.rs +++ b/src/render/texture_array_cache.rs @@ -1,19 +1,29 @@ use crate::render::extract::ExtractedTilemapTexture; -use crate::{TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize}; +use crate::{ TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize }; use bevy::asset::Assets; -use bevy::prelude::{ResMut, Resource}; +use bevy::prelude::{ ResMut, Resource }; use bevy::render::render_resource::TexelCopyTextureInfo; use bevy::{ - platform::collections::{HashMap, HashSet}, - prelude::{Image, Res}, + platform::collections::{ HashMap, HashSet }, + prelude::{ Image, Res }, render::{ render_asset::RenderAssets, render_resource::{ - AddressMode, CommandEncoderDescriptor, Extent3d, FilterMode, Origin3d, - SamplerDescriptor, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, - TextureUsages, TextureViewDescriptor, TextureViewDimension, + AddressMode, + CommandEncoderDescriptor, + Extent3d, + FilterMode, + Origin3d, + SamplerDescriptor, + TextureAspect, + TextureDescriptor, + TextureDimension, + TextureFormat, + TextureUsages, + TextureViewDescriptor, + TextureViewDimension, }, - renderer::{RenderDevice, RenderQueue}, + renderer::{ RenderDevice, RenderQueue }, texture::GpuImage, }, }; @@ -25,14 +35,7 @@ pub struct TextureArrayCache { textures: HashMap, meta_data: HashMap< TilemapTexture, - ( - u32, - TilemapTileSize, - TilemapTextureSize, - TilemapSpacing, - FilterMode, - TextureFormat, - ), + (u32, TilemapTileSize, TilemapTextureSize, TilemapSpacing, FilterMode, TextureFormat) >, prepare_queue: HashSet, queue_queue: HashSet, @@ -46,19 +49,15 @@ impl TextureArrayCache { /// checks, as this is assumed to have been done during [`ExtractedTilemapTexture::new`]. pub(crate) fn add_extracted_texture(&mut self, extracted_texture: &ExtractedTilemapTexture) { if !self.meta_data.contains_key(&extracted_texture.texture) { - self.meta_data.insert( - extracted_texture.texture.clone_weak(), - ( - extracted_texture.tile_count, - extracted_texture.tile_size, - extracted_texture.texture_size, - extracted_texture.tile_spacing, - extracted_texture.filtering, - extracted_texture.format, - ), - ); - self.prepare_queue - .insert(extracted_texture.texture.clone_weak()); + self.meta_data.insert(extracted_texture.texture.clone_weak(), ( + extracted_texture.tile_count, + extracted_texture.tile_size, + extracted_texture.texture_size, + extracted_texture.tile_spacing, + extracted_texture.filtering, + extracted_texture.format, + )); + self.prepare_queue.insert(extracted_texture.texture.clone_weak()); } } @@ -70,60 +69,60 @@ impl TextureArrayCache { tile_spacing: TilemapSpacing, filtering: FilterMode, format: TextureFormat, - image_assets: &Res>, + image_assets: &Res> ) { let (tile_count, texture_size) = match &texture { TilemapTexture::Single(handle) => { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); + let image = image_assets + .get(handle) + .expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!" + ); let texture_size: TilemapTextureSize = image.size_f32().into(); - let tile_count_x = ((texture_size.x) / (tile_size.x + tile_spacing.x)).floor(); - let tile_count_y = ((texture_size.y) / (tile_size.y + tile_spacing.y)).floor(); + let tile_count_x = (texture_size.x / (tile_size.x + tile_spacing.x)).floor(); + let tile_count_y = (texture_size.y / (tile_size.y + tile_spacing.y)).floor(); ((tile_count_x * tile_count_y) as u32, texture_size) } TilemapTexture::Vector(handles) => { for handle in handles { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); + let image = image_assets + .get(handle) + .expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!" + ); let this_tile_size: TilemapTileSize = image.size_f32().into(); if this_tile_size != tile_size { panic!( "Expected all provided image assets to have size {tile_size:?}, \ - but found image with size: {this_tile_size:?}", + but found image with size: {this_tile_size:?}" ); } } (handles.len() as u32, tile_size.into()) } TilemapTexture::TextureContainer(handle) => { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); + let image = image_assets + .get(handle) + .expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!" + ); let tile_size: TilemapTileSize = image.size_f32().into(); - ( - image.texture_descriptor.array_layer_count(), - tile_size.into(), - ) + (image.texture_descriptor.array_layer_count(), tile_size.into()) } }; if !self.meta_data.contains_key(&texture) { - self.meta_data.insert( - texture.clone_weak(), - ( - tile_count, - tile_size, - texture_size, - tile_spacing, - filtering, - format, - ), - ); + self.meta_data.insert(texture.clone_weak(), ( + tile_count, + tile_size, + texture_size, + tile_spacing, + filtering, + format, + )); self.prepare_queue.insert(texture.clone_weak()); } } @@ -140,7 +139,7 @@ impl TextureArrayCache { pub fn prepare( &mut self, render_device: &RenderDevice, - render_images: &Res>, + render_images: &Res> ) { let prepare_queue = self.prepare_queue.drain().collect::>(); for texture in prepare_queue.iter() { @@ -154,57 +153,60 @@ impl TextureArrayCache { match texture { TilemapTexture::Single(_) | TilemapTexture::Vector(_) => { - let (count, tile_size, _, _, filter, format) = - self.meta_data.get(texture).unwrap(); + let (count, tile_size, _, _, filter, format) = self.meta_data + .get(texture) + .unwrap(); // Fixes issue where wgpu's gles texture type inference fails. - let count = if *count == 1 || count % 6 == 0 { - count + 1 - } else { - *count - }; + let count = if *count == 1 || count % 6 == 0 { count + 1 } else { *count }; - let gpu_texture = render_device.create_texture(&TextureDescriptor { - label: Some("texture_array"), - size: Extent3d { - width: tile_size.x as u32, - height: tile_size.y as u32, - depth_or_array_layers: count, - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: *format, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); + let gpu_texture = render_device.create_texture( + &(TextureDescriptor { + label: Some("texture_array"), + size: Extent3d { + width: tile_size.x as u32, + height: tile_size.y as u32, + depth_or_array_layers: count, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: *format, + usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }) + ); - let sampler = render_device.create_sampler(&SamplerDescriptor { - label: Some("texture_array_sampler"), - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: *filter, - min_filter: *filter, - mipmap_filter: *filter, - lod_min_clamp: 0.0, - lod_max_clamp: f32::MAX, - compare: None, - anisotropy_clamp: 1, - border_color: None, - }); + let sampler = render_device.create_sampler( + &(SamplerDescriptor { + label: Some("texture_array_sampler"), + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: *filter, + min_filter: *filter, + mipmap_filter: *filter, + lod_min_clamp: 0.0, + lod_max_clamp: f32::MAX, + compare: None, + anisotropy_clamp: 1, + border_color: None, + }) + ); - let texture_view = gpu_texture.create_view(&TextureViewDescriptor { - label: Some("texture_array_view"), - format: None, - dimension: Some(TextureViewDimension::D2Array), - aspect: TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: Some(count), - usage: Some(gpu_texture.usage()), - }); + let texture_view = gpu_texture.create_view( + &(TextureViewDescriptor { + label: Some("texture_array_view"), + format: None, + dimension: Some(TextureViewDimension::D2Array), + aspect: TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: Some(count), + usage: Some(gpu_texture.usage()), + }) + ); let mip_level_count = gpu_texture.mip_level_count(); @@ -226,8 +228,7 @@ impl TextureArrayCache { } TilemapTexture::TextureContainer(handle) => { if let Some(gpu_image) = render_images.get(handle) { - self.textures - .insert(texture.clone_weak(), gpu_image.clone()); + self.textures.insert(texture.clone_weak(), gpu_image.clone()); } else { self.prepare_queue.insert(texture.clone_weak()); } @@ -240,7 +241,7 @@ impl TextureArrayCache { &mut self, render_device: &RenderDevice, render_queue: &RenderQueue, - render_images: &Res>, + render_images: &Res> ) { let queue_queue = self.queue_queue.drain().collect::>(); @@ -254,22 +255,24 @@ impl TextureArrayCache { continue; }; - let (count, tile_size, texture_size, spacing, _, _) = - self.meta_data.get(texture).unwrap(); + let (count, tile_size, texture_size, spacing, _, _) = self.meta_data + .get(texture) + .unwrap(); let array_gpu_image = self.textures.get(texture).unwrap(); let count = *count; - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { + let mut command_encoder = render_device.create_command_encoder( + &(CommandEncoderDescriptor { label: Some("create_texture_array_from_atlas"), - }); + }) + ); for i in 0..count { let columns = (texture_size.x / (tile_size.x + spacing.x)).floor(); let sprite_sheet_x: f32 = - (i as f32 % columns).floor() * (tile_size.x + spacing.x) + spacing.x; + ((i as f32) % columns).floor() * (tile_size.x + spacing.x) + spacing.x; let sprite_sheet_y: f32 = - (i as f32 / columns).floor() * (tile_size.y + spacing.y) + spacing.y; + ((i as f32) / columns).floor() * (tile_size.y + spacing.y) + spacing.y; command_encoder.copy_texture_to_texture( TexelCopyTextureInfo { @@ -292,7 +295,7 @@ impl TextureArrayCache { width: tile_size.x as u32, height: tile_size.y as u32, depth_or_array_layers: 1, - }, + } ); } @@ -303,7 +306,7 @@ impl TextureArrayCache { let mut gpu_images = Vec::with_capacity(handles.len()); for handle in handles { if let Some(gpu_image) = render_images.get(handle) { - gpu_images.push(gpu_image) + gpu_images.push(gpu_image); } else { self.prepare_queue.insert(texture.clone_weak()); continue; @@ -314,10 +317,11 @@ impl TextureArrayCache { let array_gpu_image = self.textures.get(texture).unwrap(); let count = *count; - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { + let mut command_encoder = render_device.create_command_encoder( + &(CommandEncoderDescriptor { label: Some("create_texture_array_from_handles_vec"), - }); + }) + ); for i in 0..count { command_encoder.copy_texture_to_texture( @@ -337,7 +341,7 @@ impl TextureArrayCache { width: tile_size.x as u32, height: tile_size.y as u32, depth_or_array_layers: 1, - }, + } ); } @@ -357,17 +361,13 @@ impl TextureArrayCache { /// responsive to hot-reloading, for example. pub fn remove_modified_textures( modified_image_ids: Res, - mut texture_cache: ResMut, + mut texture_cache: ResMut ) { - let texture_is_unmodified = - |texture: &TilemapTexture| !modified_image_ids.is_texture_modified(texture); + let texture_is_unmodified = |texture: &TilemapTexture| + !modified_image_ids.is_texture_modified(texture); - texture_cache - .textures - .retain(|texture, _| texture_is_unmodified(texture)); - texture_cache - .meta_data - .retain(|texture, _| texture_is_unmodified(texture)); + texture_cache.textures.retain(|texture, _| texture_is_unmodified(texture)); + texture_cache.meta_data.retain(|texture, _| texture_is_unmodified(texture)); texture_cache.prepare_queue.retain(texture_is_unmodified); texture_cache.queue_queue.retain(texture_is_unmodified); texture_cache.bad_flag_queue.retain(texture_is_unmodified); diff --git a/src/tiles/mod.rs b/src/tiles/mod.rs index 891c8fb1..b70fa791 100644 --- a/src/tiles/mod.rs +++ b/src/tiles/mod.rs @@ -1,8 +1,8 @@ mod storage; use bevy::{ - math::{UVec2, Vec2}, - prelude::{Bundle, Color, Component, Reflect, ReflectComponent}, + math::{ UVec2, Vec2 }, + prelude::{ Bundle, Color, Component, Reflect, ReflectComponent }, render::sync_world::SyncToRenderWorld, }; pub use storage::*; @@ -27,7 +27,7 @@ impl TilePos { /// Converts a tile position (2D) into an index in a flattened vector (1D), assuming the /// tile position lies in a tilemap of the specified size. pub fn to_index(&self, tilemap_size: &TilemapSize) -> usize { - ((self.y * tilemap_size.x) + self.x) as usize + (self.y * tilemap_size.x + self.x) as usize } /// Checks to see if `self` lies within a tilemap of the specified size. @@ -141,3 +141,44 @@ pub struct AnimatedTile { /// The speed the animation plays back at. pub speed: f32, } + +#[cfg(test)] +mod tests { + use super::*; + use bevy::math::{ UVec2, Vec2 }; + + #[test] + fn tile_pos_to_index() { + let map_size = TilemapSize { x: 10, y: 10 }; + let pos = TilePos::new(3, 4); + assert_eq!(pos.to_index(&map_size), 4 * 10 + 3); + } + + #[test] + fn tile_pos_within_bounds() { + let map_size = TilemapSize { x: 8, y: 8 }; + assert!(TilePos::new(7, 7).within_map_bounds(&map_size)); + assert!(!TilePos::new(8, 0).within_map_bounds(&map_size)); + assert!(!TilePos::new(0, 8).within_map_bounds(&map_size)); + } + + #[test] + fn conversions_round_trip() { + let original = TilePos::new(5, 6); + + // TilePos → UVec2 → TilePos + let as_uvec: UVec2 = original.into(); + assert_eq!(as_uvec, UVec2::new(5, 6)); + let back: TilePos = as_uvec.into(); + assert_eq!(back, original); + + // TilePos → Vec2 + let as_vec: Vec2 = original.into(); + assert_eq!(as_vec, Vec2::new(5.0, 6.0)); + } + + #[test] + fn visible_default_is_true() { + assert!(TileVisible::default().0); + } +} diff --git a/src/tiles/storage.rs b/src/tiles/storage.rs index 378b17e2..3a3dc221 100644 --- a/src/tiles/storage.rs +++ b/src/tiles/storage.rs @@ -120,3 +120,100 @@ impl TileStorage { self.tiles.iter_mut().filter_map(|opt| opt.take()) } } + +#[cfg(test)] +mod tests { + use super::*; + use bevy::prelude::Entity; + + fn e(id: u32) -> Entity { + // Helper that makes a deterministic dummy `Entity` + Entity::from_raw(id) + } + + fn size_3x3() -> TilemapSize { + TilemapSize { x: 3, y: 3 } + } + + #[test] + fn empty_storage_is_filled_with_none() { + let storage = TileStorage::empty(size_3x3()); + assert_eq!(storage.size, size_3x3()); + assert!(storage.iter().all(|opt| opt.is_none())); + } + + #[test] + fn set_and_get_roundtrip() { + let mut storage = TileStorage::empty(size_3x3()); + let pos = TilePos { x: 1, y: 2 }; + storage.set(&pos, e(42)); + assert_eq!(storage.get(&pos), Some(e(42))); + } + + #[test] + fn checked_get_respects_bounds() { + let storage = TileStorage::empty(size_3x3()); + // In-bounds → None (nothing stored yet) + assert_eq!(storage.checked_get(&TilePos { x: 0, y: 0 }), None); + // Out-of-bounds → None, **not** panic + assert_eq!(storage.checked_get(&TilePos { x: 99, y: 99 }), None); + } + + #[test] + #[should_panic] + fn get_panics_when_out_of_bounds() { + let storage = TileStorage::empty(size_3x3()); + let _ = storage.get(&TilePos { x: 50, y: 50 }); + } + + #[test] + fn remove_returns_entity_and_leaves_none() { + let mut storage = TileStorage::empty(size_3x3()); + let pos = TilePos { x: 2, y: 1 }; + storage.set(&pos, e(7)); + assert_eq!(storage.remove(&pos), Some(e(7))); + assert_eq!(storage.get(&pos), None); + } + + #[test] + fn drain_yields_every_entity_and_empties_storage() { + let mut storage = TileStorage::empty(size_3x3()); + storage.set(&TilePos { x: 0, y: 0 }, e(1)); + storage.set(&TilePos { x: 1, y: 1 }, e(2)); + storage.set(&TilePos { x: 2, y: 2 }, e(3)); + + let mut drained: Vec<_> = storage.drain().collect(); + drained.sort_by_key(|e| e.index()); + assert_eq!(drained, vec![e(1), e(2), e(3)]); + assert!(storage.iter().all(|opt| opt.is_none())); + } + + // ─────────────────────────────── + // MapEntities implementation + // ─────────────────────────────── + use bevy::ecs::entity::EntityMapper; + + struct AddOneMapper; + impl EntityMapper for AddOneMapper { + fn get_mapped(&mut self, entity: Entity) -> Entity { + // Just bump the raw id for test purposes + e(entity.index() + 1) + } + + fn set_mapped(&mut self, _source: Entity, _target: Entity) { + + } + } + + #[test] + fn map_entities_transforms_every_entity() { + let mut storage = TileStorage::empty(size_3x3()); + storage.set(&TilePos { x: 0, y: 0 }, e(10)); + storage.set(&TilePos { x: 0, y: 1 }, e(11)); + + storage.map_entities(&mut AddOneMapper); + + assert_eq!(storage.get(&TilePos { x: 0, y: 0 }), Some(e(11))); + assert_eq!(storage.get(&TilePos { x: 0, y: 1 }), Some(e(12))); + } +} diff --git a/tests/anchor.rs b/tests/anchor.rs new file mode 100644 index 00000000..e39f4017 --- /dev/null +++ b/tests/anchor.rs @@ -0,0 +1,44 @@ +use bevy::prelude::*; +use bevy_ecs_tilemap::{ anchor::*, map::* }; +use proptest::prelude::*; + +proptest! { + #[test] + fn custom_equivalences_hold_across_random_inputs( + map_x in 1u32..20, + map_y in 1u32..20, + grid_x in 0.5f32..4.0, + grid_y in 0.5f32..4.0, + tile_x in 0.5f32..4.0, + tile_y in 0.5f32..4.0, + ) { + let map_size = TilemapSize { x: map_x, y: map_y }; + let grid_size = TilemapGridSize { x: grid_x, y: grid_y }; + let tile_size = TilemapTileSize { x: tile_x, y: tile_y }; + let map_type = TilemapType::Square; + + // Had to do some trickery because proptest and approx weren't playing nice. + // Accurate to 3 digits, change as needed. + let precision = 10f32.powf(3f32); + + // Center + prop_assert_eq!( + (TilemapAnchor::Center.as_offset(&map_size, &grid_size, &tile_size, &map_type) * precision).round() / precision, + (TilemapAnchor::Custom(Vec2::ZERO).as_offset(&map_size, &grid_size, &tile_size, &map_type) * precision).round() / precision + ); + + // Top-left + prop_assert_eq!( + (TilemapAnchor::TopLeft.as_offset(&map_size, &grid_size, &tile_size, &map_type) * precision).round() / precision, + (TilemapAnchor::Custom(Vec2::new(-0.5, 0.5)) + .as_offset(&map_size, &grid_size, &tile_size, &map_type) * precision).round() / precision + ); + + // Bottom-right + prop_assert_eq!( + (TilemapAnchor::BottomRight.as_offset(&map_size, &grid_size, &tile_size, &map_type) * precision).round() / precision, + (TilemapAnchor::Custom(Vec2::new(0.5, -0.5)) + .as_offset(&map_size, &grid_size, &tile_size, &map_type) * precision).round() / precision + ); + } +} diff --git a/tests/helpers__filling.rs b/tests/helpers__filling.rs new file mode 100644 index 00000000..1e277e17 --- /dev/null +++ b/tests/helpers__filling.rs @@ -0,0 +1,38 @@ +use bevy::ecs::{ system::Commands, world::{ CommandQueue, World } }; +use bevy_ecs_tilemap::{ + map::{ TilemapId, TilemapSize }, + prelude::fill_tilemap, + tiles::{ TilePos, TileStorage, TileTextureIndex }, +}; + +fn spawn_tilemap(world: &mut World) -> (TilemapId, TileStorage) { + let size = TilemapSize { x: 4, y: 3 }; + let id = TilemapId(world.spawn_empty().id()); + (id, TileStorage::empty(size)) +} + +#[test] +fn fill_tilemap_fills_every_cell() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let (tilemap_id, mut storage) = spawn_tilemap(&mut world); + let mut commands = Commands::new(&mut queue, &mut world); + + let size = storage.size; + + fill_tilemap(TileTextureIndex(7), size, tilemap_id, &mut commands, &mut storage); + queue.apply(&mut world); + + // every position should have an entity and the world should own it + let mut filled = 0; + for x in 0..size.x { + for y in 0..size.y { + let pos = TilePos { x, y }; + let entity = storage.get(&pos).expect("position not filled"); + assert!(!world.get_entity(entity).is_err()); + + filled += 1; + } + } + assert_eq!(filled, (size.x * size.y) as usize); +} diff --git a/tests/helpers__hex_grid__axial.rs b/tests/helpers__hex_grid__axial.rs new file mode 100644 index 00000000..87dfebc1 --- /dev/null +++ b/tests/helpers__hex_grid__axial.rs @@ -0,0 +1,48 @@ +use bevy_ecs_tilemap::{ prelude::* }; +use bevy_ecs_tilemap::helpers::hex_grid::axial::AxialPos; +use bevy_ecs_tilemap::map::{ TilemapGridSize, TilemapSize }; + +const GRID: TilemapGridSize = TilemapGridSize { x: 32.0, y: 32.0 }; + +#[test] +fn row_projection_round_trip() { + let samples = [AxialPos::new(0, 0), AxialPos::new(3, -2), AxialPos::new(-4, 5)]; + + for ax in samples { + let world = ax.center_in_world_row(&GRID); + let back = AxialPos::from_world_pos_row(&world, &GRID); + assert_eq!(ax, back, "row-oriented round-trip failed for {ax:?}"); + } +} + +#[test] +fn col_projection_round_trip() { + let samples = [AxialPos::new(0, 0), AxialPos::new(1, 4), AxialPos::new(-3, -2)]; + + for ax in samples { + let world = ax.center_in_world_col(&GRID); + let back = AxialPos::from_world_pos_col(&world, &GRID); + assert_eq!(ax, back, "col-oriented round-trip failed for {ax:?}"); + } +} + +#[test] +fn tilepos_coord_system_helpers() { + let map_size = TilemapSize { x: 10, y: 10 }; + let ax = AxialPos::new(3, 2); + + for &sys in &[ + HexCoordSystem::Row, + HexCoordSystem::Column, + HexCoordSystem::RowEven, + HexCoordSystem::RowOdd, + HexCoordSystem::ColumnEven, + HexCoordSystem::ColumnOdd, + ] { + let tp = ax + .as_tile_pos_given_coord_system_and_map_size(sys, &map_size) + .expect("axial pos should be inside map"); + let back = AxialPos::from_tile_pos_given_coord_system(&tp, sys); + assert_eq!(ax, back, "coord-system conversion failed for {:?} (tile={tp:?})", sys); + } +} diff --git a/tests/helpers__hex_grid__cube.rs b/tests/helpers__hex_grid__cube.rs new file mode 100644 index 00000000..3af45dc6 --- /dev/null +++ b/tests/helpers__hex_grid__cube.rs @@ -0,0 +1,10 @@ +use bevy_ecs_tilemap::helpers::hex_grid::axial::{ AxialPos }; +use bevy_ecs_tilemap::helpers::hex_grid::cube::CubePos; + +#[test] +fn axial_round_trip_is_lossless() { + let axial = AxialPos { q: -5, r: 2 }; + let cube: CubePos = axial.into(); + let back: AxialPos = AxialPos { q: cube.q, r: cube.r }; + assert_eq!(back, axial, "Axial → Cube → Axial should be identity"); +} diff --git a/tests/helpers__hex_grid__neighbors.rs b/tests/helpers__hex_grid__neighbors.rs new file mode 100644 index 00000000..58ad73ae --- /dev/null +++ b/tests/helpers__hex_grid__neighbors.rs @@ -0,0 +1,30 @@ +use bevy_ecs_tilemap::helpers::hex_grid::neighbors::{ HexNeighbors, HexDirection }; +use bevy_ecs_tilemap::map::TilemapSize; +use bevy_ecs_tilemap::tiles::TilePos; +use bevy_ecs_tilemap::map::HexCoordSystem; + +fn pos(x: u32, y: u32) -> TilePos { + TilePos { x, y } +} + +/// A border tile should yield `None` for neighbors that would fall off the map. +#[test] +fn border_tiles_clamp_neighbors_out_of_bounds() { + let size = TilemapSize { x: 3, y: 3 }; + // South-west corner + let corner = pos(0, 2); + + let neighbors = HexNeighbors::::get_neighboring_positions( + &corner, + &size, + &HexCoordSystem::Row + ); + + for dir in [HexDirection::One, HexDirection::Two, HexDirection::Three] { + assert!(neighbors.get(dir).is_none(), "{dir:?} should be None"); + } + + for dir in [HexDirection::Zero, HexDirection::Four, HexDirection::Five] { + assert!(neighbors.get(dir).is_some(), "{dir:?} should be Some"); + } +} diff --git a/tests/helpers__projection.rs b/tests/helpers__projection.rs new file mode 100644 index 00000000..42c2dc82 --- /dev/null +++ b/tests/helpers__projection.rs @@ -0,0 +1,66 @@ +use bevy_ecs_tilemap::{ + anchor::TilemapAnchor, + map::{ + HexCoordSystem, + IsoCoordSystem, + TilemapGridSize, + TilemapSize, + TilemapTileSize, + TilemapType, + }, + tiles::TilePos, +}; + +fn roundtrip( + original: TilePos, + map_type: TilemapType, + grid_size: TilemapGridSize, + tile_size: TilemapTileSize +) { + let map_size = TilemapSize { x: 10, y: 10 }; + let anchor = TilemapAnchor::BottomLeft; + + let world = original.center_in_world(&map_size, &grid_size, &tile_size, &map_type, &anchor); + let recon = TilePos::from_world_pos( + &world, + &map_size, + &grid_size, + &tile_size, + &map_type, + &anchor + ).expect("round-trip should succeed"); + + assert_eq!(original, recon, "round-trip failed for {map_type:?}"); +} + +#[test] +fn square_roundtrip() { + roundtrip( + TilePos { x: 4, y: 6 }, + TilemapType::Square, + TilemapGridSize { x: 32.0, y: 32.0 }, + TilemapTileSize { x: 32.0, y: 32.0 } + ); +} + +#[test] +fn hex_row_even_roundtrip() { + use HexCoordSystem::*; + roundtrip( + TilePos { x: 2, y: 5 }, + TilemapType::Hexagon(RowEven), + TilemapGridSize { x: 32.0, y: 32.0 }, + TilemapTileSize { x: 32.0, y: 32.0 } + ); +} + +#[test] +fn iso_diamond_roundtrip() { + use IsoCoordSystem::*; + roundtrip( + TilePos { x: 1, y: 8 }, + TilemapType::Isometric(Diamond), + TilemapGridSize { x: 32.0, y: 16.0 }, + TilemapTileSize { x: 32.0, y: 16.0 } + ); +} diff --git a/tests/helpers__square_grid__diamond.rs b/tests/helpers__square_grid__diamond.rs new file mode 100644 index 00000000..f02c2f7b --- /dev/null +++ b/tests/helpers__square_grid__diamond.rs @@ -0,0 +1,27 @@ +use bevy_ecs_tilemap::helpers::square_grid::neighbors::SquareDirection; +use bevy_ecs_tilemap::helpers::square_grid::diamond::{ DiamondPos }; +use bevy_ecs_tilemap::map::TilemapSize; +use bevy_ecs_tilemap::tiles::TilePos; + +#[test] +fn as_tile_pos_respects_bounds() { + let map = TilemapSize { x: 5, y: 5 }; + let inside = DiamondPos::new(3, 4); + let outside = DiamondPos::new(-1, 0); + + assert_eq!(inside.as_tile_pos(&map), Some(TilePos { x: 3, y: 4 })); + assert_eq!(outside.as_tile_pos(&map), None); +} + +#[test] +fn diamond_offset_follows_square_direction() { + use SquareDirection::*; + let map = TilemapSize { x: 4, y: 4 }; + let origin = TilePos { x: 1, y: 1 }; + + let up = origin.diamond_offset(&North, &map).unwrap(); + assert_eq!(up, TilePos { x: 1, y: 2 }); + + let right = origin.diamond_offset(&East, &map).unwrap(); + assert_eq!(right, TilePos { x: 2, y: 1 }); +} diff --git a/tests/helpers__square_grid__mod.rs b/tests/helpers__square_grid__mod.rs new file mode 100644 index 00000000..d071a28c --- /dev/null +++ b/tests/helpers__square_grid__mod.rs @@ -0,0 +1,27 @@ +use bevy_ecs_tilemap::{ map::TilemapSize, tiles::TilePos, * }; +use helpers::square_grid::neighbors::SquareDirection; + +#[test] +fn square_offset_roundtrip() { + let map_size = TilemapSize { x: 5, y: 5 }; + let origin = TilePos::new(2, 2); + + // Take a step north then south – we should land back on origin. + let north = origin + .square_offset(&SquareDirection::North, &map_size) + .expect("in-bounds north neighbour"); + let back = north + .square_offset(&SquareDirection::South, &map_size) + .expect("in-bounds south neighbour"); + + assert_eq!(back, origin); +} + +#[test] +fn square_offset_out_of_bounds_returns_none() { + let map_size = TilemapSize { x: 4, y: 4 }; + let edge = TilePos::new(0, 0); + + assert!(edge.square_offset(&SquareDirection::South, &map_size).is_none()); + assert!(edge.square_offset(&SquareDirection::West, &map_size).is_none()); +} diff --git a/tests/helpers__square_grid__neighbors.rs b/tests/helpers__square_grid__neighbors.rs new file mode 100644 index 00000000..a5e3e5f6 --- /dev/null +++ b/tests/helpers__square_grid__neighbors.rs @@ -0,0 +1,98 @@ +use bevy_ecs_tilemap::{ + helpers::square_grid::{ neighbors::{ Neighbors, SquareDirection }, staggered::StaggeredPos }, + map::TilemapSize, + tiles::{ TilePos, TileStorage }, +}; + +#[test] +fn square_neighboring_positions_centre_of_small_map() { + let map = TilemapSize { x: 3, y: 3 }; + let centre = TilePos::new(1, 1); + + let neighbors = Neighbors::get_square_neighboring_positions(¢re, &map, true); + + // we should get all eight neighbouring cells + for (dx, dy) in [ + (1, 0), + (1, 1), + (0, 1), + (-1, 1), + (-1, 0), + (-1, -1), + (0, -1), + (1, -1), + ] { + let expected = TilePos::new((1isize + dx) as u32, (1isize + dy) as u32); + assert!( + neighbors.iter().any(|p| *p == expected), + "expected to find neighbour at {expected:?}" + ); + } +} + +#[test] +fn square_neighboring_positions_edges_clamp_to_none() { + let map = TilemapSize { x: 2, y: 2 }; + let corner = TilePos::new(0, 0); + + let neighbors = Neighbors::get_square_neighboring_positions(&corner, &map, true); + + // (0,0) only has East, North, NorthEast inside the map + assert_eq!(neighbors.east, Some(TilePos::new(1, 0))); + assert_eq!(neighbors.north, Some(TilePos::new(0, 1))); + assert_eq!(neighbors.north_east, Some(TilePos::new(1, 1))); + + assert!(neighbors.north_west.is_none()); + assert!(neighbors.west.is_none()); + assert!(neighbors.south_west.is_none()); + assert!(neighbors.south.is_none()); + assert!(neighbors.south_east.is_none()); +} + +#[test] +fn staggered_neighboring_positions_respects_offset() { + let map: TilemapSize = TilemapSize { x: 3, y: 3 }; + let start: TilePos = TilePos::new(1, 1); + + let neighbors: Neighbors = Neighbors::get_staggered_neighboring_positions( + &start, + &map, + false + ); + + // only cardinals requested + assert!(neighbors.north_east.is_none()); + assert!(neighbors.south_west.is_none()); + assert_eq!( + neighbors.north, + Some(StaggeredPos::from(&start).offset(&SquareDirection::North).as_tile_pos(&map).unwrap()) + ); + assert_eq!( + neighbors.east, + Some(StaggeredPos::from(&start).offset(&SquareDirection::East).as_tile_pos(&map).unwrap()) + ); +} + +#[test] +fn tile_storage_entity_lookup() { + // Smoke-test the `entities` helper. + use bevy::prelude::Entity; + + let map = TilemapSize { x: 2, y: 2 }; + let mut storage = TileStorage::empty(map); + + let a = Entity::from_raw(1); + let b = Entity::from_raw(2); + storage.set(&TilePos::new(1, 0), a); + storage.set(&TilePos::new(0, 1), b); + + let pos = TilePos::new(0, 0); + let neighbors = Neighbors::get_square_neighboring_positions(&pos, &map, false); + let entity_neighbors = neighbors.entities(&storage); + + assert_eq!(entity_neighbors.east, Some(a)); + assert_eq!(entity_neighbors.north, Some(b)); + // other cardinals None, diagonals not requested + assert!(entity_neighbors.south.is_none()); + assert!(entity_neighbors.west.is_none()); +} diff --git a/tests/helpers__square_grid__staggered.rs b/tests/helpers__square_grid__staggered.rs new file mode 100644 index 00000000..c515b006 --- /dev/null +++ b/tests/helpers__square_grid__staggered.rs @@ -0,0 +1,39 @@ +use bevy_ecs_tilemap::{ + helpers::square_grid::{ neighbors::SquareDirection::*, staggered::StaggeredPos }, + map::{ TilemapGridSize, TilemapSize }, + tiles::TilePos, +}; + +#[test] +fn as_tile_pos_bounds() { + let map = TilemapSize { x: 10, y: 10 }; + + let inside = StaggeredPos::new(5, 5); + let outside = StaggeredPos::new(-1, 0); + + assert_eq!(inside.as_tile_pos(&map).unwrap(), TilePos { x: 5, y: 5 }); + assert!(outside.as_tile_pos(&map).is_none()); +} + +#[test] +fn tilepos_staggered_offset() { + let map = TilemapSize { x: 3, y: 3 }; + + let origin = TilePos { x: 1, y: 1 }; + assert_eq!(origin.staggered_offset(&North, &map).unwrap(), TilePos { x: 1, y: 2 }); + + // Edge should return None + let edge = TilePos { x: 0, y: 0 }; + assert!(edge.staggered_offset(&West, &map).is_none()); +} + +#[test] +fn world_roundtrip() { + let grid = TilemapGridSize { x: 32.0, y: 32.0 }; + let original = StaggeredPos::new(4, 2); + + let world = original.center_in_world(&grid); + let round = StaggeredPos::from_world_pos(&world, &grid); + + assert_eq!(original, round); +} diff --git a/tests/map1.rs b/tests/map1.rs new file mode 100644 index 00000000..cb3b578b --- /dev/null +++ b/tests/map1.rs @@ -0,0 +1,29 @@ +use bevy::ecs::entity::{ EntityMapper, MapEntities }; +use bevy::prelude::Entity; +use bevy_ecs_tilemap::map::TilemapId; + +/// Tiny stub that maps a single entity to a different one. +struct DummyMapper { + from: Entity, + to: Entity, +} + +impl EntityMapper for DummyMapper { + fn get_mapped(&mut self, e: Entity) -> Entity { + if e == self.from { self.to } else { e } + } + + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} +} + +#[test] +fn tilemap_id_is_remapped() { + let old = Entity::from_raw(1); + let new = Entity::from_raw(2); + + let mut id = TilemapId(old); + let mut mapper = DummyMapper { from: old, to: new }; + + id.map_entities(&mut mapper); + assert_eq!(id.0, new); +} diff --git a/tests/map2.rs b/tests/map2.rs new file mode 100644 index 00000000..e19cee13 --- /dev/null +++ b/tests/map2.rs @@ -0,0 +1,55 @@ +use bevy::{ + app::App, + asset::{ Assets, RenderAssetUsages }, + ecs::system::{ Res, ResMut, RunSystemOnce }, + image::Image, + render::render_resource::{ Extent3d, TextureDimension, TextureFormat, TextureUsages }, +}; +use bevy_ecs_tilemap::map::TilemapTexture; + +fn make_image() -> Image { + let mut image = Image::new_fill( + Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, + TextureDimension::D2, + &[255, 255, 255, 255], + TextureFormat::Rgba8UnormSrgb, + RenderAssetUsages::default() + ); + image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING; + image +} + +#[test] +fn verify_ready_and_set_copy_src_work() { + let mut app = App::new(); + app.init_resource::>(); + + let handle = { + let mut images = app.world_mut().resource_mut::>(); + images.add(make_image()) + }; + let tex = TilemapTexture::Single(handle.clone_weak()); + + // 1. `verify_ready` should fail (COPY_SRC not set yet) + { + let tex = tex.clone(); + let _ = app.world_mut().run_system_once(move |images: Res>| { + assert!(!tex.verify_ready(&images)); + }); + } + + // 2. add the COPY_SRC usage flag + { + let tex = tex.clone(); + let _ = app.world_mut().run_system_once(move |mut images: ResMut>| { + tex.set_images_to_copy_src(&mut images); + }); + } + + // 3. `verify_ready` should now succeed + { + let _ = app.world_mut().run_system_once(move |images: Res>| { + assert!(tex.verify_ready(&images)); + }); + } +} diff --git a/tests/tiles__mod.rs b/tests/tiles__mod.rs new file mode 100644 index 00000000..424ff229 --- /dev/null +++ b/tests/tiles__mod.rs @@ -0,0 +1,19 @@ +use bevy::color::Color; +use bevy_ecs_tilemap::tiles::{ TileBundle, TileFlip, TilePos }; + +#[test] +fn tile_bundle_defaults_are_consistent() { + let bundle = TileBundle::default(); + + // Position and visibility come from their own tested defaults + assert_eq!(bundle.position, TilePos::default()); + assert_eq!(bundle.visible.0, true); + + // Old-position starts in sync with `position` + assert_eq!(bundle.old_position.0, bundle.position); + + // Flip, color and texture index should be zeroed / identity + assert_eq!(bundle.flip, TileFlip::default()); + assert_eq!(bundle.texture_index.0, 0); + assert_eq!(bundle.color.0, Color::WHITE); +} diff --git a/tests/tiles__storage.rs b/tests/tiles__storage.rs new file mode 100644 index 00000000..013c7984 --- /dev/null +++ b/tests/tiles__storage.rs @@ -0,0 +1,37 @@ +use bevy::{ ecs::world::CommandQueue, prelude::* }; +use bevy_ecs_tilemap::prelude::{ TilemapSize, TilePos, TileStorage }; + +#[test] +fn drain_can_be_used_to_despawn_entities() { + let mut app = App::new(); + // Spawn three entities and store their IDs + let e1 = app.world_mut().spawn_empty().id(); + let e2 = app.world_mut().spawn_empty().id(); + let e3 = app.world_mut().spawn_empty().id(); + + // Put them in a storage component that lives on its own entity + let mut storage = TileStorage::empty(TilemapSize { x: 2, y: 2 }); + storage.set(&(TilePos { x: 0, y: 0 }), e1); + storage.set(&(TilePos { x: 1, y: 0 }), e2); + storage.set(&(TilePos { x: 0, y: 1 }), e3); + + let storage_entity = app.world_mut().spawn(storage).id(); + + // Use Commands-style despawning exactly as the docs example shows + let mut queue = CommandQueue::default(); + { + let mut storage = app.world_mut().entity_mut(storage_entity).take::().unwrap(); + let mut commands = Commands::new(&mut queue, &app.world()); + for entity in storage.drain() { + commands.entity(entity).despawn(); + } + // Put the (now empty) storage back on the entity + commands.entity(storage_entity).insert(storage); + } + queue.apply(&mut app.world_mut()); + + // Verify that the entities are really gone + assert!(!app.world().entities().contains(e1)); + assert!(!app.world().entities().contains(e2)); + assert!(!app.world().entities().contains(e3)); +} From 355939355167fd3057860a10034fbd2a6bd394fd Mon Sep 17 00:00:00 2001 From: Alex Bentley Date: Sat, 21 Jun 2025 10:43:53 -0400 Subject: [PATCH 2/5] Ran cargo fmt instead of rustfmt. --- src/anchor.rs | 20 +- src/array_texture_preload.rs | 39 +++- src/helpers/filling.rs | 27 +-- src/helpers/geometry.rs | 8 +- src/helpers/hex_grid/axial.rs | 49 ++--- src/helpers/hex_grid/cube.rs | 9 +- src/helpers/hex_grid/neighbors.rs | 46 +++-- src/helpers/hex_grid/offset.rs | 22 +- src/helpers/projection.rs | 90 ++++----- src/helpers/square_grid/diamond.rs | 18 +- src/helpers/square_grid/mod.rs | 18 +- src/helpers/square_grid/neighbors.rs | 52 +++-- src/helpers/square_grid/staggered.rs | 18 +- src/helpers/transform.rs | 13 +- src/lib.rs | 40 +--- src/map.rs | 62 +++--- src/render/chunk.rs | 73 +++---- src/render/draw.rs | 80 +++++--- src/render/extract.rs | 246 +++++++++++------------ src/render/material.rs | 208 ++++++++++--------- src/render/mod.rs | 138 +++++++------ src/render/pipeline.rs | 142 +++++-------- src/render/prepare.rs | 58 +++--- src/render/queue.rs | 21 +- src/render/texture_array_cache.rs | 164 ++++++++------- src/tiles/mod.rs | 6 +- src/tiles/storage.rs | 4 +- tests/anchor.rs | 6 +- tests/helpers__filling.rs | 17 +- tests/helpers__hex_grid__axial.rs | 22 +- tests/helpers__hex_grid__cube.rs | 7 +- tests/helpers__hex_grid__neighbors.rs | 11 +- tests/helpers__projection.rs | 25 +-- tests/helpers__square_grid__diamond.rs | 2 +- tests/helpers__square_grid__mod.rs | 12 +- tests/helpers__square_grid__neighbors.rs | 28 ++- tests/helpers__square_grid__staggered.rs | 9 +- tests/map1.rs | 2 +- tests/map2.rs | 38 ++-- tests/tiles__mod.rs | 2 +- tests/tiles__storage.rs | 10 +- 41 files changed, 959 insertions(+), 903 deletions(-) diff --git a/src/anchor.rs b/src/anchor.rs index c343dcc6..a4d7ef0c 100644 --- a/src/anchor.rs +++ b/src/anchor.rs @@ -1,4 +1,4 @@ -use crate::{ TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType, prelude::chunk_aabb }; +use crate::{TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType, prelude::chunk_aabb}; use bevy::prelude::*; /// How a tilemap is positioned relative to its [`Transform`]. It defaults to @@ -45,13 +45,13 @@ impl TilemapAnchor { map_size: &TilemapSize, grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, - map_type: &TilemapType + map_type: &TilemapType, ) -> Vec2 { let aabb = chunk_aabb( UVec2::new(map_size.x - 1, map_size.y - 1), grid_size, tile_size, - map_type + map_type, ); let min = aabb.min(); let max = aabb.max(); @@ -66,11 +66,10 @@ impl TilemapAnchor { TilemapAnchor::BottomRight => Vec2::new(-max.x, -min.y), TilemapAnchor::BottomCenter => Vec2::new(-(max.x + min.x) / 2.0, -min.y), TilemapAnchor::Center => Vec2::new(-(max.x + min.x) / 2.0, -(max.y + min.y) / 2.0), - TilemapAnchor::Custom(v) => - Vec2::new( - (-0.5 - v.x) * (max.x - min.x) - min.x, - (-0.5 - v.y) * (max.y - min.y) - min.y - ), + TilemapAnchor::Custom(v) => Vec2::new( + (-0.5 - v.x) * (max.x - min.x) - min.x, + (-0.5 - v.y) * (max.y - min.y) - min.y, + ), } } } @@ -91,7 +90,10 @@ mod tests { #[test] fn none_anchor_is_zero() { let (map, grid, tile, ty) = fixed_params(); - assert_eq!(TilemapAnchor::None.as_offset(&map, &grid, &tile, &ty), Vec2::ZERO); + assert_eq!( + TilemapAnchor::None.as_offset(&map, &grid, &tile, &ty), + Vec2::ZERO + ); } #[test] diff --git a/src/array_texture_preload.rs b/src/array_texture_preload.rs index f6a680e1..bfba7ef4 100644 --- a/src/array_texture_preload.rs +++ b/src/array_texture_preload.rs @@ -1,12 +1,15 @@ -use crate::render::{ DefaultSampler, TextureArrayCache }; -use crate::{ TilemapTexture, prelude::{ TilemapSpacing, TilemapTileSize } }; -use bevy::render::render_resource::{ FilterMode, TextureFormat }; +use crate::render::{DefaultSampler, TextureArrayCache}; +use crate::{ + TilemapTexture, + prelude::{TilemapSpacing, TilemapTileSize}, +}; +use bevy::render::render_resource::{FilterMode, TextureFormat}; use bevy::{ image::BevyDefault, - prelude::{ Assets, Image, Res, ResMut, Resource }, + prelude::{Assets, Image, Res, ResMut, Resource}, render::Extract, }; -use std::sync::{ Arc, RwLock }; +use std::sync::{Arc, RwLock}; #[derive(Debug, Clone)] pub struct TilemapArrayTexture { @@ -57,11 +60,13 @@ pub(crate) fn extract( images: Extract>>, array_texture_loader: Extract>, mut texture_array_cache: ResMut, - default_image_settings: Res + default_image_settings: Res, ) { for mut array_texture in array_texture_loader.drain() { if array_texture.filter.is_none() { - array_texture.filter.replace(default_image_settings.mag_filter.into()); + array_texture + .filter + .replace(default_image_settings.mag_filter.into()); } if array_texture.texture.verify_ready(&images) { texture_array_cache.add_texture( @@ -70,7 +75,7 @@ pub(crate) fn extract( array_texture.tile_spacing, default_image_settings.min_filter.into(), array_texture.format, - &images + &images, ); } else { // Image hasn't loaded yet punt to next frame. @@ -89,8 +94,20 @@ mod tests { let tex = TilemapArrayTexture::default(); assert!(tex.filter.is_none(), "Filter should start unset (None)"); - assert_eq!(tex.texture, TilemapTexture::default(), "Texture default mismatch"); - assert_eq!(tex.tile_size, TilemapTileSize::default(), "Tile size default mismatch"); - assert_eq!(tex.tile_spacing, TilemapSpacing::default(), "Tile spacing default mismatch"); + assert_eq!( + tex.texture, + TilemapTexture::default(), + "Texture default mismatch" + ); + assert_eq!( + tex.tile_size, + TilemapTileSize::default(), + "Tile size default mismatch" + ); + assert_eq!( + tex.tile_spacing, + TilemapSpacing::default(), + "Tile spacing default mismatch" + ); } } diff --git a/src/helpers/filling.rs b/src/helpers/filling.rs index b5e2b86f..deb6b218 100644 --- a/src/helpers/filling.rs +++ b/src/helpers/filling.rs @@ -1,11 +1,11 @@ use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::neighbors::{ HEX_DIRECTIONS, HexDirection }; +use crate::helpers::hex_grid::neighbors::{HEX_DIRECTIONS, HexDirection}; use crate::map::TilemapId; use crate::prelude::HexCoordSystem; -use crate::tiles::{ TileBundle, TileColor, TilePos, TileTextureIndex }; -use crate::{ TileStorage, TilemapSize }; +use crate::tiles::{TileBundle, TileColor, TilePos, TileTextureIndex}; +use crate::{TileStorage, TilemapSize}; -use bevy::prelude::{ Color, Commands }; +use bevy::prelude::{Color, Commands}; /// Fills an entire tile storage with the given tile. pub fn fill_tilemap( @@ -13,7 +13,7 @@ pub fn fill_tilemap( size: TilemapSize, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage + tile_storage: &mut TileStorage, ) { commands.entity(tilemap_id.0).with_children(|parent| { for x in 0..size.x { @@ -43,7 +43,7 @@ pub fn fill_tilemap_rect( size: TilemapSize, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage + tile_storage: &mut TileStorage, ) { commands.entity(tilemap_id.0).with_children(|parent| { for x in 0..size.x { @@ -78,7 +78,7 @@ pub fn fill_tilemap_rect_color( color: Color, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage + tile_storage: &mut TileStorage, ) { commands.entity(tilemap_id.0).with_children(|parent| { for x in 0..size.x { @@ -112,7 +112,8 @@ pub fn generate_hex_ring(origin: AxialPos, radius: u32) -> Vec { vec![origin] } else { let mut ring = Vec::with_capacity((radius * 6) as usize); - let corners = HEX_DIRECTIONS.iter() + let corners = HEX_DIRECTIONS + .iter() .map(|direction| origin + radius * AxialPos::from(direction)) .collect::>(); // The "tangent" is the direction we must travel in to reach the next corner @@ -153,15 +154,15 @@ pub fn fill_tilemap_hexagon( hex_coord_system: HexCoordSystem, tilemap_id: TilemapId, commands: &mut Commands, - tile_storage: &mut TileStorage + tile_storage: &mut TileStorage, ) { let tile_positions = generate_hexagon( AxialPos::from_tile_pos_given_coord_system(&origin, hex_coord_system), - radius + radius, ) - .into_iter() - .map(|axial_pos| axial_pos.as_tile_pos_given_coord_system(hex_coord_system)) - .collect::>(); + .into_iter() + .map(|axial_pos| axial_pos.as_tile_pos_given_coord_system(hex_coord_system)) + .collect::>(); commands.entity(tilemap_id.0).with_children(|parent| { for tile_pos in tile_positions { diff --git a/src/helpers/geometry.rs b/src/helpers/geometry.rs index be33a2ce..b86f4513 100644 --- a/src/helpers/geometry.rs +++ b/src/helpers/geometry.rs @@ -1,6 +1,6 @@ use crate::map::TilemapType; use crate::tiles::TilePos; -use crate::{ TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, Transform }; +use crate::{TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, Transform}; // Deprecated. Skipping tests. @@ -11,7 +11,7 @@ pub fn get_tilemap_center_transform( map_size: &TilemapSize, grid_size: &TilemapGridSize, map_type: &TilemapType, - z: f32 + z: f32, ) -> Transform { let tile_size = TilemapTileSize::new(grid_size.x, grid_size.y); let low = TilePos::new(0, 0).center_in_world( @@ -19,14 +19,14 @@ pub fn get_tilemap_center_transform( grid_size, &tile_size, map_type, - &TilemapAnchor::None + &TilemapAnchor::None, ); let high = TilePos::new(map_size.x - 1, map_size.y - 1).center_in_world( map_size, grid_size, &tile_size, map_type, - &TilemapAnchor::None + &TilemapAnchor::None, ); let diff = high - low; diff --git a/src/helpers/hex_grid/axial.rs b/src/helpers/hex_grid/axial.rs index 3864a1fd..ee56d2a7 100644 --- a/src/helpers/hex_grid/axial.rs +++ b/src/helpers/hex_grid/axial.rs @@ -1,19 +1,16 @@ //! Code for the axial coordinate system. -use crate::helpers::hex_grid::consts::{ DOUBLE_INV_SQRT_3, HALF_SQRT_3, INV_SQRT_3 }; -use crate::helpers::hex_grid::cube::{ CubePos, FractionalCubePos }; +use crate::helpers::hex_grid::consts::{DOUBLE_INV_SQRT_3, HALF_SQRT_3, INV_SQRT_3}; +use crate::helpers::hex_grid::cube::{CubePos, FractionalCubePos}; use crate::helpers::hex_grid::neighbors::{ - HEX_OFFSETS, - HexColDirection, - HexDirection, - HexRowDirection, + HEX_OFFSETS, HexColDirection, HexDirection, HexRowDirection, }; -use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; +use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; use crate::map::HexCoordSystem; use crate::tiles::TilePos; -use crate::{ TilemapGridSize, TilemapSize }; -use bevy::math::{ Mat2, Vec2 }; -use std::ops::{ Add, Mul, Sub }; +use crate::{TilemapGridSize, TilemapSize}; +use bevy::math::{Mat2, Vec2}; +use std::ops::{Add, Mul, Sub}; /// A position in a hex grid labelled according to [`HexCoordSystem::Row`] or /// [`HexCoordSystem::Column`]. It is composed of a pair of `i32` digits named `q` and `r`. When @@ -194,7 +191,7 @@ pub const ROW_BASIS: Mat2 = Mat2::from_cols(Vec2::new(1.0, 0.0), Vec2::new(0.5, /// The inverse of [`ROW_BASIS`]. pub const INV_ROW_BASIS: Mat2 = Mat2::from_cols( Vec2::new(1.0, 0.0), - Vec2::new(-1.0 * INV_SQRT_3, DOUBLE_INV_SQRT_3) + Vec2::new(-1.0 * INV_SQRT_3, DOUBLE_INV_SQRT_3), ); /// The matrix for mapping from [`AxialPos`], to world position when hexes are arranged @@ -208,7 +205,7 @@ pub const COL_BASIS: Mat2 = Mat2::from_cols(Vec2::new(HALF_SQRT_3, 0.5), Vec2::n /// The inverse of [`COL_BASIS`]. pub const INV_COL_BASIS: Mat2 = Mat2::from_cols( Vec2::new(DOUBLE_INV_SQRT_3, -1.0 * INV_SQRT_3), - Vec2::new(0.0, 1.0) + Vec2::new(0.0, 1.0), ); pub const UNIT_Q: AxialPos = AxialPos { q: 1, r: 0 }; @@ -242,7 +239,10 @@ impl AxialPos { #[inline] pub fn project_row(axial_pos: Vec2, grid_size: &TilemapGridSize) -> Vec2 { let unscaled_pos = ROW_BASIS * axial_pos; - Vec2::new(grid_size.x * unscaled_pos.x, ROW_BASIS.y_axis.y * grid_size.y * unscaled_pos.y) + Vec2::new( + grid_size.x * unscaled_pos.x, + ROW_BASIS.y_axis.y * grid_size.y * unscaled_pos.y, + ) } /// Returns the center of a hex tile world space, assuming that: @@ -259,7 +259,7 @@ impl AxialPos { #[inline] pub fn corner_offset_in_world_row( corner_direction: HexRowDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let corner_offset = AxialPos::from(HexDirection::from(corner_direction)); let corner_pos = 0.5 * Vec2::new(corner_offset.q as f32, corner_offset.r as f32); @@ -275,7 +275,7 @@ impl AxialPos { pub fn corner_in_world_row( &self, corner_direction: HexRowDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let center = Vec2::new(self.q as f32, self.r as f32); @@ -294,7 +294,10 @@ impl AxialPos { #[inline] pub fn project_col(axial_pos: Vec2, grid_size: &TilemapGridSize) -> Vec2 { let unscaled_pos = COL_BASIS * axial_pos; - Vec2::new(COL_BASIS.x_axis.x * grid_size.x * unscaled_pos.x, grid_size.y * unscaled_pos.y) + Vec2::new( + COL_BASIS.x_axis.x * grid_size.x * unscaled_pos.x, + grid_size.y * unscaled_pos.y, + ) } /// Returns the center of a hex tile world space, assuming that: @@ -311,7 +314,7 @@ impl AxialPos { #[inline] pub fn corner_offset_in_world_col( corner_direction: HexColDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let corner_offset = AxialPos::from(HexDirection::from(corner_direction)); let corner_pos = 0.5 * Vec2::new(corner_offset.q as f32, corner_offset.r as f32); @@ -327,7 +330,7 @@ impl AxialPos { pub fn corner_in_world_col( &self, corner_direction: HexColDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let center = Vec2::new(self.q as f32, self.r as f32); @@ -345,7 +348,7 @@ impl AxialPos { pub fn from_world_pos_row(world_pos: &Vec2, grid_size: &TilemapGridSize) -> AxialPos { let normalized_world_pos = Vec2::new( world_pos.x / grid_size.x, - world_pos.y / (ROW_BASIS.y_axis.y * grid_size.y) + world_pos.y / (ROW_BASIS.y_axis.y * grid_size.y), ); let frac_pos = FractionalAxialPos::from(INV_ROW_BASIS * normalized_world_pos); frac_pos.round() @@ -359,7 +362,7 @@ impl AxialPos { pub fn from_world_pos_col(world_pos: &Vec2, grid_size: &TilemapGridSize) -> AxialPos { let normalized_world_pos = Vec2::new( world_pos.x / (COL_BASIS.x_axis.x * grid_size.x), - world_pos.y / grid_size.y + world_pos.y / grid_size.y, ); let frac_pos = FractionalAxialPos::from(INV_COL_BASIS * normalized_world_pos); frac_pos.round() @@ -413,7 +416,7 @@ impl AxialPos { pub fn as_tile_pos_given_coord_system_and_map_size( &self, hex_coord_sys: HexCoordSystem, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> Option { match hex_coord_sys { HexCoordSystem::RowEven => RowEvenPos::from(*self).as_tile_pos_given_map_size(map_size), @@ -439,7 +442,7 @@ impl AxialPos { #[inline] pub fn from_tile_pos_given_coord_system( tile_pos: &TilePos, - hex_coord_sys: HexCoordSystem + hex_coord_sys: HexCoordSystem, ) -> AxialPos { match hex_coord_sys { HexCoordSystem::RowEven => RowEvenPos::from(tile_pos).into(), @@ -510,7 +513,7 @@ impl From for FractionalAxialPos { #[cfg(test)] mod tests { use super::*; - use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; + use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; // ---------- small private helper ----------------------------------------------------------- #[test] diff --git a/src/helpers/hex_grid/cube.rs b/src/helpers/hex_grid/cube.rs index b847e3d2..2e3343f2 100644 --- a/src/helpers/hex_grid/cube.rs +++ b/src/helpers/hex_grid/cube.rs @@ -1,7 +1,10 @@ //! Code for the cube coordinate system -use crate::{ helpers::hex_grid::axial::{ AxialPos, FractionalAxialPos }, tiles::TilePos }; -use std::ops::{ Add, Mul, Sub }; +use crate::{ + helpers::hex_grid::axial::{AxialPos, FractionalAxialPos}, + tiles::TilePos, +}; +use std::ops::{Add, Mul, Sub}; /// Identical to [`AxialPos`], but has an extra component `s`. Together, `q`, `r`, `s` /// satisfy the identity: `q + r + s = 0`. @@ -176,7 +179,7 @@ impl FractionalCubePos { #[cfg(test)] mod tests { use super::*; - use crate::helpers::hex_grid::axial::{ AxialPos }; + use crate::helpers::hex_grid::axial::AxialPos; #[test] fn axial_to_cube_preserves_identity() { diff --git a/src/helpers/hex_grid/neighbors.rs b/src/helpers/hex_grid/neighbors.rs index a09cb721..0d9d241f 100644 --- a/src/helpers/hex_grid/neighbors.rs +++ b/src/helpers/hex_grid/neighbors.rs @@ -1,10 +1,10 @@ use crate::TilePos; use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; -use crate::map::{ HexCoordSystem, TilemapSize }; +use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; +use crate::map::{HexCoordSystem, TilemapSize}; use crate::prelude::TileStorage; use bevy::prelude::Entity; -use std::ops::{ Add, Sub }; +use std::ops::{Add, Sub}; /// Neighbors of a hexagonal tile. `Zero` corresponds with `East` for row-oriented tiles, and /// `North` for column-oriented tiles. It might also correspond with a custom direction for @@ -322,13 +322,18 @@ impl HexNeighbors { /// If a neighbor is `None`, this iterator will skip it. #[inline] pub fn iter(&self) -> impl Iterator + '_ { - HEX_DIRECTIONS.into_iter().filter_map(|direction| self.get(direction)) + HEX_DIRECTIONS + .into_iter() + .filter_map(|direction| self.get(direction)) } /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by value. #[inline] - pub fn and_then(self, f: F) -> HexNeighbors where F: Fn(T) -> Option { + pub fn and_then(self, f: F) -> HexNeighbors + where + F: Fn(T) -> Option, + { HexNeighbors { zero: self.zero.and_then(&f), one: self.one.and_then(&f), @@ -342,7 +347,10 @@ impl HexNeighbors { /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by reference. #[inline] - pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors where F: Fn(&'a T) -> Option { + pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors + where + F: Fn(&'a T) -> Option, + { HexNeighbors { zero: self.zero.as_ref().and_then(&f), one: self.one.as_ref().and_then(&f), @@ -356,7 +364,10 @@ impl HexNeighbors { /// Applies the supplied closure `f` with a [`map`](std::option::Option::map) to each /// neighbor element, where `f` takes `T` by reference. #[inline] - pub fn map_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors where F: Fn(&'a T) -> U { + pub fn map_ref<'a, U, F>(&'a self, f: F) -> HexNeighbors + where + F: Fn(&'a T) -> U, + { HexNeighbors { zero: self.zero.as_ref().map(&f), one: self.one.as_ref().map(&f), @@ -371,7 +382,8 @@ impl HexNeighbors { /// `Option`. #[inline] pub fn from_directional_closure(f: F) -> HexNeighbors - where F: Fn(HexDirection) -> Option + where + F: Fn(HexDirection) -> Option, { use HexDirection::*; HexNeighbors { @@ -402,7 +414,7 @@ impl HexNeighbors { pub fn get_neighboring_positions( tile_pos: &TilePos, map_size: &TilemapSize, - hex_coord_sys: &HexCoordSystem + hex_coord_sys: &HexCoordSystem, ) -> HexNeighbors { match hex_coord_sys { HexCoordSystem::RowEven => { @@ -440,10 +452,14 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_standard( tile_pos: &TilePos, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> HexNeighbors { let axial_pos = AxialPos::from(tile_pos); - let f = |direction| { axial_pos.offset(direction).as_tile_pos_given_map_size(map_size) }; + let f = |direction| { + axial_pos + .offset(direction) + .as_tile_pos_given_map_size(map_size) + }; HexNeighbors::from_directional_closure(f) } @@ -454,7 +470,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_row_even( tile_pos: &TilePos, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> HexNeighbors { let axial_pos = AxialPos::from(RowEvenPos::from(tile_pos)); let f = |direction| { @@ -470,7 +486,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_row_odd( tile_pos: &TilePos, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> HexNeighbors { let axial_pos = AxialPos::from(RowOddPos::from(tile_pos)); let f = |direction| { @@ -486,7 +502,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_col_even( tile_pos: &TilePos, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> HexNeighbors { let axial_pos = AxialPos::from(ColEvenPos::from(tile_pos)); let f = |direction| { @@ -502,7 +518,7 @@ impl HexNeighbors { #[inline] pub fn get_neighboring_positions_col_odd( tile_pos: &TilePos, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> HexNeighbors { let axial_pos = AxialPos::from(ColOddPos::from(tile_pos)); let f = |direction| { diff --git a/src/helpers/hex_grid/offset.rs b/src/helpers/hex_grid/offset.rs index 0c7050ba..d84e27db 100644 --- a/src/helpers/hex_grid/offset.rs +++ b/src/helpers/hex_grid/offset.rs @@ -1,9 +1,9 @@ //! Code for the offset coordinate system. use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::neighbors::{ HexColDirection, HexDirection, HexRowDirection }; +use crate::helpers::hex_grid::neighbors::{HexColDirection, HexDirection, HexRowDirection}; use crate::tiles::TilePos; -use crate::{ TilemapGridSize, TilemapSize }; +use crate::{TilemapGridSize, TilemapSize}; use bevy::math::Vec2; #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] @@ -30,7 +30,7 @@ impl RowOddPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexRowDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { AxialPos::corner_offset_in_world_row(corner_direction, grid_size) } @@ -41,7 +41,7 @@ impl RowOddPos { pub fn corner_in_world( &self, corner_direction: HexRowDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_row(corner_direction, grid_size) @@ -121,7 +121,7 @@ impl RowEvenPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexRowDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { AxialPos::corner_offset_in_world_row(corner_direction, grid_size) } @@ -132,7 +132,7 @@ impl RowEvenPos { pub fn corner_in_world( &self, corner_direction: HexRowDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_row(corner_direction, grid_size) @@ -212,7 +212,7 @@ impl ColOddPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexColDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { AxialPos::corner_offset_in_world_col(corner_direction, grid_size) } @@ -223,7 +223,7 @@ impl ColOddPos { pub fn corner_in_world( &self, corner_direction: HexColDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_col(corner_direction, grid_size) @@ -303,7 +303,7 @@ impl ColEvenPos { #[inline] pub fn corner_offset_in_world( corner_direction: HexColDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { AxialPos::corner_offset_in_world_col(corner_direction, grid_size) } @@ -314,7 +314,7 @@ impl ColEvenPos { pub fn corner_in_world( &self, corner_direction: HexColDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let axial_pos = AxialPos::from(*self); axial_pos.corner_in_world_col(corner_direction, grid_size) @@ -373,7 +373,7 @@ impl From<&TilePos> for ColEvenPos { #[cfg(test)] mod tests { use super::*; - use crate::{ TilemapSize }; + use crate::TilemapSize; // Helper: a 10 × 10 map that fits all positive test coordinates. fn test_map_size() -> TilemapSize { diff --git a/src/helpers/projection.rs b/src/helpers/projection.rs index e924c0da..966d9fc0 100644 --- a/src/helpers/projection.rs +++ b/src/helpers/projection.rs @@ -1,10 +1,10 @@ use crate::helpers::hex_grid::axial::AxialPos; -use crate::helpers::hex_grid::offset::{ ColEvenPos, ColOddPos, RowEvenPos, RowOddPos }; +use crate::helpers::hex_grid::offset::{ColEvenPos, ColOddPos, RowEvenPos, RowOddPos}; use crate::helpers::square_grid::diamond::DiamondPos; use crate::helpers::square_grid::staggered::StaggeredPos; -use crate::map::{ HexCoordSystem, IsoCoordSystem }; +use crate::map::{HexCoordSystem, IsoCoordSystem}; use crate::tiles::TilePos; -use crate::{ TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType }; +use crate::{TilemapAnchor, TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType}; use bevy::math::Vec2; impl TilePos { @@ -17,7 +17,7 @@ impl TilePos { grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, map_type: &TilemapType, - anchor: &TilemapAnchor + anchor: &TilemapAnchor, ) -> Vec2 { let offset = anchor.as_offset(map_size, grid_size, tile_size, map_type); offset + self.center_in_world_unanchored(grid_size, map_type) @@ -26,27 +26,24 @@ impl TilePos { pub(crate) fn center_in_world_unanchored( &self, grid_size: &TilemapGridSize, - map_type: &TilemapType + map_type: &TilemapType, ) -> Vec2 { match map_type { TilemapType::Square => { Vec2::new(grid_size.x * (self.x as f32), grid_size.y * (self.y as f32)) } - TilemapType::Hexagon(hex_coord_sys) => - match hex_coord_sys { - HexCoordSystem::RowEven => RowEvenPos::from(self).center_in_world(grid_size), - HexCoordSystem::RowOdd => RowOddPos::from(self).center_in_world(grid_size), - HexCoordSystem::ColumnEven => ColEvenPos::from(self).center_in_world(grid_size), - HexCoordSystem::ColumnOdd => ColOddPos::from(self).center_in_world(grid_size), - HexCoordSystem::Row => AxialPos::from(self).center_in_world_row(grid_size), - HexCoordSystem::Column => AxialPos::from(self).center_in_world_col(grid_size), - } - TilemapType::Isometric(coord_system) => - match coord_system { - IsoCoordSystem::Diamond => DiamondPos::from(self).center_in_world(grid_size), - IsoCoordSystem::Staggered => - StaggeredPos::from(self).center_in_world(grid_size), - } + TilemapType::Hexagon(hex_coord_sys) => match hex_coord_sys { + HexCoordSystem::RowEven => RowEvenPos::from(self).center_in_world(grid_size), + HexCoordSystem::RowOdd => RowOddPos::from(self).center_in_world(grid_size), + HexCoordSystem::ColumnEven => ColEvenPos::from(self).center_in_world(grid_size), + HexCoordSystem::ColumnOdd => ColOddPos::from(self).center_in_world(grid_size), + HexCoordSystem::Row => AxialPos::from(self).center_in_world_row(grid_size), + HexCoordSystem::Column => AxialPos::from(self).center_in_world_col(grid_size), + }, + TilemapType::Isometric(coord_system) => match coord_system { + IsoCoordSystem::Diamond => DiamondPos::from(self).center_in_world(grid_size), + IsoCoordSystem::Staggered => StaggeredPos::from(self).center_in_world(grid_size), + }, } } @@ -77,7 +74,7 @@ impl TilePos { grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, map_type: &TilemapType, - anchor: &TilemapAnchor + anchor: &TilemapAnchor, ) -> Option { let offset = anchor.as_offset(map_size, grid_size, tile_size, map_type); let pos = world_pos - offset; @@ -90,41 +87,28 @@ impl TilePos { } TilemapType::Hexagon(hex_coord_sys) => { match hex_coord_sys { - HexCoordSystem::RowEven => - RowEvenPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( - map_size - ), - HexCoordSystem::RowOdd => - RowOddPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( - map_size - ), - HexCoordSystem::ColumnEven => - ColEvenPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( - map_size - ), - HexCoordSystem::ColumnOdd => - ColOddPos::from_world_pos(&pos, grid_size).as_tile_pos_given_map_size( - map_size - ), - HexCoordSystem::Row => - AxialPos::from_world_pos_row(&pos, grid_size).as_tile_pos_given_map_size( - map_size - ), - HexCoordSystem::Column => - AxialPos::from_world_pos_col(&pos, grid_size).as_tile_pos_given_map_size( - map_size - ), + HexCoordSystem::RowEven => RowEvenPos::from_world_pos(&pos, grid_size) + .as_tile_pos_given_map_size(map_size), + HexCoordSystem::RowOdd => RowOddPos::from_world_pos(&pos, grid_size) + .as_tile_pos_given_map_size(map_size), + HexCoordSystem::ColumnEven => ColEvenPos::from_world_pos(&pos, grid_size) + .as_tile_pos_given_map_size(map_size), + HexCoordSystem::ColumnOdd => ColOddPos::from_world_pos(&pos, grid_size) + .as_tile_pos_given_map_size(map_size), + HexCoordSystem::Row => AxialPos::from_world_pos_row(&pos, grid_size) + .as_tile_pos_given_map_size(map_size), + HexCoordSystem::Column => AxialPos::from_world_pos_col(&pos, grid_size) + .as_tile_pos_given_map_size(map_size), } } - TilemapType::Isometric(coord_system) => - match coord_system { - IsoCoordSystem::Diamond => { - DiamondPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) - } - IsoCoordSystem::Staggered => { - StaggeredPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) - } + TilemapType::Isometric(coord_system) => match coord_system { + IsoCoordSystem::Diamond => { + DiamondPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) + } + IsoCoordSystem::Staggered => { + StaggeredPos::from_world_pos(&pos, grid_size).as_tile_pos(map_size) } + }, } } } diff --git a/src/helpers/square_grid/diamond.rs b/src/helpers/square_grid/diamond.rs index 790f95c5..f1ac643e 100644 --- a/src/helpers/square_grid/diamond.rs +++ b/src/helpers/square_grid/diamond.rs @@ -1,12 +1,12 @@ //! Code for the isometric diamond coordinate system. use crate::helpers::square_grid::SquarePos; -use crate::helpers::square_grid::neighbors::{ SQUARE_OFFSETS, SquareDirection }; +use crate::helpers::square_grid::neighbors::{SQUARE_OFFSETS, SquareDirection}; use crate::helpers::square_grid::staggered::StaggeredPos; use crate::tiles::TilePos; -use crate::{ TilemapGridSize, TilemapSize }; -use bevy::math::{ Mat2, Vec2 }; -use std::ops::{ Add, Mul, Sub }; +use crate::{TilemapGridSize, TilemapSize}; +use bevy::math::{Mat2, Vec2}; +use std::ops::{Add, Mul, Sub}; /// Position for tiles arranged in [`Diamond`](crate::map::IsoCoordSystem::Diamond) isometric /// coordinate system. @@ -151,7 +151,7 @@ impl DiamondPos { #[inline] pub fn corner_offset_in_world( corner_direction: SquareDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let corner_offset = DiamondPos::from(SquarePos::from(corner_direction)); let corner_pos = 0.5 * Vec2::new(corner_offset.x as f32, corner_offset.y as f32); @@ -164,7 +164,7 @@ impl DiamondPos { pub fn corner_in_world( &self, corner_direction: SquareDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let center = Vec2::new(self.x as f32, self.y as f32); @@ -208,9 +208,11 @@ impl TilePos { pub fn diamond_offset( &self, direction: &SquareDirection, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> Option { - DiamondPos::from(self).offset(direction).as_tile_pos(map_size) + DiamondPos::from(self) + .offset(direction) + .as_tile_pos(map_size) } } diff --git a/src/helpers/square_grid/mod.rs b/src/helpers/square_grid/mod.rs index 2010cd61..25ece6d3 100644 --- a/src/helpers/square_grid/mod.rs +++ b/src/helpers/square_grid/mod.rs @@ -3,12 +3,12 @@ pub mod neighbors; pub mod staggered; use crate::helpers::square_grid::diamond::DiamondPos; -use crate::helpers::square_grid::neighbors::{ SQUARE_OFFSETS, SquareDirection }; +use crate::helpers::square_grid::neighbors::{SQUARE_OFFSETS, SquareDirection}; use crate::helpers::square_grid::staggered::StaggeredPos; use crate::tiles::TilePos; -use crate::{ TilemapGridSize, TilemapSize }; +use crate::{TilemapGridSize, TilemapSize}; use bevy::math::Vec2; -use std::ops::{ Add, Mul, Sub }; +use std::ops::{Add, Mul, Sub}; /// Position for tiles arranged in a square coordinate system. /// @@ -128,7 +128,7 @@ impl SquarePos { #[inline] pub fn corner_offset_in_world( corner_direction: SquareDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let corner_offset = SquarePos::from(corner_direction); let corner_pos = 0.5 * Vec2::new(corner_offset.x as f32, corner_offset.y as f32); @@ -141,7 +141,7 @@ impl SquarePos { pub fn corner_in_world( &self, corner_direction: SquareDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let center = Vec2::new(self.x as f32, self.y as f32); @@ -185,9 +185,11 @@ impl TilePos { pub fn square_offset( &self, direction: &SquareDirection, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> Option { - SquarePos::from(self).offset(direction).as_tile_pos(map_size) + SquarePos::from(self) + .offset(direction) + .as_tile_pos(map_size) } } @@ -195,7 +197,7 @@ impl TilePos { mod tests { use super::*; use crate::helpers::square_grid::neighbors::SquareDirection; - use crate::{ TilemapGridSize, TilemapSize, tiles::TilePos }; + use crate::{TilemapGridSize, TilemapSize, tiles::TilePos}; fn gs() -> TilemapGridSize { TilemapGridSize { x: 32.0, y: 32.0 } diff --git a/src/helpers/square_grid/neighbors.rs b/src/helpers/square_grid/neighbors.rs index b303c2aa..cef9f050 100644 --- a/src/helpers/square_grid/neighbors.rs +++ b/src/helpers/square_grid/neighbors.rs @@ -1,9 +1,9 @@ use crate::helpers::square_grid::SquarePos; use crate::helpers::square_grid::staggered::StaggeredPos; use crate::map::TilemapSize; -use crate::prelude::{ TilePos, TileStorage }; +use crate::prelude::{TilePos, TileStorage}; use bevy::prelude::Entity; -use std::ops::{ Add, Sub }; +use std::ops::{Add, Sub}; /// The eight directions in which a neighbor may lie, on a square-like grid. /// @@ -232,7 +232,9 @@ impl Neighbors { /// /// If a neighbor is `None`, this iterator will skip it. pub fn iter(&self) -> impl Iterator + '_ { - SQUARE_DIRECTIONS.into_iter().filter_map(|direction| self.get(direction)) + SQUARE_DIRECTIONS + .into_iter() + .filter_map(|direction| self.get(direction)) } /// Iterate over neighbors, in the order specified by [`SQUARE_DIRECTIONS`]. @@ -240,14 +242,17 @@ impl Neighbors { /// /// If a neighbor is `None`, this iterator will skip it. pub fn iter_with_direction(&self) -> impl Iterator + '_ { - SQUARE_DIRECTIONS.into_iter().filter_map(|direction| - self.get(direction).map(|value| (direction, value)) - ) + SQUARE_DIRECTIONS + .into_iter() + .filter_map(|direction| self.get(direction).map(|value| (direction, value))) } /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by value. - pub fn and_then(self, f: F) -> Neighbors where F: Fn(T) -> Option { + pub fn and_then(self, f: F) -> Neighbors + where + F: Fn(T) -> Option, + { Neighbors { east: self.east.and_then(&f), north_east: self.north_east.and_then(&f), @@ -262,7 +267,10 @@ impl Neighbors { /// Applies the supplied closure `f` with an [`and_then`](std::option::Option::and_then) to each /// neighbor element, where `f` takes `T` by reference. - pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> Neighbors where F: Fn(&'a T) -> Option { + pub fn and_then_ref<'a, U, F>(&'a self, f: F) -> Neighbors + where + F: Fn(&'a T) -> Option, + { Neighbors { east: self.east.as_ref().and_then(&f), north_east: self.north_east.as_ref().and_then(&f), @@ -277,7 +285,10 @@ impl Neighbors { /// Applies the supplied closure `f` with a [`map`](std::option::Option::map) to each /// neighbor element, where `f` takes `T` by reference. - pub fn map_ref<'a, U, F>(&'a self, f: F) -> Neighbors where F: Fn(&'a T) -> U { + pub fn map_ref<'a, U, F>(&'a self, f: F) -> Neighbors + where + F: Fn(&'a T) -> U, + { Neighbors { east: self.east.as_ref().map(&f), north_east: self.north_east.as_ref().map(&f), @@ -293,7 +304,8 @@ impl Neighbors { /// Generates `SquareNeighbors` from a closure that takes a hex direction and outputs /// `Option`. pub fn from_directional_closure(f: F) -> Neighbors - where F: Fn(SquareDirection) -> Option + where + F: Fn(SquareDirection) -> Option, { use SquareDirection::*; Neighbors { @@ -331,12 +343,12 @@ impl Neighbors { pub fn get_square_neighboring_positions( tile_pos: &TilePos, map_size: &TilemapSize, - include_diagonals: bool + include_diagonals: bool, ) -> Neighbors { let square_pos = SquarePos::from(tile_pos); if include_diagonals { - let f = |direction: SquareDirection| - square_pos.offset(&direction).as_tile_pos(map_size); + let f = + |direction: SquareDirection| square_pos.offset(&direction).as_tile_pos(map_size); Neighbors::from_directional_closure(f) } else { @@ -360,12 +372,12 @@ impl Neighbors { pub fn get_staggered_neighboring_positions( tile_pos: &TilePos, map_size: &TilemapSize, - include_diagonals: bool + include_diagonals: bool, ) -> Neighbors { let staggered_pos = StaggeredPos::from(tile_pos); if include_diagonals { - let f = |direction: SquareDirection| - staggered_pos.offset(&direction).as_tile_pos(map_size); + let f = + |direction: SquareDirection| staggered_pos.offset(&direction).as_tile_pos(map_size); Neighbors::from_directional_closure(f) } else { @@ -438,7 +450,13 @@ mod tests { // iter_with_direction gives the same ordering and pairs let pairs: Vec<_> = n.iter_with_direction().collect(); - assert_eq!(pairs, vec![(SquareDirection::East, &11), (SquareDirection::SouthWest, &42)]); + assert_eq!( + pairs, + vec![ + (SquareDirection::East, &11), + (SquareDirection::SouthWest, &42) + ] + ); } #[test] diff --git a/src/helpers/square_grid/staggered.rs b/src/helpers/square_grid/staggered.rs index 4427113f..ef352f96 100644 --- a/src/helpers/square_grid/staggered.rs +++ b/src/helpers/square_grid/staggered.rs @@ -1,10 +1,10 @@ use crate::helpers::square_grid::SquarePos; use crate::helpers::square_grid::diamond::DiamondPos; -use crate::helpers::square_grid::neighbors::{ SQUARE_OFFSETS, SquareDirection }; +use crate::helpers::square_grid::neighbors::{SQUARE_OFFSETS, SquareDirection}; use crate::tiles::TilePos; -use crate::{ TilemapGridSize, TilemapSize }; +use crate::{TilemapGridSize, TilemapSize}; use bevy::math::Vec2; -use std::ops::{ Add, Mul, Sub }; +use std::ops::{Add, Mul, Sub}; /// Position for tiles arranged in [`Staggered`](crate::map::IsoCoordSystem::Diamond) isometric /// coordinate system. @@ -105,7 +105,7 @@ impl StaggeredPos { #[inline] pub fn corner_offset_in_world( corner_direction: SquareDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { DiamondPos::corner_offset_in_world(corner_direction, grid_size) } @@ -116,7 +116,7 @@ impl StaggeredPos { pub fn corner_in_world( &self, corner_direction: SquareDirection, - grid_size: &TilemapGridSize + grid_size: &TilemapGridSize, ) -> Vec2 { let diamond_pos = DiamondPos::from(self); @@ -157,17 +157,19 @@ impl TilePos { pub fn staggered_offset( &self, direction: &SquareDirection, - map_size: &TilemapSize + map_size: &TilemapSize, ) -> Option { - StaggeredPos::from(self).offset(direction).as_tile_pos(map_size) + StaggeredPos::from(self) + .offset(direction) + .as_tile_pos(map_size) } } #[cfg(test)] mod tests { use super::*; - use crate::helpers::square_grid::diamond::DiamondPos; use crate::helpers::square_grid::SquarePos; + use crate::helpers::square_grid::diamond::DiamondPos; use crate::helpers::square_grid::neighbors::SquareDirection::*; #[test] diff --git a/src/helpers/transform.rs b/src/helpers/transform.rs index 945c3ab4..66dd1884 100644 --- a/src/helpers/transform.rs +++ b/src/helpers/transform.rs @@ -1,6 +1,6 @@ use crate::tiles::TilePos; -use crate::{ TilemapGridSize, TilemapTileSize, TilemapType }; -use bevy::math::{ UVec2, Vec2, Vec3 }; +use crate::{TilemapGridSize, TilemapTileSize, TilemapType}; +use bevy::math::{UVec2, Vec2, Vec3}; use bevy::render::primitives::Aabb; /// Calculates the world-space position of the bottom-left of the specified chunk. @@ -8,7 +8,7 @@ pub fn chunk_index_to_world_space( chunk_index: UVec2, chunk_size: UVec2, grid_size: &TilemapGridSize, - map_type: &TilemapType + map_type: &TilemapType, ) -> Vec2 { // Get the position of the bottom left tile of the chunk: the "anchor tile". let anchor_tile_pos = TilePos { @@ -30,7 +30,7 @@ pub fn chunk_aabb( chunk_size: UVec2, grid_size: &TilemapGridSize, tile_size: &TilemapTileSize, - map_type: &TilemapType + map_type: &TilemapType, ) -> Aabb { // The AABB minimum and maximum have to be modified by -border and +border respectively. let border = Vec2::from(grid_size).max(tile_size.into()) / 2.0; @@ -55,7 +55,7 @@ pub fn chunk_aabb( mod tests { use super::*; use approx::assert_relative_eq; - use bevy::math::{ UVec2, Vec3 }; + use bevy::math::{UVec2, Vec3}; #[test] fn chunk_index_to_world_space_origin_square() { @@ -63,7 +63,8 @@ mod tests { let grid_size = TilemapGridSize { x: 1.0, y: 1.0 }; // Bottom-left chunk (index 0,0) should have its anchor-tile centre at (0.0, 0.0) - let ws = chunk_index_to_world_space(UVec2::ZERO, chunk_size, &grid_size, &TilemapType::Square); + let ws = + chunk_index_to_world_space(UVec2::ZERO, chunk_size, &grid_size, &TilemapType::Square); assert_relative_eq!(ws.x, 0.0, epsilon = 1e-6); assert_relative_eq!(ws.y, 0.0, epsilon = 1e-6); } diff --git a/src/lib.rs b/src/lib.rs index 8fe64183..f1a06aa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,21 +18,8 @@ use bevy::{ ecs::schedule::IntoScheduleConfigs, prelude::{ - Bundle, - Changed, - Component, - Deref, - First, - GlobalTransform, - InheritedVisibility, - Plugin, - Query, - Reflect, - ReflectComponent, - SystemSet, - Transform, - ViewVisibility, - Visibility, + Bundle, Changed, Component, Deref, First, GlobalTransform, InheritedVisibility, Plugin, + Query, Reflect, ReflectComponent, SystemSet, Transform, ViewVisibility, Visibility, }, render::sync_world::SyncToRenderWorld, time::TimeSystem, @@ -43,30 +30,19 @@ use render::material::MaterialTilemapHandle; use anchor::TilemapAnchor; use map::{ - TilemapGridSize, - TilemapSize, - TilemapSpacing, - TilemapTexture, - TilemapTextureSize, - TilemapTileSize, - TilemapType, + TilemapGridSize, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, + TilemapTileSize, TilemapType, }; -use prelude::{ TilemapId, TilemapRenderSettings }; +use prelude::{TilemapId, TilemapRenderSettings}; #[cfg(feature = "render")] -use render::material::{ MaterialTilemap, StandardTilemapMaterial }; +use render::material::{MaterialTilemap, StandardTilemapMaterial}; use tiles::{ - AnimatedTile, - TileColor, - TileFlip, - TilePos, - TilePosOld, - TileStorage, - TileTextureIndex, + AnimatedTile, TileColor, TileFlip, TilePos, TilePosOld, TileStorage, TileTextureIndex, TileVisible, }; #[cfg(all(not(feature = "atlas"), feature = "render"))] -use bevy::render::{ ExtractSchedule, RenderApp }; +use bevy::render::{ExtractSchedule, RenderApp}; pub mod anchor; /// A module that allows pre-loading of atlases into array textures. diff --git a/src/map.rs b/src/map.rs index ca8e09fc..c74a5f8e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,20 +1,17 @@ use bevy::{ asset::Assets, - ecs::{ entity::{ EntityMapper, MapEntities }, reflect::ReflectMapEntities }, - math::{ UVec2, Vec2 }, + ecs::{ + entity::{EntityMapper, MapEntities}, + reflect::ReflectMapEntities, + }, + math::{UVec2, Vec2}, prelude::{ - Component, - Deref, - DerefMut, - Entity, - Handle, - Image, - Reflect, - ReflectComponent, - Res, - ResMut, + Component, Deref, DerefMut, Entity, Handle, Image, Reflect, ReflectComponent, Res, ResMut, + }, + render::{ + render_resource::TextureUsages, + view::{VisibilityClass, add_visibility_class}, }, - render::{ render_resource::TextureUsages, view::{ VisibilityClass, add_visibility_class } }, }; use std::ops::Add; @@ -181,15 +178,16 @@ impl TilemapTexture { } #[cfg(not(feature = "atlas"))] - self.image_handles() - .into_iter() - .all(|h| { - if let Some(image) = images.get(h) { - image.texture_descriptor.usage.contains(TextureUsages::COPY_SRC) - } else { - false - } - }) + self.image_handles().into_iter().all(|h| { + if let Some(image) = images.get(h) { + image + .texture_descriptor + .usage + .contains(TextureUsages::COPY_SRC) + } else { + false + } + }) } /// Sets images with the `COPY_SRC` flag. @@ -198,12 +196,15 @@ impl TilemapTexture { // NOTE: We retrieve it non-mutably first to avoid triggering an `AssetEvent::Modified` // if we didn't actually need to modify it if let Some(image) = images.get(handle) { - if !image.texture_descriptor.usage.contains(TextureUsages::COPY_SRC) { + if !image + .texture_descriptor + .usage + .contains(TextureUsages::COPY_SRC) + { if let Some(image) = images.get_mut(handle) { - image.texture_descriptor.usage = - TextureUsages::TEXTURE_BINDING | - TextureUsages::COPY_SRC | - TextureUsages::COPY_DST; + image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_SRC + | TextureUsages::COPY_DST; }; } } @@ -215,12 +216,7 @@ impl TilemapTexture { TilemapTexture::Single(handle) => TilemapTexture::Single(handle.clone_weak()), #[cfg(not(feature = "atlas"))] TilemapTexture::Vector(handles) => { - TilemapTexture::Vector( - handles - .iter() - .map(|h| h.clone_weak()) - .collect() - ) + TilemapTexture::Vector(handles.iter().map(|h| h.clone_weak()).collect()) } #[cfg(not(feature = "atlas"))] TilemapTexture::TextureContainer(handle) => { diff --git a/src/render/chunk.rs b/src/render/chunk.rs index f8484e4a..222410cf 100644 --- a/src/render/chunk.rs +++ b/src/render/chunk.rs @@ -1,31 +1,29 @@ -use std::hash::{ Hash, Hasher }; +use std::hash::{Hash, Hasher}; use bevy::platform::collections::HashMap; use bevy::render::render_asset::RenderAssetUsages; use bevy::render::render_resource::Buffer; -use bevy::render::{ mesh::BaseMeshPipelineKey, primitives::Aabb }; -use bevy::{ math::Mat4, render::mesh::PrimitiveTopology }; +use bevy::render::{mesh::BaseMeshPipelineKey, primitives::Aabb}; +use bevy::{math::Mat4, render::mesh::PrimitiveTopology}; use bevy::{ - math::{ UVec2, UVec3, UVec4, Vec2, Vec3Swizzles, Vec4, Vec4Swizzles }, - prelude::{ Component, Entity, GlobalTransform, Mesh }, + math::{UVec2, UVec3, UVec4, Vec2, Vec3Swizzles, Vec4, Vec4Swizzles}, + prelude::{Component, Entity, GlobalTransform, Mesh}, render::{ - mesh::{ Indices, RenderMesh, RenderMeshBufferInfo, VertexAttributeValues }, - render_resource::{ BufferInitDescriptor, BufferUsages, ShaderType }, + mesh::{Indices, RenderMesh, RenderMeshBufferInfo, VertexAttributeValues}, + render_resource::{BufferInitDescriptor, BufferUsages, ShaderType}, renderer::RenderDevice, }, }; use bevy::{ - prelude::{ InheritedVisibility, Resource, Transform }, + prelude::{InheritedVisibility, Resource, Transform}, render::mesh::MeshVertexBufferLayouts, }; -use crate::prelude::helpers::transform::{ chunk_aabb, chunk_index_to_world_space }; +use crate::prelude::helpers::transform::{chunk_aabb, chunk_index_to_world_space}; use crate::render::extract::ExtractedFrustum; use crate::{ - FrustumCulling, - TilemapGridSize, - TilemapTileSize, - map::{ TilemapSize, TilemapTexture, TilemapType }, + FrustumCulling, TilemapGridSize, TilemapTileSize, + map::{TilemapSize, TilemapTexture, TilemapType}, tiles::TilePos, }; @@ -61,11 +59,12 @@ impl RenderChunk2dStorage { visibility: &InheritedVisibility, frustum_culling: &FrustumCulling, render_size: RenderChunkSize, - y_sort: bool + y_sort: bool, ) -> &mut RenderChunk2d { let pos = position.xyz(); - self.entity_to_chunk_tile.insert(tile_entity, (position.w, pos, tile_pos)); + self.entity_to_chunk_tile + .insert(tile_entity, (position.w, pos, tile_pos)); let chunk_storage = if self.chunks.contains_key(&position.w) { self.chunks.get_mut(&position.w).unwrap() @@ -97,7 +96,7 @@ impl RenderChunk2dStorage { visibility.get(), **frustum_culling, render_size, - y_sort + y_sort, ); self.entity_to_chunk.insert(chunk_entity, pos); chunk_storage.insert(pos, chunk); @@ -164,7 +163,9 @@ impl RenderChunk2dStorage { } pub fn iter_mut(&mut self) -> impl Iterator { - self.chunks.iter_mut().flat_map(|(_, x)| x.iter_mut().map(|x| x.1)) + self.chunks + .iter_mut() + .flat_map(|(_, x)| x.iter_mut().map(|x| x.1)) } pub fn remove_map(&mut self, entity: Entity) { @@ -242,7 +243,7 @@ impl RenderChunk2d { visible: bool, frustum_culling: bool, render_size: RenderChunkSize, - y_sort: bool + y_sort: bool, ) -> Self { let position = chunk_index_to_world_space(index.xy(), size_in_tiles, &grid_size, &map_type); let local_transform = Transform::from_translation(position.extend(0.0)); @@ -268,7 +269,7 @@ impl RenderChunk2d { transform_matrix, mesh: Mesh::new( bevy::render::render_resource::PrimitiveTopology::TriangleList, - RenderAssetUsages::default() + RenderAssetUsages::default(), ), vertex_buffer: None, index_buffer: None, @@ -323,7 +324,7 @@ impl RenderChunk2d { global_transform: Transform, grid_size: TilemapGridSize, tile_size: TilemapTileSize, - map_type: TilemapType + map_type: TilemapType, ) { let mut dirty_local_transform = false; @@ -336,7 +337,7 @@ impl RenderChunk2d { self.index.xy(), self.size_in_tiles, &self.grid_size, - &self.map_type + &self.map_type, ); self.local_transform = Transform::from_translation(self.position.extend(0.0)); @@ -346,7 +347,7 @@ impl RenderChunk2d { self.size_in_tiles, &self.grid_size, &self.tile_size, - &self.map_type + &self.map_type, ); } @@ -365,16 +366,15 @@ impl RenderChunk2d { pub fn prepare( &mut self, device: &RenderDevice, - mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts + mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, ) { if self.dirty_mesh { let size = (self.size_in_tiles.x * self.size_in_tiles.y * 4) as usize; let mut positions: Vec<[f32; 4]> = Vec::with_capacity(size); let mut textures: Vec<[f32; 4]> = Vec::with_capacity(size); let mut colors: Vec<[f32; 4]> = Vec::with_capacity(size); - let mut indices: Vec = Vec::with_capacity( - (self.size_in_tiles.x * self.size_in_tiles.y * 6) as usize - ); + let mut indices: Vec = + Vec::with_capacity((self.size_in_tiles.x * self.size_in_tiles.y * 6) as usize); let mut i = 0; @@ -398,7 +398,8 @@ impl RenderChunk2d { // X + 1, Y //[tile_pos.x + 1.0, tile_pos.y, animation_speed], position, - ].into_iter() + ] + .into_iter(), ); colors.extend(std::iter::repeat_n(tile.color, 4)); @@ -421,15 +422,15 @@ impl RenderChunk2d { self.mesh.insert_attribute( crate::render::ATTRIBUTE_POSITION, - VertexAttributeValues::Float32x4(positions) + VertexAttributeValues::Float32x4(positions), ); self.mesh.insert_attribute( crate::render::ATTRIBUTE_TEXTURE, - VertexAttributeValues::Float32x4(textures) + VertexAttributeValues::Float32x4(textures), ); self.mesh.insert_attribute( crate::render::ATTRIBUTE_COLOR, - VertexAttributeValues::Float32x4(colors) + VertexAttributeValues::Float32x4(colors), ); self.mesh.insert_indices(Indices::U32(indices)); @@ -439,7 +440,7 @@ impl RenderChunk2d { usage: BufferUsages::VERTEX, label: Some("Mesh Vertex Buffer"), contents: &vertex_buffer_data, - }) + }), ); let index_buffer = device.create_buffer_with_data( @@ -447,7 +448,7 @@ impl RenderChunk2d { usage: BufferUsages::INDEX, contents: self.mesh.get_index_buffer_bytes().unwrap(), label: Some("Mesh Index Buffer"), - }) + }), ); let buffer_info = RenderMeshBufferInfo::Indexed { @@ -455,16 +456,16 @@ impl RenderChunk2d { index_format: self.mesh.indices().unwrap().into(), }; - let mesh_vertex_buffer_layout = self.mesh.get_mesh_vertex_buffer_layout( - mesh_vertex_buffer_layouts - ); + let mesh_vertex_buffer_layout = self + .mesh + .get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts); self.render_mesh = Some(RenderMesh { vertex_count: self.mesh.count_vertices() as u32, buffer_info, morph_targets: None, layout: mesh_vertex_buffer_layout, key_bits: BaseMeshPipelineKey::from_primitive_topology( - PrimitiveTopology::TriangleList + PrimitiveTopology::TriangleList, ), }); self.vertex_buffer = Some(vertex_buffer); diff --git a/src/render/draw.rs b/src/render/draw.rs index 005159a4..a31bf9f3 100644 --- a/src/render/draw.rs +++ b/src/render/draw.rs @@ -2,11 +2,14 @@ use std::marker::PhantomData; use bevy::{ core_pipeline::core_2d::Transparent2d, - ecs::system::{ SystemParamItem, lifetimeless::{ Read, SQuery, SRes } }, + ecs::system::{ + SystemParamItem, + lifetimeless::{Read, SQuery, SRes}, + }, math::UVec4, render::{ mesh::RenderMeshBufferInfo, - render_phase::{ RenderCommand, RenderCommandResult, TrackedRenderPass }, + render_phase::{RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::PipelineCache, view::ViewUniformOffset, }, @@ -17,10 +20,10 @@ use crate::map::TilemapId; use super::{ DynamicUniformIndex, - chunk::{ ChunkId, RenderChunk2dStorage, TilemapUniformData }, - material::{ MaterialTilemap, MaterialTilemapHandle, RenderMaterialsTilemap }, + chunk::{ChunkId, RenderChunk2dStorage, TilemapUniformData}, + material::{MaterialTilemap, MaterialTilemapHandle, RenderMaterialsTilemap}, prepare::MeshUniform, - queue::{ ImageBindGroups, TilemapViewBindGroup, TransformBindGroup }, + queue::{ImageBindGroups, TilemapViewBindGroup, TransformBindGroup}, }; pub struct SetMeshViewBindGroup; @@ -34,7 +37,7 @@ impl RenderCommand for SetMeshViewBindGroup { (view_uniform, pbr_view_bind_group): (&'w ViewUniformOffset, &'w TilemapViewBindGroup), _entity: Option<()>, _param: (), - pass: &mut TrackedRenderPass<'w> + pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { pass.set_bind_group(I, &pbr_view_bind_group.value, &[view_uniform.offset]); @@ -54,11 +57,12 @@ impl RenderCommand for SetTransformBindGroup { fn render<'w>( _item: &Transparent2d, _view: (), - uniform_indices: Option< - (&'w DynamicUniformIndex, &'w DynamicUniformIndex) - >, + uniform_indices: Option<( + &'w DynamicUniformIndex, + &'w DynamicUniformIndex, + )>, transform_bind_group: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w> + pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some((transform_index, tilemap_index)) = uniform_indices else { return RenderCommandResult::Skip; @@ -67,7 +71,7 @@ impl RenderCommand for SetTransformBindGroup { pass.set_bind_group( I, &transform_bind_group.into_inner().value, - &[transform_index.index(), tilemap_index.index()] + &[transform_index.index(), tilemap_index.index()], ); RenderCommandResult::Success @@ -85,7 +89,7 @@ impl RenderCommand for SetTextureBindGroup { _view: (), texture: Option<&'w TilemapTexture>, image_bind_groups: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w> + pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some(texture) = texture else { return RenderCommandResult::Skip; @@ -109,9 +113,12 @@ impl RenderCommand for SetItemPipeline { _view: (), _entity: Option<()>, pipeline_cache: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w> + pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache.into_inner().get_render_pipeline(item.pipeline) { + if let Some(pipeline) = pipeline_cache + .into_inner() + .get_render_pipeline(item.pipeline) + { pass.set_render_pipeline(pipeline); RenderCommandResult::Success } else { @@ -139,8 +146,12 @@ pub type DrawTilemapMaterial = ( pub struct SetMaterialBindGroup(PhantomData); impl RenderCommand -for SetMaterialBindGroup { - type Param = (SRes>, SQuery<&'static MaterialTilemapHandle>); + for SetMaterialBindGroup +{ + type Param = ( + SRes>, + SQuery<&'static MaterialTilemapHandle>, + ); type ViewQuery = (); type ItemQuery = Read; #[inline] @@ -149,14 +160,17 @@ for SetMaterialBindGroup { _view: (), id: Option<&'w TilemapId>, (material_bind_groups, material_handles): SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w> + pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some(id) = id else { return RenderCommandResult::Skip; }; if let Ok(material_handle) = material_handles.get(id.0) { - let bind_group = material_bind_groups.into_inner().get(&material_handle.id()).unwrap(); + let bind_group = material_bind_groups + .into_inner() + .get(&material_handle.id()) + .unwrap(); pass.set_bind_group(I, &bind_group.bind_group, &[]); } @@ -175,31 +189,33 @@ impl RenderCommand for DrawMesh { _view: (), ids: Option<(&'w ChunkId, &'w TilemapId)>, chunk_storage: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w> + pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some((chunk_id, tilemap_id)) = ids else { return RenderCommandResult::Skip; }; - if - let Some(chunk) = chunk_storage - .into_inner() - .get(&UVec4::new(chunk_id.0.x, chunk_id.0.y, chunk_id.0.z, tilemap_id.0.index())) - { - if - let (Some(render_mesh), Some(vertex_buffer), Some(index_buffer)) = ( - &chunk.render_mesh, - &chunk.vertex_buffer, - &chunk.index_buffer, - ) - { + if let Some(chunk) = chunk_storage.into_inner().get(&UVec4::new( + chunk_id.0.x, + chunk_id.0.y, + chunk_id.0.z, + tilemap_id.0.index(), + )) { + if let (Some(render_mesh), Some(vertex_buffer), Some(index_buffer)) = ( + &chunk.render_mesh, + &chunk.vertex_buffer, + &chunk.index_buffer, + ) { if render_mesh.vertex_count == 0 { return RenderCommandResult::Skip; } pass.set_vertex_buffer(0, vertex_buffer.slice(..)); match &render_mesh.buffer_info { - RenderMeshBufferInfo::Indexed { index_format, count } => { + RenderMeshBufferInfo::Indexed { + index_format, + count, + } => { pass.set_index_buffer(index_buffer.slice(..), 0, *index_format); pass.draw_indexed(0..*count, 0, 0..1); } diff --git a/src/render/extract.rs b/src/render/extract.rs index d4660d79..c2a8c026 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -3,8 +3,8 @@ use bevy::{ platform::collections::HashMap, prelude::*, render::Extract, - render::primitives::{ Aabb, Frustum }, - render::render_resource::{ FilterMode, TextureFormat }, + render::primitives::{Aabb, Frustum}, + render::render_resource::{FilterMode, TextureFormat}, render::sync_world::RenderEntity, }; @@ -17,15 +17,10 @@ use crate::tiles::TilePosOld; use crate::{ FrustumCulling, map::{ - TilemapId, - TilemapSize, - TilemapSpacing, - TilemapTexture, - TilemapTextureSize, - TilemapTileSize, - TilemapType, + TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, + TilemapTileSize, TilemapType, }, - tiles::{ TileColor, TileFlip, TilePos, TileTextureIndex, TileVisible }, + tiles::{TileColor, TileFlip, TilePos, TileTextureIndex, TileVisible}, }; use super::chunk::PackedTileData; @@ -84,16 +79,14 @@ impl ExtractedTilemapTexture { tile_size: TilemapTileSize, tile_spacing: TilemapSpacing, filtering: FilterMode, - image_assets: &Res> + image_assets: &Res>, ) -> ExtractedTilemapTexture { let (tile_count, texture_size, format) = match &texture { TilemapTexture::Single(handle) => { - let image = image_assets - .get(handle) - .expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!" - ); + let image = image_assets.get(handle).expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!", + ); let texture_size: TilemapTextureSize = image.size_f32().into(); let tile_count_x = (texture_size.x / (tile_size.x + tile_spacing.x)).floor(); let tile_count_y = (texture_size.y / (tile_size.y + tile_spacing.y)).floor(); @@ -106,12 +99,10 @@ impl ExtractedTilemapTexture { #[cfg(not(feature = "atlas"))] TilemapTexture::Vector(handles) => { for handle in handles { - let image = image_assets - .get(handle) - .expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!" - ); + let image = image_assets.get(handle).expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!", + ); let this_tile_size: TilemapTileSize = image.size_f32().into(); if this_tile_size != tile_size { panic!( @@ -122,15 +113,16 @@ impl ExtractedTilemapTexture { } let first_format = image_assets .get(handles.first().unwrap()) - .unwrap().texture_descriptor.format; + .unwrap() + .texture_descriptor + .format; for handle in handles { let image = image_assets.get(handle).unwrap(); if image.texture_descriptor.format != first_format { panic!( "Expected all provided image assets to have a format of: {:?} but found image with format: {:?}", - first_format, - image.texture_descriptor.format + first_format, image.texture_descriptor.format ); } } @@ -139,12 +131,10 @@ impl ExtractedTilemapTexture { } #[cfg(not(feature = "atlas"))] TilemapTexture::TextureContainer(image_handle) => { - let image = image_assets - .get(image_handle) - .expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!" - ); + let image = image_assets.get(image_handle).expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!", + ); let tile_size: TilemapTileSize = image.size_f32().into(); ( image.texture_descriptor.array_layer_count(), @@ -180,7 +170,8 @@ pub struct ExtractedFrustum { impl ExtractedFrustum { pub fn intersects_obb(&self, aabb: &Aabb, transform_matrix: &Mat4) -> bool { - self.frustum.intersects_obb(aabb, &Affine3A::from_mat4(*transform_matrix), true, false) + self.frustum + .intersects_obb(aabb, &Affine3A::from_mat4(*transform_matrix), true, false) } } @@ -201,59 +192,53 @@ pub fn extract( &TileColor, Option<&AnimatedTile>, ), - Or< - ( - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - ) - > - > + Or<( + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + )>, + >, >, tilemap_query: Extract< - Query< - ( - &RenderEntity, - &GlobalTransform, - &TilemapTileSize, - &TilemapSpacing, - &TilemapGridSize, - &TilemapType, - &TilemapTexture, - &TilemapSize, - &InheritedVisibility, - &FrustumCulling, - &TilemapRenderSettings, - &TilemapAnchor, - ) - > + Query<( + &RenderEntity, + &GlobalTransform, + &TilemapTileSize, + &TilemapSpacing, + &TilemapGridSize, + &TilemapType, + &TilemapTexture, + &TilemapSize, + &InheritedVisibility, + &FrustumCulling, + &TilemapRenderSettings, + &TilemapAnchor, + )>, >, changed_tilemap_query: Extract< Query< Entity, - Or< - ( - Added, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - ) - > - > + Or<( + Added, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + Changed, + )>, + >, >, camera_query: Extract>>, - images: Extract>> + images: Extract>>, ) { let mut extracted_tiles = Vec::new(); let mut extracted_tilemaps = >::default(); @@ -269,7 +254,8 @@ pub fn extract( flip, color, animated, - ) in changed_tiles_query.iter() { + ) in changed_tiles_query.iter() + { // flipping and rotation packed in bits // bit 0 : flip_x // bit 1 : flip_y @@ -296,24 +282,27 @@ pub fn extract( let data = tilemap_query.get(tilemap_id.0).unwrap(); - extracted_tilemaps.insert(data.0.id(), ( + extracted_tilemaps.insert( data.0.id(), - ExtractedTilemapBundle { - transform: *data.1, - tile_size: *data.2, - texture_size: TilemapTextureSize::default(), - spacing: *data.3, - grid_size: *data.4, - map_type: *data.5, - texture: data.6.clone_weak(), - map_size: *data.7, - visibility: *data.8, - frustum_culling: *data.9, - render_settings: *data.10, - changed: ChangedInMainWorld, - anchor: *data.11, - }, - )); + ( + data.0.id(), + ExtractedTilemapBundle { + transform: *data.1, + tile_size: *data.2, + texture_size: TilemapTextureSize::default(), + spacing: *data.3, + grid_size: *data.4, + map_type: *data.5, + texture: data.6.clone_weak(), + map_size: *data.7, + visibility: *data.8, + frustum_culling: *data.9, + render_settings: *data.10, + changed: ChangedInMainWorld, + anchor: *data.11, + }, + ), + ); extracted_tiles.push(( render_entity.id(), ExtractedTileBundle { @@ -331,47 +320,36 @@ pub fn extract( for tilemap_entity in changed_tilemap_query.iter() { if let Ok(data) = tilemap_query.get(tilemap_entity) { - extracted_tilemaps.insert(data.0.id(), ( + extracted_tilemaps.insert( data.0.id(), - ExtractedTilemapBundle { - transform: *data.1, - tile_size: *data.2, - texture_size: TilemapTextureSize::default(), - spacing: *data.3, - grid_size: *data.4, - map_type: *data.5, - texture: data.6.clone_weak(), - map_size: *data.7, - visibility: *data.8, - frustum_culling: *data.9, - render_settings: *data.10, - changed: ChangedInMainWorld, - anchor: *data.11, - }, - )); + ( + data.0.id(), + ExtractedTilemapBundle { + transform: *data.1, + tile_size: *data.2, + texture_size: TilemapTextureSize::default(), + spacing: *data.3, + grid_size: *data.4, + map_type: *data.5, + texture: data.6.clone_weak(), + map_size: *data.7, + visibility: *data.8, + frustum_culling: *data.9, + render_settings: *data.10, + changed: ChangedInMainWorld, + anchor: *data.11, + }, + ), + ); } } - let extracted_tilemaps: Vec<_> = extracted_tilemaps - .drain() - .map(|(_, val)| val) - .collect(); + let extracted_tilemaps: Vec<_> = extracted_tilemaps.drain().map(|(_, val)| val).collect(); // Extracts tilemap textures. - for ( - render_entity, - _, - tile_size, - tile_spacing, - _, - _, - texture, - _, - _, - _, - _, - _, - ) in tilemap_query.iter() { + for (render_entity, _, tile_size, tile_spacing, _, _, texture, _, _, _, _, _) in + tilemap_query.iter() + { if texture.verify_ready(&images) { extracted_tilemap_textures.push(( render_entity.id(), @@ -382,7 +360,7 @@ pub fn extract( *tile_size, *tile_spacing, default_image_settings.0.min_filter.into(), - &images + &images, ), changed: ChangedInMainWorld, }, @@ -391,7 +369,9 @@ pub fn extract( } for (render_entity, frustum) in camera_query.iter() { - commands.entity(render_entity.id()).insert(ExtractedFrustum { frustum: *frustum }); + commands + .entity(render_entity.id()) + .insert(ExtractedFrustum { frustum: *frustum }); } commands.insert_batch(extracted_tiles); diff --git a/src/render/material.rs b/src/render/material.rs index eb562c1f..72b50b73 100644 --- a/src/render/material.rs +++ b/src/render/material.rs @@ -1,57 +1,42 @@ -use crate::prelude::{ TilemapId, TilemapRenderSettings }; +use crate::prelude::{TilemapId, TilemapRenderSettings}; use bevy::log::error; #[cfg(not(feature = "atlas"))] use bevy::render::renderer::RenderQueue; use bevy::{ core_pipeline::core_2d::Transparent2d, - ecs::system::{ StaticSystemParam, SystemParamItem }, + ecs::system::{StaticSystemParam, SystemParamItem}, math::FloatOrd, - platform::collections::{ HashMap, HashSet }, + platform::collections::{HashMap, HashSet}, prelude::*, reflect::TypePath, render::{ - Extract, - Render, - RenderApp, - RenderSet, - extract_component::{ ExtractComponent, ExtractComponentPlugin }, + Extract, Render, RenderApp, RenderSet, + extract_component::{ExtractComponent, ExtractComponentPlugin}, globals::GlobalsBuffer, render_asset::RenderAssets, render_phase::{ - AddRenderCommand, - DrawFunctions, - PhaseItemExtraIndex, - ViewSortedRenderPhases, + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, ViewSortedRenderPhases, }, render_resource::{ - AsBindGroup, - AsBindGroupError, - BindGroup, - BindGroupEntry, - BindGroupLayout, - BindingResource, - OwnedBindingResource, - PipelineCache, - RenderPipelineDescriptor, - ShaderRef, - SpecializedRenderPipeline, - SpecializedRenderPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupEntry, BindGroupLayout, + BindingResource, OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, + ShaderRef, SpecializedRenderPipeline, SpecializedRenderPipelines, }, renderer::RenderDevice, texture::GpuImage, - view::{ ExtractedView, RenderVisibleEntities, ViewUniforms }, + view::{ExtractedView, RenderVisibleEntities, ViewUniforms}, }, }; -use std::{ hash::Hash, marker::PhantomData }; +use std::{hash::Hash, marker::PhantomData}; use super::{ ModifiedImageIds, - chunk::{ ChunkId, RenderChunk2dStorage }, + chunk::{ChunkId, RenderChunk2dStorage}, draw::DrawTilemapMaterial, - pipeline::{ TilemapPipeline, TilemapPipelineKey }, + pipeline::{TilemapPipeline, TilemapPipelineKey}, prepare, - queue::{ ImageBindGroups, TilemapViewBindGroup }, + queue::{ImageBindGroups, TilemapViewBindGroup}, }; #[cfg(not(feature = "atlas"))] @@ -83,14 +68,20 @@ pub struct MaterialTilemapKey { impl Eq for MaterialTilemapKey where M::Data: PartialEq {} -impl PartialEq for MaterialTilemapKey where M::Data: PartialEq { +impl PartialEq for MaterialTilemapKey +where + M::Data: PartialEq, +{ fn eq(&self, other: &Self) -> bool { - self.tilemap_pipeline_key == other.tilemap_pipeline_key && - self.bind_group_data == other.bind_group_data + self.tilemap_pipeline_key == other.tilemap_pipeline_key + && self.bind_group_data == other.bind_group_data } } -impl Clone for MaterialTilemapKey where M::Data: Clone { +impl Clone for MaterialTilemapKey +where + M::Data: Clone, +{ fn clone(&self) -> Self { Self { tilemap_pipeline_key: self.tilemap_pipeline_key, @@ -99,7 +90,10 @@ impl Clone for MaterialTilemapKey where M::Data: Clone { } } -impl Hash for MaterialTilemapKey where M::Data: Hash { +impl Hash for MaterialTilemapKey +where + M::Data: Hash, +{ fn hash(&self, state: &mut H) { self.tilemap_pipeline_key.hash(state); self.bind_group_data.hash(state); @@ -142,14 +136,13 @@ impl Default for MaterialTilemapPlugin { } } -impl Plugin - for MaterialTilemapPlugin - where M::Data: PartialEq + Eq + Hash + Clone +impl Plugin for MaterialTilemapPlugin +where + M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - app.init_asset::().add_plugins( - ExtractComponentPlugin::>::extract_visible() - ); + app.init_asset::() + .add_plugins(ExtractComponentPlugin::>::extract_visible()); } fn finish(&self, app: &mut App) { @@ -163,19 +156,22 @@ impl Plugin .add_systems(ExtractSchedule, extract_materials_tilemap::) .add_systems( Render, - prepare_materials_tilemap::.in_set(RenderSet::PrepareAssets) + prepare_materials_tilemap::.in_set(RenderSet::PrepareAssets), ) - .add_systems(Render, ( - // Ensure `queue_material_tilemap_meshes` runs after `prepare::prepare` because `prepare` calls `commands.spawn` with `ChunkId` - // and that data is then consumed by `queue_material_tilemap_mesh`. This is important because `prepare` is part of the `PrepareAssets` - // set. Bevy is loose on its expectation of when systems in the `PrepareAssets` set execute (for performance) and only needs them - // to run before the `Prepare` set (which is after Queue). This invites the possibility of an intermittent incorrect ordering dependent - // on the scheduler. - queue_material_tilemap_meshes:: - .in_set(RenderSet::Queue) - .after(prepare::prepare), - bind_material_tilemap_meshes::.in_set(RenderSet::PrepareBindGroups), - )); + .add_systems( + Render, + ( + // Ensure `queue_material_tilemap_meshes` runs after `prepare::prepare` because `prepare` calls `commands.spawn` with `ChunkId` + // and that data is then consumed by `queue_material_tilemap_mesh`. This is important because `prepare` is part of the `PrepareAssets` + // set. Bevy is loose on its expectation of when systems in the `PrepareAssets` set execute (for performance) and only needs them + // to run before the `Prepare` set (which is after Queue). This invites the possibility of an intermittent incorrect ordering dependent + // on the scheduler. + queue_material_tilemap_meshes:: + .in_set(RenderSet::Queue) + .after(prepare::prepare), + bind_material_tilemap_meshes::.in_set(RenderSet::PrepareBindGroups), + ), + ); } } } @@ -222,9 +218,9 @@ impl Clone for MaterialTilemapPipeline { } } -impl SpecializedRenderPipeline - for MaterialTilemapPipeline - where M::Data: PartialEq + Eq + Hash + Clone +impl SpecializedRenderPipeline for MaterialTilemapPipeline +where + M::Data: PartialEq + Eq + Hash + Clone, { type Key = MaterialTilemapKey; @@ -241,7 +237,7 @@ impl SpecializedRenderPipeline self.tilemap_pipeline.view_layout.clone(), self.tilemap_pipeline.mesh_layout.clone(), self.tilemap_pipeline.material_layout.clone(), - self.material_tilemap_layout.clone() + self.material_tilemap_layout.clone(), ]; M::specialize(&mut descriptor, key); @@ -290,7 +286,7 @@ impl Default for RenderMaterialsTilemap { fn extract_materials_tilemap( mut commands: Commands, mut events: Extract>>, - assets: Extract>> + assets: Extract>>, ) { let mut changed_assets = >::default(); let mut removed = Vec::new(); @@ -343,7 +339,7 @@ fn prepare_materials_tilemap( mut render_materials: ResMut>, render_device: Res, pipeline: Res>, - mut param: StaticSystemParam + mut param: StaticSystemParam, ) { let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (handle, material) in queued_assets { @@ -393,9 +389,10 @@ fn prepare_material_tilemap( material: &M, render_device: &RenderDevice, pipeline: &MaterialTilemapPipeline, - param: &mut SystemParamItem + param: &mut SystemParamItem, ) -> Result, AsBindGroupError> { - let prepared = material.as_bind_group(&pipeline.material_tilemap_layout, render_device, param)?; + let prepared = + material.as_bind_group(&pipeline.material_tilemap_layout, render_device, param)?; Ok(PreparedMaterialTilemap { bindings: prepared.bindings.0, bind_group: prepared.bind_group, @@ -426,9 +423,9 @@ pub fn queue_material_tilemap_meshes( ResMut, Res, ), - mut transparent_render_phases: ResMut> -) - where M::Data: PartialEq + Eq + Hash + Clone + mut transparent_render_phases: ResMut>, +) where + M::Data: PartialEq + Eq + Hash + Clone, { #[cfg(not(feature = "atlas"))] texture_array_cache.queue(&_render_device, &render_queue, &gpu_images); @@ -442,9 +439,8 @@ pub fn queue_material_tilemap_meshes( } for (view, msaa, visible_entities) in views.iter_mut() { - let Some(transparent_phase) = transparent_render_phases.get_mut( - &view.retained_view_entity - ) else { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) + else { continue; }; @@ -454,11 +450,10 @@ pub fn queue_material_tilemap_meshes( .unwrap(); for (entity, chunk_id, transform, tilemap_id) in standard_tilemap_meshes.iter() { - if - !visible_entities - .get::() - .iter() - .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) + if !visible_entities + .get::() + .iter() + .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) { continue; } @@ -470,11 +465,12 @@ pub fn queue_material_tilemap_meshes( continue; }; - if - let Some(chunk) = chunk_storage.get( - &UVec4::new(chunk_id.0.x, chunk_id.0.y, chunk_id.0.z, tilemap_id.0.index()) - ) - { + if let Some(chunk) = chunk_storage.get(&UVec4::new( + chunk_id.0.x, + chunk_id.0.y, + chunk_id.0.z, + tilemap_id.0.index(), + )) { #[cfg(not(feature = "atlas"))] if !texture_array_cache.contains(&chunk.texture) { continue; @@ -497,13 +493,13 @@ pub fn queue_material_tilemap_meshes( MaterialTilemapKey { tilemap_pipeline_key: key, bind_group_data: material.key.clone(), - } + }, ); let z = if chunk.y_sort { - transform.translation.z + - (1.0 - - transform.translation.y / - ((chunk.map_size.y as f32) * chunk.tile_size.y)) + transform.translation.z + + (1.0 + - transform.translation.y + / ((chunk.map_size.y as f32) * chunk.tile_size.y)) } else { transform.translation.z }; @@ -543,9 +539,9 @@ pub fn bind_material_tilemap_meshes( #[cfg(not(feature = "atlas"))] (mut texture_array_cache, render_queue): ( ResMut, Res, - ) -) - where M::Data: PartialEq + Eq + Hash + Clone + ), +) where + M::Data: PartialEq + Eq + Hash + Clone, { #[cfg(not(feature = "atlas"))] texture_array_cache.queue(&render_device, &render_queue, &gpu_images); @@ -554,12 +550,10 @@ pub fn bind_material_tilemap_meshes( return; } - if - let (Some(view_binding), Some(globals)) = ( - view_uniforms.uniforms.binding(), - globals_buffer.buffer.binding(), - ) - { + if let (Some(view_binding), Some(globals)) = ( + view_uniforms.uniforms.binding(), + globals_buffer.buffer.binding(), + ) { for (entity, visible_entities) in views.iter_mut() { let view_bind_group = render_device.create_bind_group( Some("tilemap_view_bind_group"), @@ -573,7 +567,7 @@ pub fn bind_material_tilemap_meshes( binding: 1, resource: globals.clone(), }, - ] + ], ); commands.entity(entity).insert(TilemapViewBindGroup { @@ -581,11 +575,10 @@ pub fn bind_material_tilemap_meshes( }); for (chunk_id, tilemap_id) in standard_tilemap_meshes.iter() { - if - !visible_entities - .get::() - .iter() - .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) + if !visible_entities + .get::() + .iter() + .any(|(entity, _main_entity)| entity.index() == tilemap_id.0.index()) { continue; } @@ -597,11 +590,12 @@ pub fn bind_material_tilemap_meshes( continue; } - if - let Some(chunk) = chunk_storage.get( - &UVec4::new(chunk_id.0.x, chunk_id.0.y, chunk_id.0.z, tilemap_id.0.index()) - ) - { + if let Some(chunk) = chunk_storage.get(&UVec4::new( + chunk_id.0.x, + chunk_id.0.y, + chunk_id.0.z, + tilemap_id.0.index(), + )) { #[cfg(not(feature = "atlas"))] if !texture_array_cache.contains(&chunk.texture) { continue; @@ -629,16 +623,16 @@ pub fn bind_material_tilemap_meshes( binding: 1, resource: BindingResource::Sampler(&gpu_image.sampler), }, - ] + ], ) }; if modified_image_ids.is_texture_modified(&chunk.texture) { - image_bind_groups.values.insert( - chunk.texture.clone_weak(), - create_bind_group() - ); + image_bind_groups + .values + .insert(chunk.texture.clone_weak(), create_bind_group()); } else { - image_bind_groups.values + image_bind_groups + .values .entry(chunk.texture.clone_weak()) .or_insert_with(create_bind_group); } diff --git a/src/render/mod.rs b/src/render/mod.rs index 914a9e82..c5762795 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,20 +1,18 @@ use std::marker::PhantomData; use bevy::{ - asset::{ load_internal_asset, weak_handle }, + asset::{load_internal_asset, weak_handle}, core_pipeline::core_2d::Transparent2d, image::ImageSamplerDescriptor, platform::collections::HashSet, prelude::*, render::{ - Render, - RenderApp, - RenderSet, - extract_component::{ ExtractComponent, ExtractComponentPlugin }, - extract_resource::{ ExtractResource, extract_resource }, + Render, RenderApp, RenderSet, + extract_component::{ExtractComponent, ExtractComponentPlugin}, + extract_resource::{ExtractResource, extract_resource}, mesh::MeshVertexAttribute, render_phase::AddRenderCommand, - render_resource::{ FilterMode, SpecializedRenderPipelines, VertexFormat }, + render_resource::{FilterMode, SpecializedRenderPipelines, VertexFormat}, sync_world::RenderEntity, }, }; @@ -25,19 +23,22 @@ use bevy::render::renderer::RenderDevice; use bevy::render::texture::GpuImage; use extract::remove_changed; -use crate::{ TilemapFirstSet, tiles::{ TilePos, TileStorage } }; +use crate::{ + TilemapFirstSet, + tiles::{TilePos, TileStorage}, +}; use crate::{ prelude::TilemapTexture, render::{ - material::{ MaterialTilemapPlugin, StandardTilemapMaterial }, - prepare::{ MeshUniformResource, TilemapUniformResource }, + material::{MaterialTilemapPlugin, StandardTilemapMaterial}, + prepare::{MeshUniformResource, TilemapUniformResource}, }, }; use self::{ chunk::RenderChunk2dStorage, draw::DrawTilemap, - pipeline::{ TILEMAP_SHADER_FRAGMENT, TILEMAP_SHADER_VERTEX, TilemapPipeline }, + pipeline::{TILEMAP_SHADER_FRAGMENT, TILEMAP_SHADER_VERTEX, TilemapPipeline}, queue::ImageBindGroups, }; @@ -103,9 +104,8 @@ pub const ROW_HEX: Handle = weak_handle!("04a9c819-45e0-42d3-9cea-8b9e54 pub const ROW_ODD_HEX: Handle = weak_handle!("9962f145-0937-44f4-98f5-0cd5deadd643"); pub const STAGGERED_ISO: Handle = weak_handle!("da349823-a307-44a5-ab78-6276c7cb582a"); pub const SQUARE: Handle = weak_handle!("6db56afb-a562-4e3c-b459-486a6d5c12ae"); -pub const TILEMAP_VERTEX_OUTPUT: Handle = weak_handle!( - "49b568da-6c5a-4936-a3c8-d5dd6b894f92" -); +pub const TILEMAP_VERTEX_OUTPUT: Handle = + weak_handle!("49b568da-6c5a-4936-a3c8-d5dd6b894f92"); impl Plugin for TilemapRenderingPlugin { fn build(&self, app: &mut App) { @@ -126,23 +126,18 @@ impl Plugin for TilemapRenderingPlugin { .resource_mut::>() .insert( Handle::::default().id(), - StandardTilemapMaterial::default() + StandardTilemapMaterial::default(), ); - app.init_resource::().add_systems( - Update, - collect_modified_image_asset_events - ); + app.init_resource::() + .add_systems(Update, collect_modified_image_asset_events); } fn finish(&self, app: &mut App) { - let sampler = app - .get_added_plugins::() - .first() - .map_or_else( - || ImagePlugin::default_nearest().default_sampler, - |plugin| plugin.default_sampler.clone() - ); + let sampler = app.get_added_plugins::().first().map_or_else( + || ImagePlugin::default_nearest().default_sampler, + |plugin| plugin.default_sampler.clone(), + ); load_internal_asset!( app, @@ -151,27 +146,62 @@ impl Plugin for TilemapRenderingPlugin { Shader::from_wgsl ); - load_internal_asset!(app, COLUMN_HEX, "shaders/column_hex.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + COLUMN_HEX, + "shaders/column_hex.wgsl", + Shader::from_wgsl + ); - load_internal_asset!(app, COLUMN_ODD_HEX, "shaders/column_odd_hex.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + COLUMN_ODD_HEX, + "shaders/column_odd_hex.wgsl", + Shader::from_wgsl + ); load_internal_asset!(app, COMMON, "shaders/common.wgsl", Shader::from_wgsl); - load_internal_asset!(app, DIAMOND_ISO, "shaders/diamond_iso.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + DIAMOND_ISO, + "shaders/diamond_iso.wgsl", + Shader::from_wgsl + ); - load_internal_asset!(app, ROW_EVEN_HEX, "shaders/row_even_hex.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + ROW_EVEN_HEX, + "shaders/row_even_hex.wgsl", + Shader::from_wgsl + ); load_internal_asset!(app, ROW_HEX, "shaders/row_hex.wgsl", Shader::from_wgsl); - load_internal_asset!(app, ROW_ODD_HEX, "shaders/row_odd_hex.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + ROW_ODD_HEX, + "shaders/row_odd_hex.wgsl", + Shader::from_wgsl + ); load_internal_asset!(app, ROW_HEX, "shaders/row_hex.wgsl", Shader::from_wgsl); - load_internal_asset!(app, MESH_OUTPUT, "shaders/mesh_output.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + MESH_OUTPUT, + "shaders/mesh_output.wgsl", + Shader::from_wgsl + ); load_internal_asset!(app, SQUARE, "shaders/square.wgsl", Shader::from_wgsl); - load_internal_asset!(app, STAGGERED_ISO, "shaders/staggered_iso.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + STAGGERED_ISO, + "shaders/staggered_iso.wgsl", + Shader::from_wgsl + ); load_internal_asset!( app, @@ -212,16 +242,19 @@ impl Plugin for TilemapRenderingPlugin { render_app .insert_resource(DefaultSampler(sampler)) .insert_resource(RenderChunk2dStorage::default()) - .add_systems(ExtractSchedule, (extract::extract, extract_resource::)) + .add_systems( + ExtractSchedule, + (extract::extract, extract_resource::), + ) .add_systems( Render, (prepare::prepare_removal, prepare::prepare) .chain() - .in_set(RenderSet::PrepareAssets) + .in_set(RenderSet::PrepareAssets), ) .add_systems( Render, - queue::queue_transform_bind_group.in_set(RenderSet::PrepareBindGroups) + queue::queue_transform_bind_group.in_set(RenderSet::PrepareBindGroups), ) .add_systems(Render, remove_changed.in_set(RenderSet::Cleanup)) .init_resource::() @@ -236,7 +269,7 @@ impl Plugin for TilemapRenderingPlugin { pub fn set_texture_to_copy_src( mut images: ResMut>, - texture_query: Query<&TilemapTexture> + texture_query: Query<&TilemapTexture>, ) { // quick and dirty, run this for all textures anytime a texture component is created. for texture in texture_query.iter() { @@ -258,21 +291,12 @@ impl DynamicUniformIndex { } } -pub const ATTRIBUTE_POSITION: MeshVertexAttribute = MeshVertexAttribute::new( - "Position", - 229221259, - VertexFormat::Float32x4 -); -pub const ATTRIBUTE_TEXTURE: MeshVertexAttribute = MeshVertexAttribute::new( - "Texture", - 222922753, - VertexFormat::Float32x4 -); -pub const ATTRIBUTE_COLOR: MeshVertexAttribute = MeshVertexAttribute::new( - "Color", - 231497124, - VertexFormat::Float32x4 -); +pub const ATTRIBUTE_POSITION: MeshVertexAttribute = + MeshVertexAttribute::new("Position", 229221259, VertexFormat::Float32x4); +pub const ATTRIBUTE_TEXTURE: MeshVertexAttribute = + MeshVertexAttribute::new("Texture", 222922753, VertexFormat::Float32x4); +pub const ATTRIBUTE_COLOR: MeshVertexAttribute = + MeshVertexAttribute::new("Color", 231497124, VertexFormat::Float32x4); #[derive(Component, ExtractComponent, Clone)] pub struct RemovedTileEntity(pub RenderEntity); @@ -283,7 +307,7 @@ pub struct RemovedMapEntity(pub RenderEntity); fn on_remove_tile( trigger: Trigger, mut commands: Commands, - query: Query<&RenderEntity> + query: Query<&RenderEntity>, ) { if let Ok(render_entity) = query.get(trigger.target()) { commands.spawn(RemovedTileEntity(*render_entity)); @@ -293,7 +317,7 @@ fn on_remove_tile( fn on_remove_tilemap( trigger: Trigger, mut commands: Commands, - query: Query<&RenderEntity> + query: Query<&RenderEntity>, ) { if let Ok(render_entity) = query.get(trigger.target()) { commands.spawn(RemovedMapEntity(*render_entity)); @@ -303,7 +327,7 @@ fn on_remove_tilemap( fn clear_removed( mut commands: Commands, removed_query: Query>, - removed_map_query: Query> + removed_map_query: Query>, ) { for entity in removed_query.iter() { commands.entity(entity).despawn(); @@ -319,7 +343,7 @@ fn prepare_textures( render_device: Res, mut texture_array_cache: ResMut, extracted_tilemap_textures: Query<&ExtractedTilemapTexture>, - render_images: Res> + render_images: Res>, ) { for extracted_texture in extracted_tilemap_textures.iter() { texture_array_cache.add_extracted_texture(extracted_texture); @@ -347,7 +371,7 @@ impl ModifiedImageIds { /// them up into a convenient resource which can be extracted for rendering. pub fn collect_modified_image_asset_events( mut asset_events: EventReader>, - mut modified_image_ids: ResMut + mut modified_image_ids: ResMut, ) { modified_image_ids.0.clear(); diff --git a/src/render/pipeline.rs b/src/render/pipeline.rs index 633cccb2..138f7704 100644 --- a/src/render/pipeline.rs +++ b/src/render/pipeline.rs @@ -2,60 +2,32 @@ use bevy::{ asset::weak_handle, core_pipeline::core_2d::CORE_2D_DEPTH_FORMAT, image::BevyDefault, - prelude::{ Component, FromWorld, Handle, Resource, Shader, World }, + prelude::{Component, FromWorld, Handle, Resource, Shader, World}, render::{ globals::GlobalsUniform, render_resource::{ - BindGroupLayout, - BindGroupLayoutEntry, - BindingType, - BlendComponent, - BlendFactor, - BlendOperation, - BlendState, - BufferBindingType, - ColorTargetState, - ColorWrites, - CompareFunction, - DepthBiasState, - DepthStencilState, - Face, - FragmentState, - FrontFace, - MultisampleState, - PolygonMode, - PrimitiveState, - PrimitiveTopology, - RenderPipelineDescriptor, - SamplerBindingType, - ShaderStages, - ShaderType, - SpecializedRenderPipeline, - StencilFaceState, - StencilState, - TextureFormat, - TextureSampleType, - TextureViewDimension, - VertexBufferLayout, - VertexFormat, - VertexState, + BindGroupLayout, BindGroupLayoutEntry, BindingType, BlendComponent, BlendFactor, + BlendOperation, BlendState, BufferBindingType, ColorTargetState, ColorWrites, + CompareFunction, DepthBiasState, DepthStencilState, Face, FragmentState, FrontFace, + MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, + RenderPipelineDescriptor, SamplerBindingType, ShaderStages, ShaderType, + SpecializedRenderPipeline, StencilFaceState, StencilState, TextureFormat, + TextureSampleType, TextureViewDimension, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::RenderDevice, - view::{ ViewTarget, ViewUniform }, + view::{ViewTarget, ViewUniform}, }, }; -use crate::map::{ HexCoordSystem, IsoCoordSystem, TilemapType }; +use crate::map::{HexCoordSystem, IsoCoordSystem, TilemapType}; -use super::{ chunk::TilemapUniformData, prepare::MeshUniform }; +use super::{chunk::TilemapUniformData, prepare::MeshUniform}; -pub const TILEMAP_SHADER_VERTEX: Handle = weak_handle!( - "915ef471-58b4-4431-acae-f38b41969a9e" -); -pub const TILEMAP_SHADER_FRAGMENT: Handle = weak_handle!( - "bf34308e-69df-4da8-b0ff-816042c716c8" -); +pub const TILEMAP_SHADER_VERTEX: Handle = + weak_handle!("915ef471-58b4-4431-acae-f38b41969a9e"); +pub const TILEMAP_SHADER_FRAGMENT: Handle = + weak_handle!("bf34308e-69df-4da8-b0ff-816042c716c8"); #[derive(Clone, Resource)] pub struct TilemapPipeline { @@ -92,7 +64,7 @@ impl FromWorld for TilemapPipeline { }, count: None, }, - ] + ], ); let mesh_layout = render_device.create_bind_group_layout( @@ -120,7 +92,7 @@ impl FromWorld for TilemapPipeline { }, count: None, }, - ] + ], ); #[cfg(not(feature = "atlas"))] @@ -143,7 +115,7 @@ impl FromWorld for TilemapPipeline { ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, - ] + ], ); #[cfg(feature = "atlas")] @@ -166,7 +138,7 @@ impl FromWorld for TilemapPipeline { ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, - ] + ], ); TilemapPipeline { @@ -194,20 +166,18 @@ impl SpecializedRenderPipeline for TilemapPipeline { let mesh_string = match key.map_type { TilemapType::Square => "SQUARE", - TilemapType::Isometric(coord_system) => - match coord_system { - IsoCoordSystem::Diamond => "ISO_DIAMOND", - IsoCoordSystem::Staggered => "ISO_STAGGERED", - } - TilemapType::Hexagon(coord_system) => - match coord_system { - HexCoordSystem::Column => "COLUMN_HEX", - HexCoordSystem::ColumnEven => "COLUMN_EVEN_HEX", - HexCoordSystem::ColumnOdd => "COLUMN_ODD_HEX", - HexCoordSystem::Row => "ROW_HEX", - HexCoordSystem::RowEven => "ROW_EVEN_HEX", - HexCoordSystem::RowOdd => "ROW_ODD_HEX", - } + TilemapType::Isometric(coord_system) => match coord_system { + IsoCoordSystem::Diamond => "ISO_DIAMOND", + IsoCoordSystem::Staggered => "ISO_STAGGERED", + }, + TilemapType::Hexagon(coord_system) => match coord_system { + HexCoordSystem::Column => "COLUMN_HEX", + HexCoordSystem::ColumnEven => "COLUMN_EVEN_HEX", + HexCoordSystem::ColumnOdd => "COLUMN_ODD_HEX", + HexCoordSystem::Row => "ROW_HEX", + HexCoordSystem::RowEven => "ROW_EVEN_HEX", + HexCoordSystem::RowOdd => "ROW_ODD_HEX", + }, }; shader_defs.push(mesh_string.into()); @@ -217,13 +187,11 @@ impl SpecializedRenderPipeline for TilemapPipeline { // Uv VertexFormat::Float32x4, // Color - VertexFormat::Float32x4 + VertexFormat::Float32x4, ]; - let vertex_layout = VertexBufferLayout::from_vertex_formats( - VertexStepMode::Vertex, - formats - ); + let vertex_layout = + VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); RenderPipelineDescriptor { vertex: VertexState { @@ -236,33 +204,31 @@ impl SpecializedRenderPipeline for TilemapPipeline { shader: TILEMAP_SHADER_FRAGMENT, shader_defs, entry_point: "fragment".into(), - targets: vec![ - Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() + targets: vec![Some(ColorTargetState { + format: if key.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }, + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, }, - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - }, - }), - write_mask: ColorWrites::ALL, - }) - ], + }), + write_mask: ColorWrites::ALL, + })], }), layout: vec![ self.view_layout.clone(), self.mesh_layout.clone(), - self.material_layout.clone() + self.material_layout.clone(), ], primitive: PrimitiveState { conservative: false, diff --git a/src/render/prepare.rs b/src/render/prepare.rs index a85f1774..a32d009a 100644 --- a/src/render/prepare.rs +++ b/src/render/prepare.rs @@ -2,37 +2,32 @@ use std::marker::PhantomData; use crate::anchor::TilemapAnchor; use crate::map::{ - TilemapId, - TilemapSize, - TilemapSpacing, - TilemapTexture, - TilemapTextureSize, - TilemapTileSize, + TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize, TilemapType, }; use crate::prelude::TilemapRenderSettings; use crate::render::extract::ExtractedFrustum; -use crate::{ FrustumCulling, prelude::TilemapGridSize, render::RenderChunkSize }; +use crate::{FrustumCulling, prelude::TilemapGridSize, render::RenderChunkSize}; use bevy::log::trace; -use bevy::prelude::{ InheritedVisibility, Resource, Transform, With }; +use bevy::prelude::{InheritedVisibility, Resource, Transform, With}; use bevy::render::mesh::MeshVertexBufferLayouts; use bevy::render::sync_world::TemporaryRenderEntity; use bevy::{ - math::{ Mat4, UVec4 }, - prelude::{ Commands, Component, Entity, GlobalTransform, Query, Res, ResMut, Vec2 }, + math::{Mat4, UVec4}, + prelude::{Commands, Component, Entity, GlobalTransform, Query, Res, ResMut, Vec2}, render::{ - render_resource::{ DynamicUniformBuffer, ShaderType }, - renderer::{ RenderDevice, RenderQueue }, + render_resource::{DynamicUniformBuffer, ShaderType}, + renderer::{RenderDevice, RenderQueue}, }, }; use super::extract::ChangedInMainWorld; use super::{ DynamicUniformIndex, - chunk::{ ChunkId, PackedTileData, RenderChunk2dStorage, TilemapUniformData }, - extract::{ ExtractedTile, ExtractedTilemapTexture }, + chunk::{ChunkId, PackedTileData, RenderChunk2dStorage, TilemapUniformData}, + extract::{ExtractedTile, ExtractedTilemapTexture}, }; -use super::{ RemovedMapEntity, RemovedTileEntity }; +use super::{RemovedMapEntity, RemovedTileEntity}; #[derive(Resource, Default)] pub struct MeshUniformResource(pub DynamicUniformBuffer); @@ -68,13 +63,13 @@ pub(crate) fn prepare( &TilemapRenderSettings, &TilemapAnchor, ), - With + With, >, extracted_tilemap_textures: Query<&ExtractedTilemapTexture, With>, extracted_frustum_query: Query<&ExtractedFrustum>, render_device: Res, render_queue: Res, - mut mesh_vertex_buffer_layouts: ResMut + mut mesh_vertex_buffer_layouts: ResMut, ) { for tile in extracted_tiles.iter() { // First if the tile position has changed remove the tile from the old location. @@ -104,7 +99,7 @@ pub(crate) fn prepare( chunk_index.x, chunk_index.y, transform.translation().z as u32, - tile.tilemap_id.0.index() + tile.tilemap_id.0.index(), ); let in_chunk_tile_index = chunk_size.map_tile_to_chunk_tile(&tile.position, &chunk_index); @@ -125,7 +120,7 @@ pub(crate) fn prepare( visibility, frustum_culling, chunk_size, - tilemap_render_settings.y_sort + tilemap_render_settings.y_sort, ); chunk.set( &in_chunk_tile_index.into(), @@ -136,7 +131,7 @@ pub(crate) fn prepare( .extend(tile.tile.position.z) .extend(tile.tile.position.w), ..tile.tile - }) + }), ); } @@ -155,7 +150,8 @@ pub(crate) fn prepare( frustum_culling, _, anchor, - ) in extracted_tilemaps.iter() { + ) in extracted_tilemaps.iter() + { let chunks = chunk_storage.get_chunk_storage(&UVec4::new(0, 0, 0, entity.index())); for chunk in chunks.values_mut() { chunk.texture = texture.clone(); @@ -183,9 +179,8 @@ pub(crate) fn prepare( for tilemap in extracted_tilemap_textures.iter() { let texture_size: Vec2 = tilemap.texture_size.into(); - let chunks = chunk_storage.get_chunk_storage( - &UVec4::new(0, 0, 0, tilemap.tilemap_id.0.index()) - ); + let chunks = + chunk_storage.get_chunk_storage(&UVec4::new(0, 0, 0, tilemap.tilemap_id.0.index())); for chunk in chunks.values_mut() { chunk.texture_size = texture_size; } @@ -200,9 +195,10 @@ pub(crate) fn prepare( continue; } - if - chunk.frustum_culling && - !extracted_frustum_query.iter().any(|frustum| chunk.intersects_frustum(frustum)) + if chunk.frustum_culling + && !extracted_frustum_query + .iter() + .any(|frustum| chunk.intersects_frustum(frustum)) { trace!("Frustum culled chunk: {:?}", chunk.get_index()); continue; @@ -222,7 +218,7 @@ pub(crate) fn prepare( index: mesh_uniforms.0.push( &(MeshUniform { transform: chunk.get_transform_matrix(), - }) + }), ), marker: PhantomData, }, @@ -235,13 +231,15 @@ pub(crate) fn prepare( } mesh_uniforms.0.write_buffer(&render_device, &render_queue); - tilemap_uniforms.0.write_buffer(&render_device, &render_queue); + tilemap_uniforms + .0 + .write_buffer(&render_device, &render_queue); } pub fn prepare_removal( mut chunk_storage: ResMut, removed_tiles: Query<&RemovedTileEntity>, - removed_maps: Query<&RemovedMapEntity> + removed_maps: Query<&RemovedMapEntity>, ) { for removed_tile in removed_tiles.iter() { chunk_storage.remove_tile_with_entity(removed_tile.0.id()); diff --git a/src/render/queue.rs b/src/render/queue.rs index 5168cf92..346e9fcb 100644 --- a/src/render/queue.rs +++ b/src/render/queue.rs @@ -1,10 +1,16 @@ use bevy::{ platform::collections::HashMap, prelude::*, - render::{ render_resource::{ BindGroup, BindGroupEntry }, renderer::RenderDevice }, + render::{ + render_resource::{BindGroup, BindGroupEntry}, + renderer::RenderDevice, + }, }; -use super::{ pipeline::TilemapPipeline, prepare::{ MeshUniformResource, TilemapUniformResource } }; +use super::{ + pipeline::TilemapPipeline, + prepare::{MeshUniformResource, TilemapUniformResource}, +}; use crate::TilemapTexture; #[derive(Resource)] @@ -17,13 +23,10 @@ pub fn queue_transform_bind_group( tilemap_pipeline: Res, render_device: Res, transform_uniforms: Res, - tilemap_uniforms: Res + tilemap_uniforms: Res, ) { - if - let (Some(binding1), Some(binding2)) = ( - transform_uniforms.0.binding(), - tilemap_uniforms.0.binding(), - ) + if let (Some(binding1), Some(binding2)) = + (transform_uniforms.0.binding(), tilemap_uniforms.0.binding()) { commands.insert_resource(TransformBindGroup { value: render_device.create_bind_group( @@ -38,7 +41,7 @@ pub fn queue_transform_bind_group( binding: 1, resource: binding2, }, - ] + ], ), }); } diff --git a/src/render/texture_array_cache.rs b/src/render/texture_array_cache.rs index 74034d51..c10d610f 100644 --- a/src/render/texture_array_cache.rs +++ b/src/render/texture_array_cache.rs @@ -1,29 +1,19 @@ use crate::render::extract::ExtractedTilemapTexture; -use crate::{ TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize }; +use crate::{TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize}; use bevy::asset::Assets; -use bevy::prelude::{ ResMut, Resource }; +use bevy::prelude::{ResMut, Resource}; use bevy::render::render_resource::TexelCopyTextureInfo; use bevy::{ - platform::collections::{ HashMap, HashSet }, - prelude::{ Image, Res }, + platform::collections::{HashMap, HashSet}, + prelude::{Image, Res}, render::{ render_asset::RenderAssets, render_resource::{ - AddressMode, - CommandEncoderDescriptor, - Extent3d, - FilterMode, - Origin3d, - SamplerDescriptor, - TextureAspect, - TextureDescriptor, - TextureDimension, - TextureFormat, - TextureUsages, - TextureViewDescriptor, - TextureViewDimension, + AddressMode, CommandEncoderDescriptor, Extent3d, FilterMode, Origin3d, + SamplerDescriptor, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, TextureViewDescriptor, TextureViewDimension, }, - renderer::{ RenderDevice, RenderQueue }, + renderer::{RenderDevice, RenderQueue}, texture::GpuImage, }, }; @@ -35,7 +25,14 @@ pub struct TextureArrayCache { textures: HashMap, meta_data: HashMap< TilemapTexture, - (u32, TilemapTileSize, TilemapTextureSize, TilemapSpacing, FilterMode, TextureFormat) + ( + u32, + TilemapTileSize, + TilemapTextureSize, + TilemapSpacing, + FilterMode, + TextureFormat, + ), >, prepare_queue: HashSet, queue_queue: HashSet, @@ -49,15 +46,19 @@ impl TextureArrayCache { /// checks, as this is assumed to have been done during [`ExtractedTilemapTexture::new`]. pub(crate) fn add_extracted_texture(&mut self, extracted_texture: &ExtractedTilemapTexture) { if !self.meta_data.contains_key(&extracted_texture.texture) { - self.meta_data.insert(extracted_texture.texture.clone_weak(), ( - extracted_texture.tile_count, - extracted_texture.tile_size, - extracted_texture.texture_size, - extracted_texture.tile_spacing, - extracted_texture.filtering, - extracted_texture.format, - )); - self.prepare_queue.insert(extracted_texture.texture.clone_weak()); + self.meta_data.insert( + extracted_texture.texture.clone_weak(), + ( + extracted_texture.tile_count, + extracted_texture.tile_size, + extracted_texture.texture_size, + extracted_texture.tile_spacing, + extracted_texture.filtering, + extracted_texture.format, + ), + ); + self.prepare_queue + .insert(extracted_texture.texture.clone_weak()); } } @@ -69,16 +70,14 @@ impl TextureArrayCache { tile_spacing: TilemapSpacing, filtering: FilterMode, format: TextureFormat, - image_assets: &Res> + image_assets: &Res>, ) { let (tile_count, texture_size) = match &texture { TilemapTexture::Single(handle) => { - let image = image_assets - .get(handle) - .expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!" - ); + let image = image_assets.get(handle).expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!", + ); let texture_size: TilemapTextureSize = image.size_f32().into(); let tile_count_x = (texture_size.x / (tile_size.x + tile_spacing.x)).floor(); let tile_count_y = (texture_size.y / (tile_size.y + tile_spacing.y)).floor(); @@ -86,12 +85,10 @@ impl TextureArrayCache { } TilemapTexture::Vector(handles) => { for handle in handles { - let image = image_assets - .get(handle) - .expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!" - ); + let image = image_assets.get(handle).expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!", + ); let this_tile_size: TilemapTileSize = image.size_f32().into(); if this_tile_size != tile_size { panic!( @@ -103,26 +100,30 @@ impl TextureArrayCache { (handles.len() as u32, tile_size.into()) } TilemapTexture::TextureContainer(handle) => { - let image = image_assets - .get(handle) - .expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!" - ); + let image = image_assets.get(handle).expect( + "Expected image to have finished loading if \ + it is being extracted as a texture!", + ); let tile_size: TilemapTileSize = image.size_f32().into(); - (image.texture_descriptor.array_layer_count(), tile_size.into()) + ( + image.texture_descriptor.array_layer_count(), + tile_size.into(), + ) } }; if !self.meta_data.contains_key(&texture) { - self.meta_data.insert(texture.clone_weak(), ( - tile_count, - tile_size, - texture_size, - tile_spacing, - filtering, - format, - )); + self.meta_data.insert( + texture.clone_weak(), + ( + tile_count, + tile_size, + texture_size, + tile_spacing, + filtering, + format, + ), + ); self.prepare_queue.insert(texture.clone_weak()); } } @@ -139,7 +140,7 @@ impl TextureArrayCache { pub fn prepare( &mut self, render_device: &RenderDevice, - render_images: &Res> + render_images: &Res>, ) { let prepare_queue = self.prepare_queue.drain().collect::>(); for texture in prepare_queue.iter() { @@ -153,12 +154,15 @@ impl TextureArrayCache { match texture { TilemapTexture::Single(_) | TilemapTexture::Vector(_) => { - let (count, tile_size, _, _, filter, format) = self.meta_data - .get(texture) - .unwrap(); + let (count, tile_size, _, _, filter, format) = + self.meta_data.get(texture).unwrap(); // Fixes issue where wgpu's gles texture type inference fails. - let count = if *count == 1 || count % 6 == 0 { count + 1 } else { *count }; + let count = if *count == 1 || count % 6 == 0 { + count + 1 + } else { + *count + }; let gpu_texture = render_device.create_texture( &(TextureDescriptor { @@ -174,7 +178,7 @@ impl TextureArrayCache { format: *format, usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, view_formats: &[], - }) + }), ); let sampler = render_device.create_sampler( @@ -191,7 +195,7 @@ impl TextureArrayCache { compare: None, anisotropy_clamp: 1, border_color: None, - }) + }), ); let texture_view = gpu_texture.create_view( @@ -205,7 +209,7 @@ impl TextureArrayCache { base_array_layer: 0, array_layer_count: Some(count), usage: Some(gpu_texture.usage()), - }) + }), ); let mip_level_count = gpu_texture.mip_level_count(); @@ -228,7 +232,8 @@ impl TextureArrayCache { } TilemapTexture::TextureContainer(handle) => { if let Some(gpu_image) = render_images.get(handle) { - self.textures.insert(texture.clone_weak(), gpu_image.clone()); + self.textures + .insert(texture.clone_weak(), gpu_image.clone()); } else { self.prepare_queue.insert(texture.clone_weak()); } @@ -241,7 +246,7 @@ impl TextureArrayCache { &mut self, render_device: &RenderDevice, render_queue: &RenderQueue, - render_images: &Res> + render_images: &Res>, ) { let queue_queue = self.queue_queue.drain().collect::>(); @@ -255,16 +260,15 @@ impl TextureArrayCache { continue; }; - let (count, tile_size, texture_size, spacing, _, _) = self.meta_data - .get(texture) - .unwrap(); + let (count, tile_size, texture_size, spacing, _, _) = + self.meta_data.get(texture).unwrap(); let array_gpu_image = self.textures.get(texture).unwrap(); let count = *count; let mut command_encoder = render_device.create_command_encoder( &(CommandEncoderDescriptor { label: Some("create_texture_array_from_atlas"), - }) + }), ); for i in 0..count { @@ -295,7 +299,7 @@ impl TextureArrayCache { width: tile_size.x as u32, height: tile_size.y as u32, depth_or_array_layers: 1, - } + }, ); } @@ -320,7 +324,7 @@ impl TextureArrayCache { let mut command_encoder = render_device.create_command_encoder( &(CommandEncoderDescriptor { label: Some("create_texture_array_from_handles_vec"), - }) + }), ); for i in 0..count { @@ -341,7 +345,7 @@ impl TextureArrayCache { width: tile_size.x as u32, height: tile_size.y as u32, depth_or_array_layers: 1, - } + }, ); } @@ -361,13 +365,17 @@ impl TextureArrayCache { /// responsive to hot-reloading, for example. pub fn remove_modified_textures( modified_image_ids: Res, - mut texture_cache: ResMut + mut texture_cache: ResMut, ) { - let texture_is_unmodified = |texture: &TilemapTexture| - !modified_image_ids.is_texture_modified(texture); + let texture_is_unmodified = + |texture: &TilemapTexture| !modified_image_ids.is_texture_modified(texture); - texture_cache.textures.retain(|texture, _| texture_is_unmodified(texture)); - texture_cache.meta_data.retain(|texture, _| texture_is_unmodified(texture)); + texture_cache + .textures + .retain(|texture, _| texture_is_unmodified(texture)); + texture_cache + .meta_data + .retain(|texture, _| texture_is_unmodified(texture)); texture_cache.prepare_queue.retain(texture_is_unmodified); texture_cache.queue_queue.retain(texture_is_unmodified); texture_cache.bad_flag_queue.retain(texture_is_unmodified); diff --git a/src/tiles/mod.rs b/src/tiles/mod.rs index b70fa791..35ce5359 100644 --- a/src/tiles/mod.rs +++ b/src/tiles/mod.rs @@ -1,8 +1,8 @@ mod storage; use bevy::{ - math::{ UVec2, Vec2 }, - prelude::{ Bundle, Color, Component, Reflect, ReflectComponent }, + math::{UVec2, Vec2}, + prelude::{Bundle, Color, Component, Reflect, ReflectComponent}, render::sync_world::SyncToRenderWorld, }; pub use storage::*; @@ -145,7 +145,7 @@ pub struct AnimatedTile { #[cfg(test)] mod tests { use super::*; - use bevy::math::{ UVec2, Vec2 }; + use bevy::math::{UVec2, Vec2}; #[test] fn tile_pos_to_index() { diff --git a/src/tiles/storage.rs b/src/tiles/storage.rs index 3a3dc221..9369f5bd 100644 --- a/src/tiles/storage.rs +++ b/src/tiles/storage.rs @@ -200,9 +200,7 @@ mod tests { e(entity.index() + 1) } - fn set_mapped(&mut self, _source: Entity, _target: Entity) { - - } + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} } #[test] diff --git a/tests/anchor.rs b/tests/anchor.rs index e39f4017..81d310c5 100644 --- a/tests/anchor.rs +++ b/tests/anchor.rs @@ -1,5 +1,5 @@ use bevy::prelude::*; -use bevy_ecs_tilemap::{ anchor::*, map::* }; +use bevy_ecs_tilemap::{anchor::*, map::*}; use proptest::prelude::*; proptest! { @@ -8,7 +8,7 @@ proptest! { map_x in 1u32..20, map_y in 1u32..20, grid_x in 0.5f32..4.0, - grid_y in 0.5f32..4.0, + grid_y in 0.5f32..4.0, tile_x in 0.5f32..4.0, tile_y in 0.5f32..4.0, ) { @@ -16,7 +16,7 @@ proptest! { let grid_size = TilemapGridSize { x: grid_x, y: grid_y }; let tile_size = TilemapTileSize { x: tile_x, y: tile_y }; let map_type = TilemapType::Square; - + // Had to do some trickery because proptest and approx weren't playing nice. // Accurate to 3 digits, change as needed. let precision = 10f32.powf(3f32); diff --git a/tests/helpers__filling.rs b/tests/helpers__filling.rs index 1e277e17..70edd7dc 100644 --- a/tests/helpers__filling.rs +++ b/tests/helpers__filling.rs @@ -1,8 +1,11 @@ -use bevy::ecs::{ system::Commands, world::{ CommandQueue, World } }; +use bevy::ecs::{ + system::Commands, + world::{CommandQueue, World}, +}; use bevy_ecs_tilemap::{ - map::{ TilemapId, TilemapSize }, + map::{TilemapId, TilemapSize}, prelude::fill_tilemap, - tiles::{ TilePos, TileStorage, TileTextureIndex }, + tiles::{TilePos, TileStorage, TileTextureIndex}, }; fn spawn_tilemap(world: &mut World) -> (TilemapId, TileStorage) { @@ -20,7 +23,13 @@ fn fill_tilemap_fills_every_cell() { let size = storage.size; - fill_tilemap(TileTextureIndex(7), size, tilemap_id, &mut commands, &mut storage); + fill_tilemap( + TileTextureIndex(7), + size, + tilemap_id, + &mut commands, + &mut storage, + ); queue.apply(&mut world); // every position should have an entity and the world should own it diff --git a/tests/helpers__hex_grid__axial.rs b/tests/helpers__hex_grid__axial.rs index 87dfebc1..48f75292 100644 --- a/tests/helpers__hex_grid__axial.rs +++ b/tests/helpers__hex_grid__axial.rs @@ -1,12 +1,16 @@ -use bevy_ecs_tilemap::{ prelude::* }; use bevy_ecs_tilemap::helpers::hex_grid::axial::AxialPos; -use bevy_ecs_tilemap::map::{ TilemapGridSize, TilemapSize }; +use bevy_ecs_tilemap::map::{TilemapGridSize, TilemapSize}; +use bevy_ecs_tilemap::prelude::*; const GRID: TilemapGridSize = TilemapGridSize { x: 32.0, y: 32.0 }; #[test] fn row_projection_round_trip() { - let samples = [AxialPos::new(0, 0), AxialPos::new(3, -2), AxialPos::new(-4, 5)]; + let samples = [ + AxialPos::new(0, 0), + AxialPos::new(3, -2), + AxialPos::new(-4, 5), + ]; for ax in samples { let world = ax.center_in_world_row(&GRID); @@ -17,7 +21,11 @@ fn row_projection_round_trip() { #[test] fn col_projection_round_trip() { - let samples = [AxialPos::new(0, 0), AxialPos::new(1, 4), AxialPos::new(-3, -2)]; + let samples = [ + AxialPos::new(0, 0), + AxialPos::new(1, 4), + AxialPos::new(-3, -2), + ]; for ax in samples { let world = ax.center_in_world_col(&GRID); @@ -43,6 +51,10 @@ fn tilepos_coord_system_helpers() { .as_tile_pos_given_coord_system_and_map_size(sys, &map_size) .expect("axial pos should be inside map"); let back = AxialPos::from_tile_pos_given_coord_system(&tp, sys); - assert_eq!(ax, back, "coord-system conversion failed for {:?} (tile={tp:?})", sys); + assert_eq!( + ax, back, + "coord-system conversion failed for {:?} (tile={tp:?})", + sys + ); } } diff --git a/tests/helpers__hex_grid__cube.rs b/tests/helpers__hex_grid__cube.rs index 3af45dc6..91d78d88 100644 --- a/tests/helpers__hex_grid__cube.rs +++ b/tests/helpers__hex_grid__cube.rs @@ -1,10 +1,13 @@ -use bevy_ecs_tilemap::helpers::hex_grid::axial::{ AxialPos }; +use bevy_ecs_tilemap::helpers::hex_grid::axial::AxialPos; use bevy_ecs_tilemap::helpers::hex_grid::cube::CubePos; #[test] fn axial_round_trip_is_lossless() { let axial = AxialPos { q: -5, r: 2 }; let cube: CubePos = axial.into(); - let back: AxialPos = AxialPos { q: cube.q, r: cube.r }; + let back: AxialPos = AxialPos { + q: cube.q, + r: cube.r, + }; assert_eq!(back, axial, "Axial → Cube → Axial should be identity"); } diff --git a/tests/helpers__hex_grid__neighbors.rs b/tests/helpers__hex_grid__neighbors.rs index 58ad73ae..d82e2671 100644 --- a/tests/helpers__hex_grid__neighbors.rs +++ b/tests/helpers__hex_grid__neighbors.rs @@ -1,7 +1,7 @@ -use bevy_ecs_tilemap::helpers::hex_grid::neighbors::{ HexNeighbors, HexDirection }; +use bevy_ecs_tilemap::helpers::hex_grid::neighbors::{HexDirection, HexNeighbors}; +use bevy_ecs_tilemap::map::HexCoordSystem; use bevy_ecs_tilemap::map::TilemapSize; use bevy_ecs_tilemap::tiles::TilePos; -use bevy_ecs_tilemap::map::HexCoordSystem; fn pos(x: u32, y: u32) -> TilePos { TilePos { x, y } @@ -14,11 +14,8 @@ fn border_tiles_clamp_neighbors_out_of_bounds() { // South-west corner let corner = pos(0, 2); - let neighbors = HexNeighbors::::get_neighboring_positions( - &corner, - &size, - &HexCoordSystem::Row - ); + let neighbors = + HexNeighbors::::get_neighboring_positions(&corner, &size, &HexCoordSystem::Row); for dir in [HexDirection::One, HexDirection::Two, HexDirection::Three] { assert!(neighbors.get(dir).is_none(), "{dir:?} should be None"); diff --git a/tests/helpers__projection.rs b/tests/helpers__projection.rs index 42c2dc82..44c125a4 100644 --- a/tests/helpers__projection.rs +++ b/tests/helpers__projection.rs @@ -1,12 +1,7 @@ use bevy_ecs_tilemap::{ anchor::TilemapAnchor, map::{ - HexCoordSystem, - IsoCoordSystem, - TilemapGridSize, - TilemapSize, - TilemapTileSize, - TilemapType, + HexCoordSystem, IsoCoordSystem, TilemapGridSize, TilemapSize, TilemapTileSize, TilemapType, }, tiles::TilePos, }; @@ -15,20 +10,16 @@ fn roundtrip( original: TilePos, map_type: TilemapType, grid_size: TilemapGridSize, - tile_size: TilemapTileSize + tile_size: TilemapTileSize, ) { let map_size = TilemapSize { x: 10, y: 10 }; let anchor = TilemapAnchor::BottomLeft; let world = original.center_in_world(&map_size, &grid_size, &tile_size, &map_type, &anchor); let recon = TilePos::from_world_pos( - &world, - &map_size, - &grid_size, - &tile_size, - &map_type, - &anchor - ).expect("round-trip should succeed"); + &world, &map_size, &grid_size, &tile_size, &map_type, &anchor, + ) + .expect("round-trip should succeed"); assert_eq!(original, recon, "round-trip failed for {map_type:?}"); } @@ -39,7 +30,7 @@ fn square_roundtrip() { TilePos { x: 4, y: 6 }, TilemapType::Square, TilemapGridSize { x: 32.0, y: 32.0 }, - TilemapTileSize { x: 32.0, y: 32.0 } + TilemapTileSize { x: 32.0, y: 32.0 }, ); } @@ -50,7 +41,7 @@ fn hex_row_even_roundtrip() { TilePos { x: 2, y: 5 }, TilemapType::Hexagon(RowEven), TilemapGridSize { x: 32.0, y: 32.0 }, - TilemapTileSize { x: 32.0, y: 32.0 } + TilemapTileSize { x: 32.0, y: 32.0 }, ); } @@ -61,6 +52,6 @@ fn iso_diamond_roundtrip() { TilePos { x: 1, y: 8 }, TilemapType::Isometric(Diamond), TilemapGridSize { x: 32.0, y: 16.0 }, - TilemapTileSize { x: 32.0, y: 16.0 } + TilemapTileSize { x: 32.0, y: 16.0 }, ); } diff --git a/tests/helpers__square_grid__diamond.rs b/tests/helpers__square_grid__diamond.rs index f02c2f7b..badd3f87 100644 --- a/tests/helpers__square_grid__diamond.rs +++ b/tests/helpers__square_grid__diamond.rs @@ -1,5 +1,5 @@ +use bevy_ecs_tilemap::helpers::square_grid::diamond::DiamondPos; use bevy_ecs_tilemap::helpers::square_grid::neighbors::SquareDirection; -use bevy_ecs_tilemap::helpers::square_grid::diamond::{ DiamondPos }; use bevy_ecs_tilemap::map::TilemapSize; use bevy_ecs_tilemap::tiles::TilePos; diff --git a/tests/helpers__square_grid__mod.rs b/tests/helpers__square_grid__mod.rs index d071a28c..772125f1 100644 --- a/tests/helpers__square_grid__mod.rs +++ b/tests/helpers__square_grid__mod.rs @@ -1,4 +1,4 @@ -use bevy_ecs_tilemap::{ map::TilemapSize, tiles::TilePos, * }; +use bevy_ecs_tilemap::{map::TilemapSize, tiles::TilePos, *}; use helpers::square_grid::neighbors::SquareDirection; #[test] @@ -22,6 +22,12 @@ fn square_offset_out_of_bounds_returns_none() { let map_size = TilemapSize { x: 4, y: 4 }; let edge = TilePos::new(0, 0); - assert!(edge.square_offset(&SquareDirection::South, &map_size).is_none()); - assert!(edge.square_offset(&SquareDirection::West, &map_size).is_none()); + assert!( + edge.square_offset(&SquareDirection::South, &map_size) + .is_none() + ); + assert!( + edge.square_offset(&SquareDirection::West, &map_size) + .is_none() + ); } diff --git a/tests/helpers__square_grid__neighbors.rs b/tests/helpers__square_grid__neighbors.rs index a5e3e5f6..1c04618c 100644 --- a/tests/helpers__square_grid__neighbors.rs +++ b/tests/helpers__square_grid__neighbors.rs @@ -1,7 +1,10 @@ use bevy_ecs_tilemap::{ - helpers::square_grid::{ neighbors::{ Neighbors, SquareDirection }, staggered::StaggeredPos }, + helpers::square_grid::{ + neighbors::{Neighbors, SquareDirection}, + staggered::StaggeredPos, + }, map::TilemapSize, - tiles::{ TilePos, TileStorage }, + tiles::{TilePos, TileStorage}, }; #[test] @@ -54,22 +57,29 @@ fn staggered_neighboring_positions_respects_offset() { let map: TilemapSize = TilemapSize { x: 3, y: 3 }; let start: TilePos = TilePos::new(1, 1); - let neighbors: Neighbors = Neighbors::get_staggered_neighboring_positions( - &start, - &map, - false - ); + let neighbors: Neighbors = + Neighbors::get_staggered_neighboring_positions(&start, &map, false); // only cardinals requested assert!(neighbors.north_east.is_none()); assert!(neighbors.south_west.is_none()); assert_eq!( neighbors.north, - Some(StaggeredPos::from(&start).offset(&SquareDirection::North).as_tile_pos(&map).unwrap()) + Some( + StaggeredPos::from(&start) + .offset(&SquareDirection::North) + .as_tile_pos(&map) + .unwrap() + ) ); assert_eq!( neighbors.east, - Some(StaggeredPos::from(&start).offset(&SquareDirection::East).as_tile_pos(&map).unwrap()) + Some( + StaggeredPos::from(&start) + .offset(&SquareDirection::East) + .as_tile_pos(&map) + .unwrap() + ) ); } diff --git a/tests/helpers__square_grid__staggered.rs b/tests/helpers__square_grid__staggered.rs index c515b006..0452fc2d 100644 --- a/tests/helpers__square_grid__staggered.rs +++ b/tests/helpers__square_grid__staggered.rs @@ -1,6 +1,6 @@ use bevy_ecs_tilemap::{ - helpers::square_grid::{ neighbors::SquareDirection::*, staggered::StaggeredPos }, - map::{ TilemapGridSize, TilemapSize }, + helpers::square_grid::{neighbors::SquareDirection::*, staggered::StaggeredPos}, + map::{TilemapGridSize, TilemapSize}, tiles::TilePos, }; @@ -20,7 +20,10 @@ fn tilepos_staggered_offset() { let map = TilemapSize { x: 3, y: 3 }; let origin = TilePos { x: 1, y: 1 }; - assert_eq!(origin.staggered_offset(&North, &map).unwrap(), TilePos { x: 1, y: 2 }); + assert_eq!( + origin.staggered_offset(&North, &map).unwrap(), + TilePos { x: 1, y: 2 } + ); // Edge should return None let edge = TilePos { x: 0, y: 0 }; diff --git a/tests/map1.rs b/tests/map1.rs index cb3b578b..ed9bb114 100644 --- a/tests/map1.rs +++ b/tests/map1.rs @@ -1,4 +1,4 @@ -use bevy::ecs::entity::{ EntityMapper, MapEntities }; +use bevy::ecs::entity::{EntityMapper, MapEntities}; use bevy::prelude::Entity; use bevy_ecs_tilemap::map::TilemapId; diff --git a/tests/map2.rs b/tests/map2.rs index e19cee13..cadcdcf8 100644 --- a/tests/map2.rs +++ b/tests/map2.rs @@ -1,19 +1,23 @@ use bevy::{ app::App, - asset::{ Assets, RenderAssetUsages }, - ecs::system::{ Res, ResMut, RunSystemOnce }, + asset::{Assets, RenderAssetUsages}, + ecs::system::{Res, ResMut, RunSystemOnce}, image::Image, - render::render_resource::{ Extent3d, TextureDimension, TextureFormat, TextureUsages }, + render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages}, }; use bevy_ecs_tilemap::map::TilemapTexture; fn make_image() -> Image { let mut image = Image::new_fill( - Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, + Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, TextureDimension::D2, &[255, 255, 255, 255], TextureFormat::Rgba8UnormSrgb, - RenderAssetUsages::default() + RenderAssetUsages::default(), ); image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING; image @@ -33,23 +37,29 @@ fn verify_ready_and_set_copy_src_work() { // 1. `verify_ready` should fail (COPY_SRC not set yet) { let tex = tex.clone(); - let _ = app.world_mut().run_system_once(move |images: Res>| { - assert!(!tex.verify_ready(&images)); - }); + let _ = app + .world_mut() + .run_system_once(move |images: Res>| { + assert!(!tex.verify_ready(&images)); + }); } // 2. add the COPY_SRC usage flag { let tex = tex.clone(); - let _ = app.world_mut().run_system_once(move |mut images: ResMut>| { - tex.set_images_to_copy_src(&mut images); - }); + let _ = app + .world_mut() + .run_system_once(move |mut images: ResMut>| { + tex.set_images_to_copy_src(&mut images); + }); } // 3. `verify_ready` should now succeed { - let _ = app.world_mut().run_system_once(move |images: Res>| { - assert!(tex.verify_ready(&images)); - }); + let _ = app + .world_mut() + .run_system_once(move |images: Res>| { + assert!(tex.verify_ready(&images)); + }); } } diff --git a/tests/tiles__mod.rs b/tests/tiles__mod.rs index 424ff229..b2e79fed 100644 --- a/tests/tiles__mod.rs +++ b/tests/tiles__mod.rs @@ -1,5 +1,5 @@ use bevy::color::Color; -use bevy_ecs_tilemap::tiles::{ TileBundle, TileFlip, TilePos }; +use bevy_ecs_tilemap::tiles::{TileBundle, TileFlip, TilePos}; #[test] fn tile_bundle_defaults_are_consistent() { diff --git a/tests/tiles__storage.rs b/tests/tiles__storage.rs index 013c7984..6929e770 100644 --- a/tests/tiles__storage.rs +++ b/tests/tiles__storage.rs @@ -1,5 +1,5 @@ -use bevy::{ ecs::world::CommandQueue, prelude::* }; -use bevy_ecs_tilemap::prelude::{ TilemapSize, TilePos, TileStorage }; +use bevy::{ecs::world::CommandQueue, prelude::*}; +use bevy_ecs_tilemap::prelude::{TilePos, TileStorage, TilemapSize}; #[test] fn drain_can_be_used_to_despawn_entities() { @@ -20,7 +20,11 @@ fn drain_can_be_used_to_despawn_entities() { // Use Commands-style despawning exactly as the docs example shows let mut queue = CommandQueue::default(); { - let mut storage = app.world_mut().entity_mut(storage_entity).take::().unwrap(); + let mut storage = app + .world_mut() + .entity_mut(storage_entity) + .take::() + .unwrap(); let mut commands = Commands::new(&mut queue, &app.world()); for entity in storage.drain() { commands.entity(entity).despawn(); From 754d343da9670c2ab45d759a41f10595970422ff Mon Sep 17 00:00:00 2001 From: Alex Bentley Date: Sat, 21 Jun 2025 13:35:20 -0400 Subject: [PATCH 3/5] Fixed clippy errors --- examples/helpers/ldtk.rs | 7 ++----- examples/helpers/tiled.rs | 4 ++-- mintty.exe.stackdump | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 mintty.exe.stackdump diff --git a/examples/helpers/ldtk.rs b/examples/helpers/ldtk.rs index bea87496..e81face8 100644 --- a/examples/helpers/ldtk.rs +++ b/examples/helpers/ldtk.rs @@ -4,7 +4,7 @@ use bevy_ecs_tilemap::{ map::{TilemapId, TilemapSize, TilemapTexture, TilemapTileSize}, tiles::{TileBundle, TilePos, TileStorage, TileTextureIndex}, }; -use std::{collections::HashMap, io::ErrorKind}; +use std::{collections::HashMap}; use thiserror::Error; use bevy::{asset::io::Reader, reflect::TypePath}; @@ -71,10 +71,7 @@ impl AssetLoader for LdtkLoader { reader.read_to_end(&mut bytes).await?; let project: ldtk_rust::Project = serde_json::from_slice(&bytes).map_err(|e| { - std::io::Error::new( - ErrorKind::Other, - format!("Could not read contents of Ldtk map: {e}"), - ) + std::io::Error::other(format!("Could not read contents of Ldtk map: {e}")) })?; let dependencies: Vec<(i64, AssetPath)> = project .defs diff --git a/examples/helpers/tiled.rs b/examples/helpers/tiled.rs index 50ee6f5d..645345da 100644 --- a/examples/helpers/tiled.rs +++ b/examples/helpers/tiled.rs @@ -12,7 +12,7 @@ // * When the 'atlas' feature is enabled tilesets using a collection of images will be skipped. // * Only finite tile layers are loaded. Infinite tile layers and object layers will be skipped. -use std::io::{Cursor, ErrorKind}; +use std::io::{Cursor}; use std::path::Path; use std::sync::Arc; @@ -119,7 +119,7 @@ impl AssetLoader for TiledLoader { BytesResourceReader::new(&bytes), ); let map = loader.load_tmx_map(load_context.path()).map_err(|e| { - std::io::Error::new(ErrorKind::Other, format!("Could not load TMX map: {e}")) + std::io::Error::other(format!("Could not load TMX map: {e}")) })?; let mut tilemap_textures = HashMap::default(); diff --git a/mintty.exe.stackdump b/mintty.exe.stackdump new file mode 100644 index 00000000..4b351647 --- /dev/null +++ b/mintty.exe.stackdump @@ -0,0 +1,23 @@ +Exception: STATUS_ACCESS_VIOLATION at rip=0010042165E +rax=0000000000000001 rbx=00000000FFFFC1B0 rcx=0000000000000000 +rdx=00000000FFFFCE00 rsi=0000000000000000 rdi=000000000000008F +r8 =000000000000005A r9 =000000000000005E r10=0000000100000000 +r11=00000001004216A8 r12=0000000000000002 r13=00000000FFFFC1B0 +r14=000000080010E650 r15=0000000000000000 +rbp=00000000FFFFC270 rsp=00000000FFFFC150 +program=C:\Users\Alex Work\AppData\Local\Programs\Git\usr\bin\mintty.exe, pid 905, thread main +cs=0033 ds=002B es=002B fs=0053 gs=002B ss=002B +Stack trace: +Frame Function Args +000FFFFC270 0010042165E (000FFFFC1E4, 00000000000, 00100426F2C, 00000000000) +000FFFFC270 00100422355 (00100427609, 6D6178656D305B1B, 7865745C73656C70, 008000A52A0) +000FFFFC270 0010041A6C4 (001004031A3, FFFFFFFF00000000, 00000000000, 001004E8600) +00000022510 0010042967A (00000000000, 00000000CE6, 001004E8600, 001004E8600) +00000022510 0010042BD70 (001004E300A, 001004E8F40, 00000000018, 000FFFFC3C0) +00000022510 0010042E238 (001004E30E0, 00000000100, 7FF886D6E684, 00000000000) +00000001000 00100404C33 (000FFFFC550, 008000434A0, 7FF886D69680, 00800000001) +000FFFFC550 00100460CB1 (00000000014, 00800042DD8, 000FFFFCB80, 000FFFFCC93) +000FFFFCCE0 0018004B0FB (00000000000, 00000000000, 00000000000, 00000000000) +000FFFFCDA0 00180048A2A (00000000000, 00000000000, 00000000000, 00000000000) +000FFFFCE50 00180048AEC (00000000000, 00000000000, 00000000000, 00000000000) +End of stack trace From d7193642942457c4b296459de33deaf983c7f752 Mon Sep 17 00:00:00 2001 From: Alex Bentley Date: Sat, 21 Jun 2025 13:41:20 -0400 Subject: [PATCH 4/5] cargo fmt... --- examples/helpers/ldtk.rs | 2 +- examples/helpers/tiled.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/helpers/ldtk.rs b/examples/helpers/ldtk.rs index e81face8..85551cfe 100644 --- a/examples/helpers/ldtk.rs +++ b/examples/helpers/ldtk.rs @@ -4,7 +4,7 @@ use bevy_ecs_tilemap::{ map::{TilemapId, TilemapSize, TilemapTexture, TilemapTileSize}, tiles::{TileBundle, TilePos, TileStorage, TileTextureIndex}, }; -use std::{collections::HashMap}; +use std::collections::HashMap; use thiserror::Error; use bevy::{asset::io::Reader, reflect::TypePath}; diff --git a/examples/helpers/tiled.rs b/examples/helpers/tiled.rs index 645345da..0763652d 100644 --- a/examples/helpers/tiled.rs +++ b/examples/helpers/tiled.rs @@ -12,7 +12,7 @@ // * When the 'atlas' feature is enabled tilesets using a collection of images will be skipped. // * Only finite tile layers are loaded. Infinite tile layers and object layers will be skipped. -use std::io::{Cursor}; +use std::io::Cursor; use std::path::Path; use std::sync::Arc; @@ -118,9 +118,9 @@ impl AssetLoader for TiledLoader { tiled::DefaultResourceCache::new(), BytesResourceReader::new(&bytes), ); - let map = loader.load_tmx_map(load_context.path()).map_err(|e| { - std::io::Error::other(format!("Could not load TMX map: {e}")) - })?; + let map = loader + .load_tmx_map(load_context.path()) + .map_err(|e| std::io::Error::other(format!("Could not load TMX map: {e}")))?; let mut tilemap_textures = HashMap::default(); #[cfg(not(feature = "atlas"))] From 6ed06ecbcdb54441116a14985f8ffe8cc448d0b9 Mon Sep 17 00:00:00 2001 From: Alex Bentley Date: Sat, 21 Jun 2025 17:11:15 -0400 Subject: [PATCH 5/5] Limit number of concurrent build processes --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..11700c7c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +jobs = 2