veloren_world/site2/util/
sprites.rs

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