Skip to main content

veloren_world/site/plot/
building.rs

1use super::*;
2use crate::Land;
3use common::{
4    calendar::Calendar,
5    terrain::{Block, BlockKind},
6};
7use rand::prelude::*;
8use vek::*;
9
10/// Wings represent the external structure of a building.
11/// They are arranged in a tree, with child wings branching off their parents.
12/// Wings are defined by an origin on one of their ends, a direction, and a
13/// width/length. The connecting face of a child wing must be fully enclosed by
14/// the area of its parent. Wings are unconcerned with aesthetics: it is the job
15/// of style-driven rendering to turn a set of wings into blocks.
16///
17/// .------.
18/// |      |
19/// ^      |
20/// |      |
21/// x------'
22struct Wing {
23    origin: Vec2<i32>,
24    dir: Dir2,
25    // (width, length)
26    dim: Vec2<i32>,
27    children: Vec<Wing>,
28}
29
30impl Wing {
31    fn aabr(&self) -> Aabr<i32> {
32        let p = self.origin
33            + self.dir.rotated_cw().to_vec2() * self.dim.x
34            + self.dir.to_vec2() * self.dim.y;
35        Aabr {
36            min: Vec2::min(self.origin, p),
37            max: Vec2::max(self.origin, p),
38        }
39    }
40}
41
42struct Room {
43    #[allow(dead_code)]
44    aabb: Aabb<i32>,
45}
46
47/// Represents house data generated by the `generate()` method
48pub struct Building {
49    /// Tile position of the door tile
50    pub door_tile: Vec2<i32>,
51    /// Axis aligned bounding region of tiles
52    tile_aabr: Aabr<i32>,
53    /// Axis aligned bounding region for the house
54    #[allow(dead_code)]
55    bounds: Aabr<i32>,
56    /// Approximate altitude of the door tile
57    pub(crate) alt: i32,
58    #[allow(dead_code)]
59    rooms: Vec<Room>,
60    base: Wing,
61}
62
63impl Building {
64    pub fn generate(
65        land: &Land,
66        rng: &mut impl Rng,
67        site: &Site,
68        door_tile: Vec2<i32>,
69        door_dir: Vec2<i32>,
70        tile_aabr: Aabr<i32>,
71        _calendar: Option<&Calendar>,
72        alt: Option<i32>,
73    ) -> Self {
74        let mut rooms = Vec::new();
75
76        for _ in 0..rng.random_range(2..5) {
77            let min = tile_aabr
78                .min
79                .map2(tile_aabr.max, |min, max| rng.random_range(min..max));
80            let max = min.map2(tile_aabr.max, |min, max| rng.random_range(min..max) + 1);
81            rooms.push(Room {
82                aabb: Aabb {
83                    min: min.with_z(0),
84                    max: max.with_z(rng.random_range(1..4)),
85                },
86            });
87        }
88
89        fn generate_wing(parent: &mut Wing, _limits: Aabr<i32>, rng: &mut impl Rng) {
90            if rng.random_bool(0.5) && parent.dim.y >= 3 {
91                let w = rng.random_range(1..=parent.dim.y);
92                let is_left = rng.random_bool(0.5);
93                let offset = rng.random_range(0..=parent.dim.y - w);
94                let mut wing = Wing {
95                    dir: if is_left {
96                        parent.dir.rotated_cw()
97                    } else {
98                        parent.dir.rotated_ccw()
99                    },
100                    origin: parent.origin
101                        + if is_left {
102                            Vec2::new(-1, 0)
103                        } else {
104                            parent.dir.rotated_cw().to_vec2() * parent.dim.x
105                        }
106                        + parent.dir.to_vec2()
107                            * if is_left {
108                                offset
109                            } else {
110                                parent.dim.y - 1 - offset
111                            },
112                    dim: Vec2::new(w, parent.dim.x + rng.random_range(1..3)),
113                    children: Vec::new(),
114                };
115                generate_wing(&mut wing, _limits, rng);
116                parent.children.push(wing);
117            }
118        }
119
120        let mut base = Wing {
121            dir: Dir2::Y,
122            dim: Vec2::new(2, 3),
123            origin: Vec2::new((tile_aabr.min.x + tile_aabr.max.x) / 2, tile_aabr.min.y),
124            children: Vec::new(),
125        };
126        generate_wing(&mut base, tile_aabr, rng);
127
128        Self {
129            door_tile,
130            tile_aabr,
131            bounds: Aabr {
132                min: site.tile_wpos(tile_aabr.min),
133                max: site.tile_wpos(tile_aabr.max),
134            },
135            alt: alt.unwrap_or_else(|| {
136                land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32
137            }),
138            rooms,
139            base,
140        }
141    }
142
143    fn walk_wing(
144        &self,
145        wing: &Wing,
146        site: &Site,
147        painter: &Painter,
148        _interior: &mut PrimitiveRef,
149        body: &mut PrimitiveRef,
150        roof: &mut PrimitiveRef,
151        depth: i32,
152    ) {
153        let aabr = wing.aabr();
154        let aabb = Aabb {
155            min: site.tile_wpos(aabr.min).with_z(self.alt),
156            max: site.tile_wpos(aabr.max).with_z(self.alt + 10 - depth * 3) + 1,
157        };
158        *body = body.union(painter.aabb(aabb));
159        *roof = roof.union(
160            painter.vault(
161                Aabb {
162                    min: aabb.min.xy().with_z(aabb.max.z),
163                    max: aabb
164                        .max
165                        .xy()
166                        .with_z(aabb.max.z + wing.dim.x * TILE_SIZE as i32 / 2),
167                },
168                wing.dir,
169            ),
170        );
171        for child in &wing.children {
172            self.walk_wing(child, site, painter, _interior, body, roof, depth + 1);
173        }
174    }
175}
176
177impl Structure for Building {
178    #[cfg(feature = "dyn-lib")]
179    #[unsafe(export_name = "as_dyn_structure_building")]
180    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
181        Some((Self::as_dyn_impl(self), "as_dyn_structure_building"))
182    }
183
184    fn spawn_rules_inner(
185        &self,
186        spawn_rules: &mut SpawnRules,
187        _land: &Land,
188        _wpos: Vec2<i32>,
189        weight: f32,
190    ) {
191        spawn_rules.prefer_alt(self.alt as f32, weight * 1.0);
192    }
193
194    fn render_inner(&self, site: &Site, _land: &Land, painter: &Painter) {
195        const ROOM_HEIGHT: i32 = 5;
196        let mut interior = painter.empty();
197        let mut body = painter.empty();
198        let mut frame = painter.empty();
199        let mut roof = painter.empty();
200
201        self.walk_wing(
202            &self.base,
203            site,
204            painter,
205            &mut interior,
206            &mut body,
207            &mut roof,
208            0,
209        );
210
211        for room in &self.rooms {
212            let min = site
213                .tile_wpos(room.aabb.min.xy())
214                .with_z(self.alt + room.aabb.min.z * ROOM_HEIGHT);
215            let max = site
216                .tile_wpos(room.aabb.max.xy())
217                .with_z(self.alt + room.aabb.max.z * ROOM_HEIGHT)
218                + 1;
219
220            const EAVES: i32 = 1;
221
222            // Interior
223            interior = interior.union(painter.aabb(Aabb {
224                min: min + 1,
225                max: max - 1,
226            }));
227
228            // Body
229            body = body.union(painter.aabb(Aabb { min, max }));
230
231            // Roof
232            roof = roof.union(
233                painter.pyramid(Aabb {
234                    min: (min.xy() - EAVES).with_z(max.z),
235                    max: (max.xy() + EAVES)
236                        .with_z(max.z + (max.xy() - min.xy()).reduce_min() + EAVES * 2),
237                }),
238            );
239
240            // Frame
241            for tile in util::aabb_iter(room.aabb) {
242                let min = site
243                    .tile_wpos(tile.xy())
244                    .with_z(self.alt + tile.z * ROOM_HEIGHT);
245                let max = min + Vec2::broadcast(TILE_SIZE as i32).with_z(ROOM_HEIGHT) + 1;
246                frame = frame.union(
247                    painter
248                        .aabb(Aabb { min, max })
249                        .without(painter.aabb(Aabb {
250                            min: min + 1 - Vec3::unit_x(),
251                            max: max - 1 + Vec3::unit_x(),
252                        }))
253                        .without(painter.aabb(Aabb {
254                            min: min + 1 - Vec3::unit_y(),
255                            max: max - 1 + Vec3::unit_y(),
256                        }))
257                        .without(painter.aabb(Aabb {
258                            min: min + 1 - Vec3::unit_z(),
259                            max: max - 1 + Vec3::unit_z(),
260                        })),
261                );
262            }
263        }
264
265        // Base
266        painter
267            .aabb(Aabb {
268                min: (site.tile_wpos(self.tile_aabr.min) - 1).with_z(self.alt - 10),
269                max: (site.tile_wpos(self.tile_aabr.max) + 2).with_z(self.alt),
270            })
271            .fill(Fill::Block(Block::new(
272                BlockKind::Rock,
273                Rgb::new(50, 50, 60),
274            )));
275        // Interior
276        body.without(interior).fill(Fill::Block(Block::new(
277            BlockKind::Wood,
278            Rgb::new(180, 120, 70),
279        )));
280        // Roof
281        roof.fill(Fill::Block(Block::new(
282            BlockKind::Wood,
283            Rgb::new(100, 30, 10),
284        )));
285        // Frame
286        frame.fill(Fill::Block(Block::new(
287            BlockKind::Wood,
288            Rgb::new(100, 70, 50),
289        )));
290        // Door
291        painter
292            .aabb(Aabb {
293                min: (site.tile_wpos(self.door_tile)).with_z(self.alt),
294                max: (site.tile_wpos(self.door_tile + 1)).with_z(self.alt + 10),
295            })
296            .fill(Fill::Block(Block::new(
297                BlockKind::Wood,
298                Rgb::new(50, 70, 50),
299            )));
300    }
301}