fyrox_impl/scene/tilemap/
effect.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Rendering a tile map is often not as simple as rendering each tile in its
22//! proper place. Sometimes tiles need to be made invisible or replaced by other
23//! tiles. Sometimes additional rendering needs to happen along with some tile, such
24//! as for creating highlights or other special effects.
25//!
26//! For this purpose, a tile map may optionally have a list of [`TileMapEffect`]
27//! references which can intervene in the rendering process of the tile map.
28
29use crate::{
30    core::{
31        algebra::{Matrix4, Vector2, Vector3, Vector4},
32        parking_lot::Mutex,
33    },
34    scene::mesh::vertex::StaticVertex,
35};
36use fxhash::{FxHashMap, FxHashSet};
37use std::{fmt::Debug, sync::Arc};
38
39use super::*;
40
41/// A reference to [`TileMapEffect`]. A TileMap keeps some of these if it needs to be
42/// rendered specially.
43pub type TileMapEffectRef = Arc<Mutex<dyn TileMapEffect>>;
44
45/// A trait for objects that can perform specialized rendering for a tile map by
46/// adding them to [`TileMap::before_effects`] or [`TileMap::after_effects`],
47/// depending on whether the effect should render before the tile map renders
48/// or after the tile map renders.
49pub trait TileMapEffect: Send + Debug {
50    /// Use the given context to render the special effect for the [`TileMap`].
51    fn render_special_tiles(&self, context: &mut TileMapRenderContext);
52}
53
54/// Renders a rectangle of the given material at the given position in the tile map.
55#[derive(Debug)]
56pub struct TileCursorEffect {
57    /// The position of the cursor in the tile map.
58    pub position: Option<Vector2<i32>>,
59    /// The material to use to render the cursor.
60    pub material: Option<MaterialResource>,
61}
62
63impl TileMapEffect for TileCursorEffect {
64    fn render_special_tiles(&self, context: &mut TileMapRenderContext) {
65        if let Some(material) = self.material.as_ref() {
66            if let Some(position) = self.position {
67                push_cursor(position, material, context);
68            }
69        }
70    }
71}
72
73/// Renders borders of the given material around the given positions in the tile map.
74#[derive(Debug)]
75pub struct TileSelectionEffect {
76    /// This vector is added to the positions before rendering.
77    pub offset: Option<Vector2<i32>>,
78    /// The positions at which to draw the borders
79    pub positions: FxHashSet<Vector2<i32>>,
80    /// The size of the border
81    pub thickness: f32,
82    /// The material to use to render the border
83    pub material: Option<MaterialResource>,
84}
85
86impl TileMapEffect for TileSelectionEffect {
87    fn render_special_tiles(&self, context: &mut TileMapRenderContext) {
88        if let (Some(material), Some(offset)) = (self.material.as_ref(), self.offset) {
89            for &position in self.positions.iter() {
90                let position = position + offset;
91                push_highlight(position, material, self.thickness, context);
92            }
93        }
94    }
95}
96
97/// Sets the tiles at the given positions to invisible.
98#[derive(Debug)]
99pub struct TileEraseEffect {
100    /// The positions of the tiles to hide
101    pub positions: FxHashSet<Vector2<i32>>,
102}
103
104impl TileMapEffect for TileEraseEffect {
105    fn render_special_tiles(&self, context: &mut TileMapRenderContext) {
106        for &position in self.positions.iter() {
107            context.set_tile_visible(position, false);
108        }
109    }
110}
111
112/// Draws the given tiles with the given offset, and sets the drawn tile positions
113/// to invisible so that no other tiles will be drawn there.
114#[derive(Debug)]
115pub struct TileOverlayEffect {
116    /// True if the tiles are to be drawn. If false, then this effect does nothing.
117    pub active: bool,
118    /// Vector to be added to the positions of the tiles before rendering
119    pub offset: Vector2<i32>,
120    /// The tiles to render
121    pub tiles: FxHashMap<Vector2<i32>, TileDefinitionHandle>,
122}
123
124impl TileMapEffect for TileOverlayEffect {
125    fn render_special_tiles(&self, context: &mut TileMapRenderContext) {
126        if !self.active {
127            return;
128        }
129        for (&position, &handle) in self.tiles.iter() {
130            let position = position + self.offset;
131            if context.is_tile_visible(position) {
132                context.draw_tile(position, handle);
133                context.set_tile_visible(position, false);
134            }
135        }
136    }
137}
138
139/// Uses the given tile update to render the replacement tiles and make
140/// the erased tiles invisible.
141#[derive(Debug)]
142pub struct TileUpdateEffect {
143    /// True if the tiles are to be drawn. If false, then this effect does nothing.
144    pub active: bool,
145    /// The update data to render in the tile map
146    pub update: TransTilesUpdate,
147}
148
149impl TileMapEffect for TileUpdateEffect {
150    fn render_special_tiles(&self, context: &mut TileMapRenderContext) {
151        if !self.active {
152            return;
153        }
154        for (&position, value) in self.update.iter() {
155            if context.is_tile_visible(position) {
156                context.set_tile_visible(position, false);
157                if let Some((transform, handle)) = *value {
158                    let handle = context
159                        .tile_set
160                        .get_transformed_version(transform, handle)
161                        .unwrap_or(handle);
162                    context.draw_tile(position, handle);
163                }
164            }
165        }
166    }
167}
168
169fn make_highlight_vertex(transform: &Matrix4<f32>, position: Vector2<f32>) -> StaticVertex {
170    StaticVertex {
171        position: transform
172            .transform_point(&position.to_homogeneous().into())
173            .coords,
174        tex_coord: Vector2::default(),
175        normal: Vector3::new(0.0, 0.0, 1.0),
176        tangent: Vector4::new(0.0, 1.0, 0.0, 1.0),
177    }
178}
179
180fn push_highlight(
181    position: Vector2<i32>,
182    material: &MaterialResource,
183    thickness: f32,
184    ctx: &mut TileMapRenderContext,
185) {
186    let transform = ctx.transform();
187    let position = position.cast::<f32>();
188    let t = thickness;
189    let vertices = [
190        (0.0, 1.0),
191        (1.0, 1.0),
192        (1.0, 0.0),
193        (0.0, 0.0),
194        (t, 1.0 - t),
195        (1.0 - t, 1.0 - t),
196        (1.0 - t, t),
197        (t, t),
198    ]
199    .map(|(x, y)| Vector2::new(x, y))
200    .map(|p| make_highlight_vertex(transform, position + p));
201
202    let triangles = [
203        [0, 4, 5],
204        [0, 1, 5],
205        [1, 5, 6],
206        [1, 2, 6],
207        [2, 6, 7],
208        [2, 3, 7],
209        [3, 7, 4],
210        [3, 0, 4],
211    ]
212    .map(TriangleDefinition);
213
214    let sort_index = ctx
215        .context
216        .calculate_sorting_index(ctx.position())
217        .saturating_sub(1);
218
219    ctx.context.storage.push_triangles(
220        StaticVertex::layout(),
221        material,
222        RenderPath::Forward,
223        sort_index,
224        ctx.tile_map_handle(),
225        &mut move |mut vertex_buffer, mut triangle_buffer| {
226            let start_vertex_index = vertex_buffer.vertex_count();
227
228            vertex_buffer.push_vertices(&vertices).unwrap();
229
230            triangle_buffer
231                .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
232        },
233    );
234}
235
236fn push_cursor(
237    position: Vector2<i32>,
238    material: &MaterialResource,
239    ctx: &mut TileMapRenderContext,
240) {
241    let transform = ctx.transform();
242    let position = position.cast::<f32>();
243    let vertices = [(0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
244        .map(|(x, y)| Vector2::new(x, y))
245        .map(|p| make_highlight_vertex(transform, position + p));
246
247    let triangles = [[0, 1, 2], [0, 2, 3]].map(TriangleDefinition);
248
249    let sort_index = ctx.context.calculate_sorting_index(ctx.position());
250
251    ctx.context.storage.push_triangles(
252        StaticVertex::layout(),
253        material,
254        RenderPath::Forward,
255        sort_index,
256        ctx.tile_map_handle(),
257        &mut move |mut vertex_buffer, mut triangle_buffer| {
258            let start_vertex_index = vertex_buffer.vertex_count();
259
260            vertex_buffer.push_vertices(&vertices).unwrap();
261
262            triangle_buffer
263                .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
264        },
265    );
266}