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    /// Converts a local anchor offset (x maps to world X, y to world Z) to a world XZ position,
76    /// following the `Quat::from_rotation_y` rotation used when placing entities.
77    pub fn anchor_world(&self, local: Vec2) -> Vec2 {
78        let (sin, cos) = self.rotation.rotation().sin_cos();
79        self.center + Vec2::new(local.x * cos + local.y * sin, local.y * cos - local.x * sin)
80    }
81    /// Gets a reference to the placement configuration entry that describes this entity.
82    pub fn entry(&self) -> &PlacementConfigEntry {
83        &self.entry
84    }
85}
86
87fn compute_footprint(
88    center: Vec2,
89    extent: UVec2,
90    rotation: GridRotation
91) -> (Vec2, (Vec2, Vec2), Vec<GridTilePos>) {
92    // Change extent if rotated
93    let extent = match rotation {
94        GridRotation::R0 | GridRotation::R180 => extent,
95        GridRotation::R90 | GridRotation::R270 => UVec2::new(extent.y, extent.x)
96    };
97
98    // Add an offset to start from the center of the object
99    let offset_to_center_object =
100        Vec2::new(extent.x as f32, extent.y as f32) * TILE_SIZE / 2.0 - TILE_SIZE / 2.0;
101
102    // Compute the footprint
103    let mut footprint = Vec::new();
104    for x in 0..extent.x {
105        for z in 0..extent.y {
106            let local_pos =
107                center - offset_to_center_object + Vec2::new(x as f32, z as f32) * TILE_SIZE;
108            let (chunk_pos, local_pos) = Grid::get_nearest_tile(local_pos);
109            footprint.push((chunk_pos, (local_pos.0, local_pos.1)));
110        }
111    }
112
113    // Compute bbox
114    let bottom_left_pos = center - offset_to_center_object
115        + Vec2::new(extent.x as f32 - 1.0, extent.y as f32 - 1.0) * TILE_SIZE;
116    let bottom_left_pos = Grid::get_tile_world_pos(Grid::get_nearest_tile(bottom_left_pos));
117    let top_right_pos = center - offset_to_center_object;
118    let top_right_pos = Grid::get_tile_world_pos(Grid::get_nearest_tile(top_right_pos));
119    let center = (bottom_left_pos + top_right_pos) / 2.0;
120
121    (center, (bottom_left_pos, top_right_pos), footprint)
122}