1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
use bevy::prelude::*;

use crate::enums::biome::Marker;

/// A resource that stores the seed for the generation of the primal realm.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Resource)]
pub struct GenerationSeed(pub u32);

impl GenerationSeed {
    /// Get the seed as a `u64` for use in seedable `thread_rng`.
    #[must_use]
    pub fn as_u64(&self) -> u64 {
        u64::from(self.0)
    }
}

/// Stores in the generated biome map and generated object map.
///
/// This is used to store the results of the noise generation, and then
/// used to generate the actual realm. The default map size is 1000x1000.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Resource)]
pub struct GeneratedMaps {
    /// The biome map.
    pub biome_map: Vec<Vec<Marker>>,
    /// The object map.
    pub object_map: Vec<Vec<usize>>,
    /// The width of the map. Each tile is 16x16 px.
    width: usize,
    /// The height of the map. Each tile is 16x16 px.
    height: usize,
}

impl GeneratedMaps {
    /// The default size of the map.
    pub const DEFAULT_SIZE: (usize, usize) = (100, 100);

    /// Create a new Empty `GeneratedMaps` with the given size.
    #[must_use]
    pub fn new(size: (usize, usize)) -> Self {
        let mut empty_map = Self {
            biome_map: Vec::with_capacity(size.0),
            object_map: Vec::with_capacity(size.0),
            width: size.0,
            height: size.1,
        };
        for _ in 0..size.0 {
            empty_map.biome_map.push(Vec::with_capacity(size.1));
            empty_map.object_map.push(Vec::with_capacity(size.1));
        }

        empty_map
    }

    /// Ask for the biome at the given position.
    ///
    /// Each tile is a 16x16 px but is accessed via the x and y coordinates of the tile.
    ///
    /// The world origin is in the exact center of the map. This is represented by a 0,0
    /// coordinate being at the middle index of the map.
    #[must_use]
    pub fn get_biome(&self, pos: Vec2) -> Marker {
        let (x, y) = self.world_to_map(Vec3::new(pos.x, pos.y, 0.0));

        self.biome_map[x][y]
    }

    /// Function to transform map coordinates to world coordinates.
    ///
    /// The world origin is in the exact center of the map. This is represented by a 0,0
    /// coordinate being at the middle index of the map.
    ///
    /// The world grid is 16x16 pixels per tile.
    ///
    /// ## Note
    ///
    /// This function has possible truncation and precision loss. This is because the
    /// world coordinates are floats and the map coordinates are usize.
    ///
    /// # Parameters
    ///
    /// * `pos`: The map position to convert to world coordinates. It sets `pos.z` to 0.0.
    ///
    /// # Returns
    ///
    /// The world position for the given map coordinates.
    #[must_use]
    pub fn map_to_world(&self, pos: (usize, usize)) -> Vec3 {
        #[allow(clippy::cast_precision_loss)]
        Vec3::new(
            (pos.0 as f32).mul_add(16.0, self.width as f32 * -8.0),
            (pos.1 as f32).mul_add(16.0, self.height as f32 * -8.0),
            0.0,
        )
    }

