veloren_world/site/util/
sprites.rs

1use crate::site::{Fill, Painter};
2
3use common::{
4    terrain::{
5        Block, SpriteKind,
6        sprite::{MirrorX, Ori},
7    },
8    util::Dir2,
9};
10use enum_map::EnumMap;
11use strum::IntoEnumIterator as _;
12use vek::*;
13
14/// A struct to make it easier to create sprites that tile on a 2d plane. Both
15/// the `side` and `corner` sprite have to be mirrorable.
16///
17/// The bounds are inclusive.
18pub struct Tileable2 {
19    alt: i32,
20    bounds: Aabr<i32>,
21    center: Block,
22    side: EnumMap<Dir2, Block>,
23    /// The corner selected is `Dir::diagonal()`.
24    corner: EnumMap<Dir2, Block>,
25    rotation: Dir2,
26}
27
28impl Tileable2 {
29    pub fn empty() -> Self {
30        Self {
31            alt: 0,
32            bounds: Aabr::default(),
33            center: Block::empty(),
34            side: EnumMap::from_fn(|_| Block::empty()),
35            corner: EnumMap::from_fn(|_| Block::empty()),
36            rotation: Dir2::X,
37        }
38    }
39
40    pub fn new_sprite(
41        bounds: Aabr<i32>,
42        alt: i32,
43        center: SpriteKind,
44        side: SpriteKind,
45        corner: SpriteKind,
46    ) -> Self {
47        Self::empty()
48            .with_bounds(bounds)
49            .with_alt(alt)
50            .with_center_sprite(center)
51            .with_side_sprite(side)
52            .with_corner_sprite(corner)
53    }
54
55    pub fn two_by(len: i32, pos: Vec3<i32>, dir: Dir2) -> Self {
56        Self::empty()
57            .with_rotation(dir)
58            .with_center_size(pos, Vec2::new(len, 2))
59    }
60
61    pub fn with_center_size(self, center: Vec3<i32>, size: Vec2<i32>) -> Self {
62        let extent_min = (size - 1) / 2;
63        let extent_max = size / 2;
64        let rot = self.rotation;
65        let bounds = Aabr {
66            min: center.xy() - rot.vec2(extent_min.x, extent_min.y),
67            max: center.xy() + rot.vec2(extent_max.x, extent_max.y),
68        }
69        .made_valid();
70        self.with_alt(center.z).with_bounds(bounds)
71    }
72
73    /// Bounds are inclusive.
74    pub fn with_bounds(mut self, bounds: Aabr<i32>) -> Self {
75        self.bounds = bounds;
76        self
77    }
78
79    pub fn with_alt(mut self, alt: i32) -> Self {
80        self.alt = alt;
81        self
82    }
83
84    pub fn with_center(mut self, block: Block) -> Self {
85        self.center = block;
86        self
87    }
88
89    pub fn with_center_sprite(mut self, sprite: SpriteKind) -> Self {
90        self.center = self.center.with_sprite(sprite);
91        self
92    }
93
94    pub fn with_side_sprite(mut self, sprite: SpriteKind) -> Self {
95        for (_, block) in self.side.iter_mut() {
96            *block = block.with_sprite(sprite);
97        }
98        self
99    }
100
101    pub fn with_side(mut self, new_block: Block) -> Self {
102        for (_, block) in self.side.iter_mut() {
103            *block = new_block;
104        }
105        self
106    }
107
108    pub fn with_side_dir(mut self, dir: Dir2, sprite: SpriteKind) -> Self {
109        self.side[dir] = self.side[dir].with_sprite(sprite);
110        self
111    }
112
113    pub fn with_side_axis(self, axis: Dir2, sprite: SpriteKind) -> Self {
114        self.with_side_dir(axis, sprite)
115            .with_side_dir(-axis, sprite)
116    }
117
118    pub fn with_corner_sprite(mut self, sprite: SpriteKind) -> Self {
119        for (_, block) in self.corner.iter_mut() {
120            *block = block.with_sprite(sprite);
121        }
122        self
123    }
124
125    /// The corner selected is `Dir::diagonal()`.
126    pub fn with_corner_dir(mut self, dir: Dir2, block: Block) -> Self {
127        self.corner[dir] = block;
128        self
129    }
130
131    /// The corner selected is `Dir::diagonal()`.
132    pub fn with_corner_sprite_dir(mut self, dir: Dir2, sprite: SpriteKind) -> Self {
133        self.corner[dir] = self.corner[dir].with_sprite(sprite);
134        self
135    }
136
137    pub fn with_corner_side(self, axis: Dir2, sprite: Block) -> Self {
138        self.with_corner_dir(axis, sprite)
139            .with_corner_dir(axis.rotated_ccw(), sprite)
140    }
141
142    pub fn with_corner_sprite_side(self, axis: Dir2, sprite: SpriteKind) -> Self {
143        self.with_corner_sprite_dir(axis, sprite)
144            .with_corner_sprite_dir(axis.rotated_ccw(), sprite)
145    }
146
147    pub fn with_rotation(mut self, dir: Dir2) -> Self {
148        self.rotation = dir;
149        self
150    }
151
152    pub fn bounds(&self) -> Aabr<i32> { self.bounds }
153
154    pub fn size(&self) -> Extent2<i32> { self.bounds.size() + 1 }
155
156    pub fn center(&self) -> Block { self.center }
157
158    pub fn side(&self, dir: Dir2) -> Block { self.side[dir.relative_to(self.rotation)] }
159
160    pub fn corner(&self, dir: Dir2) -> Block { self.corner[dir.relative_to(self.rotation)] }
161}
162
163fn single_block(painter: &Painter, pos: Vec3<i32>, block: Block) {
164    painter
165        .aabb(Aabb {
166            min: pos,
167            max: pos + 1,
168        })
169        .fill(Fill::Sprite(block))
170}
171
172/// Only applies changes if the block can have the attributes `Ori` and
173/// `MirrorX`.
174fn ori_mirror(mut block: Block, dir: Dir2, x: bool, y: bool) -> Block {
175    let dir_res = block.get_attr::<Ori>().map(|old_ori| {
176        let (old_dir, offset) =
177            Dir2::from_sprite_ori(old_ori.0).expect("We got this from the Ori attr");
178        let new_dir = dir.relative_to(old_dir);
179        Ori(new_dir.sprite_ori() + offset)
180    });
181    let mirror_x_res = block
182        .get_attr::<MirrorX>()
183        .map(|old_x| MirrorX(old_x.0 ^ x));
184
185    if let (Ok(o), Ok(x)) = (dir_res, mirror_x_res) {
186        if y {
187            // flip
188            block
189                .set_attr(Ori((o.0 + 4) % 8))
190                .expect("We read the attr");
191            block.set_attr(MirrorX(!x.0)).expect("We read the attr");
192        } else {
193            block.set_attr(o).expect("We read the attr");
194            block.set_attr(x).expect("We read the attr");
195        }
196    }
197
198    block
199}
200
201pub trait PainterSpriteExt {
202    fn lanternpost_wood(&self, pos: Vec3<i32>, dir: Dir2);
203
204    fn bed(
205        &self,
206        pos: Vec3<i32>,
207        dir: Dir2,
208        head: SpriteKind,
209        middle: SpriteKind,
210        tail: SpriteKind,
211    ) -> Aabr<i32> {
212        let bed = Tileable2::two_by(3, pos, dir)
213            .with_corner_sprite_side(Dir2::Y, head)
214            .with_corner_sprite_side(Dir2::NegY, tail)
215            .with_side_sprite(middle);
216        self.tileable2(&bed);
217
218        bed.bounds()
219    }
220
221    fn bed_wood_woodland(&self, pos: Vec3<i32>, dir: Dir2) -> Aabr<i32> {
222        self.bed(
223            pos,
224            dir,
225            SpriteKind::BedWoodWoodlandHead,
226            SpriteKind::BedWoodWoodlandMiddle,
227            SpriteKind::BedWoodWoodlandTail,
228        )
229    }
230
231    fn bed_desert(&self, pos: Vec3<i32>, dir: Dir2) -> Aabr<i32> {
232        self.bed(
233            pos,
234            dir,
235            SpriteKind::BedDesertHead,
236            SpriteKind::BedDesertMiddle,
237            SpriteKind::BedDesertTail,
238        )
239    }
240
241    fn bed_cliff(&self, pos: Vec3<i32>, dir: Dir2) -> Aabr<i32> {
242        self.bed(
243            pos,
244            dir,
245            SpriteKind::BedCliffHead,
246            SpriteKind::BedCliffMiddle,
247            SpriteKind::BedCliffTail,
248        )
249    }
250
251    fn bed_savannah(&self, pos: Vec3<i32>, dir: Dir2) -> Aabr<i32> {
252        self.bed(
253            pos,
254            dir,
255            SpriteKind::BedSavannahHead,
256            SpriteKind::BedSavannahMiddle,
257            SpriteKind::BedSavannahTail,
258        )
259    }
260
261    fn bed_coastal(&self, pos: Vec3<i32>, dir: Dir2) -> Aabr<i32> {
262        self.bed(
263            pos,
264            dir,
265            SpriteKind::BedCoastalHead,
266            SpriteKind::BedCoastalMiddle,
267            SpriteKind::BedCoastalTail,
268        )
269    }
270
271    fn table_wood_fancy_woodland(&self, pos: Vec3<i32>, axis: Dir2) -> Aabr<i32> {
272        let table = Tileable2::two_by(3, pos, axis)
273            .with_side_sprite(SpriteKind::TableWoodFancyWoodlandBody)
274            .with_corner_sprite(SpriteKind::TableWoodFancyWoodlandCorner);
275
276        self.tileable2(&table);
277
278        table.bounds()
279    }
280
281    /// Bounds are inclusive
282    fn chairs_around(&self, chair: SpriteKind, spacing: usize, bounds: Aabr<i32>, alt: i32);
283
284    /// Tileable in 1 dimension.
285    ///
286    /// Does nothing if size is less than 2.
287    fn tileable1(
288        &self,
289        pos: Vec3<i32>,
290        dir: Dir2,
291        size: i32,
292        middle_sprite: SpriteKind,
293        side_sprite: SpriteKind,
294    );
295
296    /// This will be placed with the "right side" looking forward at `pos`.
297    fn mirrored2(&self, pos: Vec3<i32>, dir: Dir2, sprite: SpriteKind) {
298        self.tileable1(pos, dir, 2, SpriteKind::Empty, sprite);
299    }
300
301    /// Tileable in 2 dimensions.
302    ///
303    /// Does nothing if the size is less than 2 in any axis.
304    fn tileable2(&self, tileable: &Tileable2);
305}
306
307impl PainterSpriteExt for Painter {
308    fn lanternpost_wood(&self, pos: Vec3<i32>, dir: Dir2) {
309        let sprite_ori = dir.sprite_ori();
310        self.rotated_sprite(pos, SpriteKind::LanternpostWoodBase, sprite_ori);
311        self.column(pos.xy(), pos.z + 1..pos.z + 4).clear();
312        self.rotated_sprite(
313            pos + Vec3::unit_z() * 3,
314            SpriteKind::LanternpostWoodUpper,
315            sprite_ori,
316        );
317        self.rotated_sprite(
318            pos + dir.to_vec3() + Vec3::unit_z() * 3,
319            SpriteKind::LanternpostWoodLantern,
320            sprite_ori,
321        );
322    }
323
324    fn chairs_around(&self, chair: SpriteKind, spacing: usize, bounds: Aabr<i32>, alt: i32) {
325        for dir in Dir2::iter() {
326            let s = dir.orthogonal().select(bounds.size());
327            // We skip small sides
328            if s <= 2 && dir.select(bounds.size()) > s {
329                continue;
330            }
331
332            let min = dir.orthogonal().select(bounds.min);
333            let max = dir.orthogonal().select(bounds.max);
334            let center = dir.orthogonal().select(bounds.center());
335            for i in (min..=center)
336                .step_by(spacing + 1)
337                .chain((center..=max).rev().step_by(spacing + 1))
338            {
339                let p = dir.select_aabr_with(bounds, i) + dir.to_vec2();
340                single_block(
341                    self,
342                    p.with_z(alt),
343                    Block::air(chair)
344                        .with_attr(Ori(dir.opposite().sprite_ori()))
345                        .expect("Chairs should have the Ori attribute"),
346                );
347            }
348        }
349    }
350
351    fn tileable1(
352        &self,
353        pos: Vec3<i32>,
354        dir: Dir2,
355        size: i32,
356        middle_sprite: SpriteKind,
357        side_sprite: SpriteKind,
358    ) {
359        if size < 2 {
360            return;
361        }
362        let orth = dir.rotated_ccw();
363
364        let extent_min = (size - 1) / 2;
365        let extent_max = size / 2;
366
367        let aabr = Aabr {
368            min: pos.xy() - orth.to_vec2() * extent_min,
369            max: pos.xy() + orth.to_vec2() * extent_max,
370        }
371        .made_valid();
372
373        if size > 2 {
374            self.aabb(Aabb {
375                min: (aabr.min + orth.abs().to_vec2()).with_z(pos.z),
376                max: (aabr.max - orth.abs().to_vec2()).with_z(pos.z) + 1,
377            })
378            .fill(Fill::Sprite(ori_mirror(
379                Block::air(middle_sprite),
380                dir,
381                false,
382                false,
383            )));
384        }
385
386        single_block(
387            self,
388            aabr.min.with_z(pos.z),
389            ori_mirror(Block::air(side_sprite), dir, false, orth.is_negative()),
390        );
391
392        single_block(
393            self,
394            aabr.max.with_z(pos.z),
395            ori_mirror(Block::air(side_sprite), dir, false, orth.is_positive()),
396        );
397    }
398
399    fn tileable2(&self, tileable: &Tileable2) {
400        let alt = tileable.alt;
401        let bounds = tileable.bounds;
402        let size = tileable.size();
403        if size.reduce_min() < 2 {
404            // Need at least 2 in each axis to be able to tile.
405            return;
406        }
407
408        if size.w > 2 && size.h > 2 {
409            self.aabb(Aabb {
410                min: (bounds.min + 1).with_z(alt),
411                max: (bounds.max - 1).with_z(alt) + 1,
412            })
413            .fill(Fill::Sprite(ori_mirror(
414                tileable.center(),
415                tileable.rotation,
416                false,
417                false,
418            )));
419        }
420
421        if size.h > 2 {
422            let rot = Dir2::NegY;
423            self.aabb(Aabb {
424                min: Vec3::new(bounds.min.x, bounds.min.y + 1, alt),
425                max: Vec3::new(bounds.min.x, bounds.max.y - 1, alt) + 1,
426            })
427            .fill(Fill::Sprite(ori_mirror(
428                tileable.side(Dir2::NegX),
429                rot,
430                false,
431                false,
432            )));
433
434            self.aabb(Aabb {
435                min: Vec3::new(bounds.max.x, bounds.min.y + 1, alt),
436                max: Vec3::new(bounds.max.x, bounds.max.y - 1, alt) + 1,
437            })
438            .fill(Fill::Sprite(ori_mirror(
439                tileable.side(Dir2::X),
440                rot,
441                false,
442                // Mirror is applied before rotation so we mirror Y
443                true,
444            )));
445        }
446
447        if size.w > 2 {
448            let rot = Dir2::X;
449            self.aabb(Aabb {
450                min: Vec3::new(bounds.min.x + 1, bounds.min.y, alt),
451                max: Vec3::new(bounds.max.x - 1, bounds.min.y, alt) + 1,
452            })
453            .fill(Fill::Sprite(ori_mirror(
454                tileable.side(Dir2::NegY),
455                rot,
456                false,
457                false,
458            )));
459
460            self.aabb(Aabb {
461                min: Vec3::new(bounds.min.x + 1, bounds.max.y, alt),
462                max: Vec3::new(bounds.max.x - 1, bounds.max.y, alt) + 1,
463            })
464            .fill(Fill::Sprite(ori_mirror(
465                tileable.side(Dir2::Y),
466                rot,
467                false,
468                true,
469            )));
470        }
471
472        let rot = tileable.rotation;
473        let orth = rot.rotated_ccw();
474        single_block(
475            self,
476            bounds.min.with_z(alt),
477            ori_mirror(
478                tileable.corner(Dir2::NegX),
479                rot,
480                rot.is_negative(),
481                orth.is_negative(),
482            ),
483        );
484        single_block(
485            self,
486            Vec3::new(bounds.max.x, bounds.min.y, alt),
487            ori_mirror(
488                tileable.corner(Dir2::NegY),
489                rot,
490                orth.is_positive(),
491                rot.is_negative(),
492            ),
493        );
494        single_block(
495            self,
496            Vec3::new(bounds.min.x, bounds.max.y, alt),
497            ori_mirror(
498                tileable.corner(Dir2::Y),
499                rot,
500                orth.is_negative(),
501                rot.is_positive(),
502            ),
503        );
504        single_block(
505            self,
506            bounds.max.with_z(alt),
507            ori_mirror(
508                tileable.corner(Dir2::X),
509                rot,
510                rot.is_positive(),
511                orth.is_positive(),
512            ),
513        );
514    }
515}