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
163pub fn 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    /// Bounds are inclusive
285    fn benches_around(
286        &self,
287        bench: SpriteKind,
288        spacing: usize,
289        offset: i32,
290        bounds: Aabr<i32>,
291        alt: i32,
292    );
293
294    /// Tileable in 1 dimension.
295    ///
296    /// Does nothing if size is less than 2.
297    fn tileable1(
298        &self,
299        pos: Vec3<i32>,
300        dir: Dir2,
301        size: i32,
302        middle_sprite: SpriteKind,
303        side_sprite: SpriteKind,
304    );
305
306    /// This will be placed with the "right side" looking forward at `pos`.
307    fn mirrored2(&self, pos: Vec3<i32>, dir: Dir2, sprite: SpriteKind) {
308        self.tileable1(pos, dir, 2, SpriteKind::Empty, sprite);
309    }
310
311    /// Tileable in 2 dimensions.
312    ///
313    /// Does nothing if the size is less than 2 in any axis.
314    fn tileable2(&self, tileable: &Tileable2);
315}
316
317impl PainterSpriteExt for Painter {
318    fn lanternpost_wood(&self, pos: Vec3<i32>, dir: Dir2) {
319        let sprite_ori = dir.sprite_ori();
320        self.rotated_sprite(pos, SpriteKind::LanternpostWoodBase, sprite_ori);
321        self.column(pos.xy(), pos.z + 1..pos.z + 4).clear();
322        self.rotated_sprite(
323            pos + Vec3::unit_z() * 3,
324            SpriteKind::LanternpostWoodUpper,
325            sprite_ori,
326        );
327        self.rotated_sprite(
328            pos + dir.to_vec3() + Vec3::unit_z() * 3,
329            SpriteKind::LanternpostWoodLantern,
330            sprite_ori,
331        );
332    }
333
334    fn chairs_around(&self, chair: SpriteKind, spacing: usize, bounds: Aabr<i32>, alt: i32) {
335        for dir in Dir2::iter() {
336            let s = dir.orthogonal().select(bounds.size());
337            // We skip small sides
338            if s <= 2 && dir.select(bounds.size()) > s {
339                continue;
340            }
341
342            let min = dir.orthogonal().select(bounds.min);
343            let max = dir.orthogonal().select(bounds.max);
344            let center = dir.orthogonal().select(bounds.center());
345            for i in (min..=center)
346                .step_by(spacing + 1)
347                .chain((center..=max).rev().step_by(spacing + 1))
348            {
349                let p = dir.select_aabr_with(bounds, i) + dir.to_vec2();
350                single_block(
351                    self,
352                    p.with_z(alt),
353                    Block::air(chair)
354                        .with_attr(Ori(dir.opposite().sprite_ori()))
355                        .expect("Chairs should have the Ori attribute"),
356                );
357            }
358        }
359    }
360
361    fn benches_around(
362        &self,
363        bench: SpriteKind,
364        spacing: usize,
365        offset: i32,
366        bounds: Aabr<i32>,
367        alt: i32,
368    ) {
369        for dir in Dir2::iter() {
370            let s = dir.orthogonal().select(bounds.size());
371            // We skip small sides
372            if s <= 2 && dir.select(bounds.size()) > s {
373                continue;
374            }
375
376            let min = dir.orthogonal().select(bounds.min);
377            let max = dir.orthogonal().select(bounds.max);
378            let center = dir.orthogonal().select(bounds.center());
379            for i in (min + offset..=center)
380                .step_by(spacing + 2)
381                .chain((center..=max - offset).rev().step_by(spacing + 2))
382            {
383                let p = dir.select_aabr_with(bounds, i) + dir.to_vec2();
384                self.mirrored2(p.with_z(alt), dir, bench);
385            }
386        }
387    }
388
389    fn tileable1(
390        &self,
391        pos: Vec3<i32>,
392        dir: Dir2,
393        size: i32,
394        middle_sprite: SpriteKind,
395        side_sprite: SpriteKind,
396    ) {
397        if size < 2 {
398            return;
399        }
400        let orth = dir.rotated_ccw();
401
402        let extent_min = (size - 1) / 2;
403        let extent_max = size / 2;
404
405        let aabr = Aabr {
406            min: pos.xy() - orth.to_vec2() * extent_min,
407            max: pos.xy() + orth.to_vec2() * extent_max,
408        }
409        .made_valid();
410
411        if size > 2 {
412            self.aabb(Aabb {
413                min: (aabr.min + orth.abs().to_vec2()).with_z(pos.z),
414                max: (aabr.max - orth.abs().to_vec2()).with_z(pos.z) + 1,
415            })
416            .fill(Fill::Sprite(ori_mirror(
417                Block::air(middle_sprite),
418                dir,
419                false,
420                false,
421            )));
422        }
423
424        single_block(
425            self,
426            aabr.min.with_z(pos.z),
427            ori_mirror(Block::air(side_sprite), dir, false, orth.is_negative()),
428        );
429
430        single_block(
431            self,
432            aabr.max.with_z(pos.z),
433            ori_mirror(Block::air(side_sprite), dir, false, orth.is_positive()),
434        );
435    }
436
437    fn tileable2(&self, tileable: &Tileable2) {
438        let alt = tileable.alt;
439        let bounds = tileable.bounds;
440        let size = tileable.size();
441        if size.reduce_min() < 2 {
442            // Need at least 2 in each axis to be able to tile.
443            return;
444        }
445
446        if size.w > 2 && size.h > 2 {
447            self.aabb(Aabb {
448                min: (bounds.min + 1).with_z(alt),
449                max: (bounds.max - 1).with_z(alt) + 1,
450            })
451            .fill(Fill::Sprite(ori_mirror(
452                tileable.center(),
453                tileable.rotation,
454                false,
455                false,
456            )));
457        }
458
459        if size.h > 2 {
460            let rot = Dir2::NegY;
461            self.aabb(Aabb {
462                min: Vec3::new(bounds.min.x, bounds.min.y + 1, alt),
463                max: Vec3::new(bounds.min.x, bounds.max.y - 1, alt) + 1,
464            })
465            .fill(Fill::Sprite(ori_mirror(
466                tileable.side(Dir2::NegX),
467                rot,
468                false,
469                false,
470            )));
471
472            self.aabb(Aabb {
473                min: Vec3::new(bounds.max.x, bounds.min.y + 1, alt),
474                max: Vec3::new(bounds.max.x, bounds.max.y - 1, alt) + 1,
475            })
476            .fill(Fill::Sprite(ori_mirror(
477                tileable.side(Dir2::X),
478                rot,
479                false,
480                // Mirror is applied before rotation so we mirror Y
481                true,
482            )));
483        }
484
485        if size.w > 2 {
486            let rot = Dir2::X;
487            self.aabb(Aabb {
488                min: Vec3::new(bounds.min.x + 1, bounds.min.y, alt),
489                max: Vec3::new(bounds.max.x - 1, bounds.min.y, alt) + 1,
490            })
491            .fill(Fill::Sprite(ori_mirror(
492                tileable.side(Dir2::NegY),
493                rot,
494                false,
495                false,
496            )));
497
498            self.aabb(Aabb {
499                min: Vec3::new(bounds.min.x + 1, bounds.max.y, alt),
500                max: Vec3::new(bounds.max.x - 1, bounds.max.y, alt) + 1,
501            })
502            .fill(Fill::Sprite(ori_mirror(
503                tileable.side(Dir2::Y),
504                rot,
505                false,
506                true,
507            )));
508        }
509
510        let rot = tileable.rotation;
511        let orth = rot.rotated_ccw();
512        single_block(
513            self,
514            bounds.min.with_z(alt),
515            ori_mirror(
516                tileable.corner(Dir2::NegX),
517                rot,
518                rot.is_negative(),
519                orth.is_negative(),
520            ),
521        );
522        single_block(
523            self,
524            Vec3::new(bounds.max.x, bounds.min.y, alt),
525            ori_mirror(
526                tileable.corner(Dir2::NegY),
527                rot,
528                orth.is_positive(),
529                rot.is_negative(),
530            ),
531        );
532        single_block(
533            self,
534            Vec3::new(bounds.min.x, bounds.max.y, alt),
535            ori_mirror(
536                tileable.corner(Dir2::Y),
537                rot,
538                orth.is_negative(),
539                rot.is_positive(),
540            ),
541        );
542        single_block(
543            self,
544            bounds.max.with_z(alt),
545            ori_mirror(
546                tileable.corner(Dir2::X),
547                rot,
548                rot.is_positive(),
549                orth.is_positive(),
550            ),
551        );
552    }
553}