veloren_world/
canvas.rs

1use crate::{
2    block::{ZCache, block_from_structure},
3    column::{ColumnGen, ColumnSample},
4    index::IndexRef,
5    land::Land,
6    sim::{SimChunk, WorldSim},
7    util::{Grid, Sampler, seed_expan},
8};
9use common::{
10    calendar::Calendar,
11    generation::EntityInfo,
12    spot::Spot,
13    terrain::{Block, BlockKind, SpriteCfg, Structure, TerrainChunk, TerrainChunkSize},
14    vol::{ReadVol, RectVolSize, WriteVol},
15};
16use rand::prelude::*;
17use rand_chacha::ChaChaRng;
18use std::{borrow::Cow, ops::Deref};
19use vek::*;
20
21#[derive(Copy, Clone)]
22pub struct CanvasInfo<'a> {
23    pub(crate) chunk_pos: Vec2<i32>,
24    pub(crate) wpos: Vec2<i32>,
25    pub(crate) column_grid: &'a Grid<Option<ZCache<'a>>>,
26    pub(crate) column_grid_border: i32,
27    pub(crate) chunks: &'a WorldSim,
28    pub(crate) index: IndexRef<'a>,
29    pub(crate) chunk: &'a SimChunk,
30    pub(crate) calendar: Option<&'a Calendar>,
31}
32
33impl<'a> CanvasInfo<'a> {
34    pub fn calendar(&self) -> Option<&'a Calendar> { self.calendar }
35
36    pub fn wpos(&self) -> Vec2<i32> { self.wpos }
37
38    pub fn area(&self) -> Aabr<i32> {
39        Rect::from((
40            self.wpos(),
41            Extent2::from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32)),
42        ))
43        .into()
44    }
45
46    pub fn col(&self, wpos: Vec2<i32>) -> Option<&'a ColumnSample> {
47        self.column_grid
48            .get(self.column_grid_border + wpos - self.wpos())
49            .and_then(Option::as_ref)
50            .map(|zc| &zc.sample)
51    }
52
53    /// Attempt to get the data for the given column, generating it if we don't
54    /// have it.
55    ///
56    /// This function does not (currently) cache generated columns.
57    pub fn col_or_gen(&self, wpos: Vec2<i32>) -> Option<Cow<'a, ColumnSample>> {
58        self.col(wpos).map(Cow::Borrowed).or_else(|| {
59            Some(Cow::Owned(ColumnGen::new(self.chunks()).get((
60                wpos,
61                self.index(),
62                self.calendar,
63            ))?))
64        })
65    }
66
67    /// Find all spots within range of this canvas's chunk. Returns `(wpos,
68    /// spot, seed)`.
69    pub fn nearby_spots(&self) -> impl Iterator<Item = (Vec2<i32>, Spot, u32)> + '_ {
70        (-1..2)
71            .flat_map(|x| (-1..2).map(move |y| Vec2::new(x, y)))
72            .filter_map(move |pos| {
73                let pos = self.chunk_pos + pos;
74                self.chunks.get(pos).and_then(|c| c.spot).map(|spot| {
75                    let wpos = pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
76                        e * sz as i32 + sz as i32 / 2
77                    });
78                    // TODO: Very dumb, not this.
79                    let seed = pos.x as u32 | (pos.y as u32).wrapping_shl(16);
80
81                    (wpos, spot, seed ^ 0xA801D82E)
82                })
83            })
84    }
85
86    pub fn index(&self) -> IndexRef<'a> { self.index }
87
88    pub fn chunk(&self) -> &'a SimChunk { self.chunk }
89
90    pub fn chunks(&self) -> &'a WorldSim { self.chunks }
91
92    pub fn land(&self) -> Land<'_> { Land::from_sim(self.chunks) }
93
94    pub fn with_mock_canvas_info<A, F: for<'b> FnOnce(&CanvasInfo<'b>) -> A>(
95        index: IndexRef<'a>,
96        sim: &'a WorldSim,
97        f: F,
98    ) -> A {
99        let zcache_grid = Grid::populate_from(Vec2::broadcast(1), |_| None);
100        let sim_chunk = SimChunk {
101            chaos: 0.0,
102            alt: 0.0,
103            basement: 0.0,
104            water_alt: 0.0,
105            downhill: None,
106            flux: 0.0,
107            temp: 0.0,
108            humidity: 0.0,
109            rockiness: 0.0,
110            tree_density: 0.0,
111            forest_kind: crate::all::ForestKind::Palm,
112            spawn_rate: 0.0,
113            river: Default::default(),
114            surface_veg: 0.0,
115            sites: Vec::new(),
116            place: None,
117            poi: None,
118            path: Default::default(),
119            cliff_height: 0.0,
120            contains_waypoint: false,
121            spot: None,
122        };
123        f(&CanvasInfo {
124            chunk_pos: Vec2::zero(),
125            wpos: Vec2::zero(),
126            column_grid: &zcache_grid,
127            column_grid_border: 0,
128            chunks: sim,
129            index,
130            chunk: &sim_chunk,
131            calendar: None,
132        })
133    }
134}
135
136pub struct Canvas<'a> {
137    pub(crate) info: CanvasInfo<'a>,
138    pub(crate) chunk: &'a mut TerrainChunk,
139    pub(crate) entities: Vec<EntityInfo>,
140    pub(crate) rtsim_resource_blocks: Vec<Vec3<i32>>,
141}
142
143impl<'a> Canvas<'a> {
144    /// The borrow checker complains at immutable features of canvas (column
145    /// sampling, etc.) being used at the same time as mutable features
146    /// (writing blocks). To avoid this, this method extracts the
147    /// inner `CanvasInfo` such that it may be used independently.
148    pub fn info(&mut self) -> CanvasInfo<'a> { self.info }
149
150    pub fn get(&self, pos: Vec3<i32>) -> Block {
151        self.chunk
152            .get(pos - self.wpos())
153            .ok()
154            .copied()
155            .unwrap_or_else(Block::empty)
156    }
157
158    /// Set a block in the canvas chunk. Note that if the block is a resource
159    /// (see [`Block::get_rtsim_resource`]) then [`Canvas::map_resource`] should
160    /// be used instead.
161    pub fn set(&mut self, pos: Vec3<i32>, block: Block) {
162        if block.get_rtsim_resource().is_some() {
163            self.rtsim_resource_blocks.push(pos);
164        }
165        let _ = self.chunk.set(pos - self.wpos(), block);
166    }
167
168    /// Map a block in the canvas chunk. Note that if the block is a resource
169    /// (see [`Block::get_rtsim_resource`]) then [`Canvas::map_resource`] should
170    /// be used instead.
171    pub fn map(&mut self, pos: Vec3<i32>, f: impl FnOnce(Block) -> Block) {
172        let _ = self.chunk.map(pos - self.wpos(), f);
173    }
174
175    /// Like [`Canvas::map`], except allows marking resource-containing blocks
176    /// appropriately.
177    pub fn map_resource(&mut self, pos: Vec3<i32>, f: impl FnOnce(Block) -> Block) {
178        let _ = self.chunk.map(pos - self.wpos(), |b| {
179            let new_block = f(b);
180            if new_block.get_rtsim_resource().is_some() {
181                self.rtsim_resource_blocks.push(pos);
182            }
183            new_block
184        });
185    }
186
187    /// Like [`Canvas::map`], except allows marking resource-containing blocks
188    /// appropriately and allows setting sprite_cfg.
189    pub fn map_resource_with_cfg(
190        &mut self,
191        pos: Vec3<i32>,
192        f: impl FnOnce(Block, &mut Option<SpriteCfg>) -> Block,
193    ) {
194        let mut sprite_cfg = None;
195        self.map_resource(pos, |block| f(block, &mut sprite_cfg));
196        if let Some(sprite_cfg) = sprite_cfg {
197            self.set_sprite_cfg(pos, sprite_cfg);
198        }
199    }
200
201    pub fn set_sprite_cfg(&mut self, pos: Vec3<i32>, sprite_cfg: SpriteCfg) {
202        let rpos = pos - self.wpos();
203        self.chunk.meta_mut().set_sprite_cfg_at(rpos, sprite_cfg);
204    }
205
206    pub fn foreach_col_area(
207        &mut self,
208        aabr: Aabr<i32>,
209        mut f: impl FnMut(&mut Self, Vec2<i32>, &ColumnSample),
210    ) {
211        let chunk_aabr = Aabr {
212            min: self.wpos(),
213            max: self.wpos() + Vec2::from(self.area().size()),
214        };
215
216        for y in chunk_aabr.min.y.max(aabr.min.y)..chunk_aabr.max.y.min(aabr.max.y) {
217            for x in chunk_aabr.min.x.max(aabr.min.x)..chunk_aabr.max.x.min(aabr.max.x) {
218                let wpos2d = Vec2::new(x, y);
219                let info = self.info;
220                let col = if let Some(col) = info.col(wpos2d) {
221                    col
222                } else {
223                    return;
224                };
225                f(self, wpos2d, col);
226            }
227        }
228    }
229
230    /// Execute an operation upon each column in this canvas.
231    pub fn foreach_col(&mut self, f: impl FnMut(&mut Self, Vec2<i32>, &ColumnSample)) {
232        self.foreach_col_area(
233            Aabr {
234                min: Vec2::broadcast(i32::MIN),
235                max: Vec2::broadcast(i32::MAX),
236            },
237            f,
238        );
239    }
240
241    /// Blit a structure on to the canvas at the given position.
242    ///
243    /// Note that this function should be called with identical parameters by
244    /// all chunks within the bounds of the structure to avoid cut-offs
245    /// occurring at chunk borders. Deterministic RNG is advised!
246    pub fn blit_structure(
247        &mut self,
248        origin: Vec3<i32>,
249        structure: &Structure,
250        seed: u32,
251        units: Vec2<Vec2<i32>>,
252        with_snow: bool,
253    ) {
254        let mut entities: Vec<(Vec3<f32>, String)> = Vec::new();
255        let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
256        let info = self.info();
257        self.foreach_col(|canvas, wpos2d, col| {
258            let rpos2d = wpos2d - origin.xy();
259            let rpos2d = units.x * rpos2d.x + units.y * rpos2d.y;
260
261            let mut above = true;
262            for z in (structure.get_bounds().min.z..structure.get_bounds().max.z).rev() {
263                if let Ok(sblock) = structure.get(rpos2d.with_z(z)) {
264                    let mut add_snow = false;
265                    let mut new_sprite_cfg = None;
266                    let wpos = wpos2d.with_z(origin.z + z);
267                    canvas.map(wpos, |block| {
268                        if let Some((new_block, sprite_cfg, entity_path)) = block_from_structure(
269                            info.index,
270                            sblock,
271                            wpos2d.with_z(origin.z + z),
272                            origin.xy(),
273                            seed,
274                            col,
275                            |sprite| block.into_vacant().with_sprite(sprite),
276                            info.calendar,
277                            &units,
278                        ) {
279                            if let Some(spec) = entity_path {
280                                // Spawn NPCs at the pos of this block
281                                entities.push((
282                                    wpos2d.with_z(origin.z + z).map(|e| e as f32)
283                                        + Vec3::new(0.5, 0.5, 0.0),
284                                    spec.to_string(),
285                                ));
286                            }
287                            new_sprite_cfg = sprite_cfg;
288                            if !new_block.is_air() {
289                                if with_snow && col.snow_cover && above {
290                                    add_snow = true;
291                                }
292                                above = false;
293                            }
294                            new_block
295                        } else {
296                            block
297                        }
298                    });
299
300                    // Add sprite configuration for those that need it
301                    if let Some(sprite_cfg) = new_sprite_cfg {
302                        canvas.set_sprite_cfg(wpos, sprite_cfg);
303                    }
304
305                    if add_snow {
306                        canvas.set(
307                            wpos2d.with_z(origin.z + z + 1),
308                            Block::new(BlockKind::Snow, Rgb::new(210, 210, 255)),
309                        );
310                    }
311                }
312            }
313        });
314        for (pos, spec) in entities.drain(..) {
315            self.spawn(EntityInfo::at(pos).with_asset_expect(&spec, &mut rng, None));
316        }
317    }
318
319    pub fn find_spawn_pos(&self, wpos: Vec3<i32>) -> Option<Vec3<i32>> {
320        let height = 2;
321        let search_dist: i32 = 8;
322
323        (1..search_dist * 2 + 1)
324            .rev()
325            .map(|z| wpos.z + if z % 2 != 0 { z / 2 } else { -(z / 2) })
326            .find(|&z| {
327                self.get(wpos.xy().with_z(z - 1)).is_solid()
328                    && (0..height).all(|z_offs| self.get(wpos.xy().with_z(z + z_offs)).is_fluid())
329            })
330            .map(|z| wpos.xy().with_z(z))
331    }
332
333    pub fn spawn(&mut self, entity: EntityInfo) { self.entities.push(entity); }
334}
335
336impl<'a> Deref for Canvas<'a> {
337    type Target = CanvasInfo<'a>;
338
339    fn deref(&self) -> &Self::Target { &self.info }
340}