Skip to main content

wde_terrain_grid/core/
grid_entity.rs

1use bevy::prelude::*;
2
3use crate::{
4    core::{
5        entries::PlacementConfigEntry,
6        grid::{GridTilePos, TILE_SIZE}
7    },
8    prelude::Grid
9};
10
11/// Local rotation of an entity on the grid around its center, in 90 degree increments.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect)]
13pub enum GridRotation {
14    #[default]
15    R0,
16    R90,
17    R180,
18    R270
19}
20impl GridRotation {
21    /// Gives the rotation resulting from the given rotation.
22    pub fn rotation(self) -> f32 {
23        match self {
24            GridRotation::R0 => 0.0,
25            GridRotation::R90 => std::f32::consts::FRAC_PI_2,
26            GridRotation::R180 => std::f32::consts::PI,
27            GridRotation::R270 => 3.0 * std::f32::consts::FRAC_PI_2
28        }
29    }
30}
31
32/// Describes an entity that has been placed on the grid.
33#[derive(Component, Clone, Debug, Reflect)]
34#[reflect(Component)]
35pub struct GridEntity {
36    entry: PlacementConfigEntry,
37    center: Vec2,
38    rotation: GridRotation,
39    bbox: (Vec2, Vec2), // (bottom left, top_right)
40    footprint: Vec<GridTilePos>
41}
42impl GridEntity {
43    /// Creates a new GridEntity with the given center position, rotation, and placement configuration entry.
44    ///
45    /// # Arguments
46    /// * `center` - The center position of the entity in world coordinates. Note that this is not necessarily the center of the footprint, but the position used to place the entity on the grid. It will be adjusted to the nearest grid tile.
47    /// * `rotation` - The rotation of the entity on the grid, in 90 degree increments. This will affect the footprint of the entity.
48    /// * `entry` - The placement configuration entry that describes the entity, including its extent and anchors.
49    pub fn new(center: Vec2, rotation: GridRotation, entry: PlacementConfigEntry) -> Self {
50        let (center, bbox, footprint) = compute_footprint(center, entry.extent, rotation);
51        GridEntity {
52            entry,
53            center,
54            rotation,
55            bbox,
56            footprint
57        }
58    }
59    /// Get the center position of this entity in world coordinates.
60    pub fn center(&self) -> Vec2 {
61        self.center
62    }
63    /// Get the rotation of this entity on the grid.
64    pub fn rotation(&self) -> GridRotation {
65        self.rotation
66    }
67    /// Gets the bounding box of this entity in world coordinates (bottom left, top right).
68    pub fn bbox(&self) -> (Vec2, Vec2) {
69        self.bbox
70    }
71    /// Gets the list of grid tiles that are occupied by this entity footprint.
72    pub fn footprint(&self) -> &Vec<GridTilePos> {
73        &self.footprint
74    }
75    /// Gets a reference to the placement configuration entry that describes this entity.
76    pub fn entry(&self) -> &PlacementConfigEntry {
77        &self.entry
78    }
79}
80
81fn compute_footprint(
82    center: Vec2,
83    extent: UVec2,
84    rotation: GridRotation
85) -> (Vec2, (Vec2, Vec2), Vec<GridTilePos>) {
86    // Change extent if rotated
87    let extent = match rotation {
88        GridRotation::R0 | GridRotation::R180 => extent,
89        GridRotation::R90 | GridRotation::R270 => UVec2::new(extent.y, extent.x)
90    };
91
92    // Add an offset to start from the center of the object
93    let offset_to_center_object =
94        Vec2::new(extent.x as f32, extent.y as f32) * TILE_SIZE / 2.0 - TILE_SIZE / 2.0;
95
96    // Compute the footprint
97    let mut footprint = Vec::new();
98    for x in 0..extent.x {
99        for z in 0..extent.y {
100            let local_pos =
101                center - offset_to_center_object + Vec2::new(x as f32, z as f32) * TILE_SIZE;
102            let (chunk_pos, local_pos) = Grid::get_nearest_tile(local_pos);
103            footprint.push((chunk_pos, (local_pos.0, local_pos.1)));
104        }
105    }
106
107    // Compute bbox
108    let bottom_left_pos = center - offset_to_center_object
109        + Vec2::new(extent.x as f32 - 1.0, extent.y as f32 - 1.0) * TILE_SIZE;
110    let bottom_left_pos = Grid::get_tile_world_pos(Grid::get_nearest_tile(bottom_left_pos));
111    let top_right_pos = center - offset_to_center_object;
112    let top_right_pos = Grid::get_tile_world_pos(Grid::get_nearest_tile(top_right_pos));
113    let center = (bottom_left_pos + top_right_pos) / 2.0;
114
115    (center, (bottom_left_pos, top_right_pos), footprint)
116}