    /// Function to transform world coordinates to map coordinates.
    ///
    /// The world origin is in the exact center of the map. This is represented by a 0,0
    /// world coordinate being at the middle index of the map (width/2, height/2).
    ///
    /// The world grid is 16x16 pixels per tile.
    ///
    /// ## Note
    ///
    /// This function has possible truncation and precision loss. This is because the
    /// world coordinates are floats and the map coordinates are usize.
    ///
    /// # Parameters
    ///
    /// * `pos`: The world position to convert to map coordinates. `pos.z` is ignored.
    ///
    /// # Returns
    ///
    /// A tuple of the x and y coordinates of the map for the given world position.
    ///
    /// If the world position is out of bounds, then `(0,0)` is returned.
    #[must_use]
    pub fn world_to_map(&self, pos: Vec3) -> (usize, usize) {
        // Check if the position is out of bounds.
        #[allow(clippy::cast_precision_loss)]
        if pos.x < -(self.width as f32 / 2.0) || pos.x > (self.width as f32 / 2.0) {
            tracing::error!(
                "world_to_map: X position out of bounds: ({}, {})",
                pos.x,
                pos.y
            );
            return (0, 0);
        }
        #[allow(clippy::cast_precision_loss)]
        if pos.y < -(self.height as f32 / 2.0) || pos.y > (self.height as f32 / 2.0) {
            tracing::error!(
                "world_to_map: Y position out of bounds: ({}, {})",
                pos.x,
                pos.y
            );
            return (0, 0);
        }

        #[allow(
            clippy::cast_precision_loss,
            clippy::cast_possible_truncation,
            clippy::cast_sign_loss
        )]
        (
            pos.x.mul_add(1. / 16.0, self.width as f32 * -8.0) as usize,
            pos.y.mul_add(1. / 16.0, self.height as f32 * -8.0) as usize,
        )
    }

    /// Get the dimensions of the map.
    ///
    /// Returns a tuple of the width and height of the map.
    #[must_use]
    pub const fn dimensions(&self) -> (usize, usize) {
        (self.width, self.height)
    }

    /// Reset the maps to be empty.
    ///
    /// This will clear the biome and object maps and reset them to be empty. It will also
    /// add the correct amount of empty vectors to the maps (just like during a `new` call)
    pub fn reset(&mut self) {
        self.biome_map.clear();
        self.object_map.clear();

        self.biome_map
            .resize(self.height, Vec::with_capacity(self.width));
        self.object_map
            .resize(self.height, Vec::with_capacity(self.width));
    }

    /// Resets the maps to be empty and sets the dimensions to the given size.
    ///
    /// This will clear the biome and object maps and reset them to be empty. It will also
    /// add the correct amount of empty vectors to the maps (just like during a `new` call)
    /// and set the dimensions to the given size.
    pub fn reset_with_dimensions(&mut self, width: usize, height: usize) {
        self.width = width;
        self.height = height;

        self.reset();
    }

    /// Add a biome to the map at the given position.
    pub fn insert_biome(&mut self, pos: Vec2, biome: Marker) {
        let (x, y) = self.world_to_map(Vec3::new(pos.x, pos.y, 0.0));

        // Find the given x vector and make sure we can insert to y or else add more default
        // biomes until we can. The vec is already at the correct capacity so we don't need to
        // resize.
        while self.biome_map[x].len() <= y {
            self.biome_map[x].push(Marker::Empty);
        }

        self.biome_map[x][y] = biome;
    }

    /// Add an object to the map at the given position.
    pub fn insert_object(&mut self, pos: Vec2, object: usize) {
        let (x, y) = self.world_to_map(Vec3::new(pos.x, pos.y, 0.0));

        // Find the given x vector and make sure we can insert to y or else add more default
        // objects until we can. The vec is already at the correct capacity so we don't need to
        // resize.
        while self.object_map[x].len() <= y {
            self.object_map[x].push(0);
        }

        self.object_map[x][y] = object;
    }

    /// Push a biome onto the map.
    pub fn push_biome(&mut self, x_pos: usize, biome: Marker) {
        // Sanity check our biome_map length.
        if self.biome_map.len() != self.width {
            tracing::warn!(
                "push_biome: biome_map length did not match width: {} != {} (fixing)",
                self.biome_map.len(),
                self.width
            );
            self.biome_map
                .resize(self.width, Vec::with_capacity(self.height));
        }

        // Sanity check that x_pos is a valid index.
        if x_pos >= self.width {
            tracing::error!(
                "push_biome: x_pos out of bounds: {} (max: {})",
                x_pos,
                self.width - 1
            );
            return;
        }

        self.biome_map[x_pos].push(biome);
    }
}

impl Default for GeneratedMaps {
    fn default() -> Self {
        Self::new(Self::DEFAULT_SIZE)
    }
}