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
// "Extension" functions for `Globe`. These do not use any private details of `Globe`,
// and so they are exposed on its _inherent impl_ for convenience only.
use rand::Rng;
use grid::{GridPoint2, GridPoint3, PosInOwningRoot, GridCoord};
use grid::random_column;
use super::chunk::Material;
use super::CursorMut;
use super::globe::Globe;
impl Globe {
/// Attempt to find dry land at surface level. See `find_dry_land`.
pub fn find_surface_dry_land(
&mut self,
column: GridPoint2,
min_air_cells_above: GridCoord,
max_distance_from_starting_point: GridCoord,
) -> Option<GridPoint3> {
// Use land height from world gen to approximate cell position where we might find land.
let land_height = self.gen.land_height(column);
let approx_cell_z = self.spec().approx_cell_z_from_radius(land_height);
// Augment original column with approximate z-value and pass the buck.
let pos = column.with_z(approx_cell_z);
self.find_dry_land(pos, min_air_cells_above, max_distance_from_starting_point)
}
/// Attempt to find dry land near (above or below) the given `pos`.
///
/// A land cell position will only be returned if it has at least as many
/// contiguous cells of air directly above it as specified by `min_air_cells_above`.
///
/// Returns `None` if no such cell can be found within the maximum distance given, e.g.,
/// if the highest land was below water, or our guess about where there should be land
/// exposed to air turned out to be wrong.
///
/// Note that this returns the position of the cell found containing land, not the first cell
/// above it containing air. If you are trying to find a suitable location to, e.g., spawn new
/// entities, then you probably want to use the position one above the position returned by this function.
pub fn find_dry_land(
&mut self,
start_pos: GridPoint3,
min_air_cells_above: GridCoord,
max_distance_from_starting_point: GridCoord,
) -> Option<GridPoint3> {
// Interleave searching up and down at the same time. Start the "down" search at the
// given `start_pos`, and the "up" search one above it.
let mut distance_from_start: GridCoord = 0;
let mut down_pos = start_pos;
let mut up_pos = start_pos;
up_pos.z += 1;
// Share a cursor for searching up and down.
let start_pos_in_owning_root = PosInOwningRoot::new(start_pos, self.spec().root_resolution);
let chunk_origin = self.origin_of_chunk_owning(start_pos_in_owning_root);
let mut cursor = CursorMut::new_in_chunk(self, chunk_origin);
while distance_from_start <= max_distance_from_starting_point {
'candidate_land: for hopefully_land_pos in &[down_pos, up_pos] {
if hopefully_land_pos.z < 0 {
continue;
}
// If it's not land, then we're not interested.
cursor.set_pos(*hopefully_land_pos);
cursor.ensure_chunk_present();
{
// Non-lexical lifetimes SVP.
let cell = cursor.cell().expect(
"We just ensured the chunk is present, but apparently it's not. Kaboom!",
);
if cell.material != Material::Dirt {
continue;
}
}
// Ensure minimum required air above.
let mut hopefully_air_pos = *hopefully_land_pos;
for _ in 0..min_air_cells_above {
hopefully_air_pos.z = hopefully_air_pos.z + 1;
cursor.set_pos(hopefully_air_pos);
cursor.ensure_chunk_present();
let cell = cursor.cell().expect(
"We just ensured the chunk is present, but apparently it's not. Kaboom!",
);
if cell.material != Material::Air {
continue 'candidate_land;
}
}
// Hurrah! This land cell passed the gauntlet.
return Some(*hopefully_land_pos);
}
down_pos.z -= 1;
up_pos.z += 1;
distance_from_start += 1;
}
None
}
/// Find a random cell immediately above dry land. See `find_dry_land`.
///
/// This is useful for finding a suitable point on the surface of the planet to place new entities,
/// e.g., the player character when choosing a random spawn point on the planet.
///
/// Returns `None` if no suitable cell could be found within the maximum number of attempts.
/// Each attempt begins from a new random column.
pub fn air_above_random_surface_dry_land<R: Rng>(
&mut self,
rng: &mut R,
min_air_cells_above: GridCoord,
max_distance_from_starting_point: GridCoord,
max_attempts: usize,
) -> Option<GridPoint3> {
let mut attempts_remaining = max_attempts;
while attempts_remaining > 0 {
let column = random_column(self.spec().root_resolution, rng);
let maybe_pos = self.find_surface_dry_land(
column,
min_air_cells_above,
max_distance_from_starting_point,
);
if let Some(mut pos) = maybe_pos {
// We want the air above the land we found.
pos.z += 1;
return Some(pos);
}
attempts_remaining -= 1;
}
None
}
// TODO: this is not sufficient for finding a suitable place
// to put a cell dweller; i.e. we need something that randomly
// samples positions to find a column with land at the top,
// probably by using the `Gen` to find an approximate location,
// and then working up and down at the same time to find the
// closest land to the "surface".
//
// TODO: now that `air_above_random_surface_dry_land` exists,
// track down and destroy all uses of this.
pub fn find_lowest_cell_containing(
&mut self,
column: GridPoint3,
material: Material,
) -> GridPoint3 {
// Translate into owning root, then start at bedrock.
let mut column = PosInOwningRoot::new(column, self.spec().root_resolution);
column.set_z(0);
let chunk_origin = self.origin_of_chunk_owning(column);
let mut cursor = CursorMut::new_in_chunk(self, chunk_origin);
cursor.set_pos(column.into());
loop {
// TODO: cursor doesn't guarantee you're reading authoritative data.
// Do we care about that? Do we just need to make sure that "ensure chunk"
// loads any other chunks that might be needed? But gah, then you're going to
// have a chain reaction, and load ALL chunks. Maybe it's Cursor's
// responsibility, then. TODO: think about this. :)
//
// Maybe you need a special kind of cursor. That only looks at owned cells
// and automatically updates itself whenever you set its position.
//
// TODO: "collect garbage" occasionally? Or every iteration, even.
cursor.ensure_chunk_present();
{
let pos = cursor.pos();
let cell = cursor.cell().expect(
"We just ensured the chunk is present, but apparently it's not. Kaboom!",
);
if cell.material == material {
// Yay, we found it!
return pos.into();
}
}
let new_pos = cursor.pos().with_z(cursor.pos().z + 1);
cursor.set_pos(new_pos);
}
}
}