fyrox_impl/scene/tilemap/
property.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//! A property layer allows a tile set to store arbitrary values along with each tile
22//! in a tile set. A tile may have an integer, a float, or a string that the game may
23//! use to recognize special properties of the tile. These properties can be accessed
24//! using either the property name or the property UUID.
25//!
26//! See [`TileSetPropertyValue`] for the possible value types.
27//!
28//! A property layer may also have a list of pre-defined values which can be named
29//! to specify special meanings to particular values for the property.
30//!
31//! In addition to property layers, there are also collider layers which work much
32//! like property layers, but instead of storing arbitrary data, a collider layer
33//! associates each tile with a shape made from triangles. Each collider layer
34//! has a color that will be used to render the shape in the tile set editor,
35//! so the user can see each tile's shape and the shepe's layer at a glance.
36
37use crate::core::{
38    algebra::Vector2, color::Color, num_traits::Euclid, reflect::prelude::*,
39    type_traits::prelude::*, visitor::prelude::*, ImmutableString,
40};
41use std::fmt::{Debug, Display, Formatter};
42
43use super::*;
44use tileset::*;
45
46/// Since a tile map may require multiple colliders to represent the diverse ways that physics objects may interact with the tiles,
47/// tile set data must allow each tile to include multiple values for its collider information.
48/// These multiple collider values are associated with their collider objects by a UUID and a name.
49#[derive(Clone, Default, Debug, Reflect, Visit)]
50pub struct TileSetColliderLayer {
51    /// The id number that identifies the collider
52    pub uuid: Uuid,
53    /// The name of the collider
54    pub name: ImmutableString,
55    /// The color that will be used to represent the collider in the editor.
56    pub color: Color,
57}
58
59/// In order to allow tile properties to be easily edited, properties need to have consistent names and data types
60/// across all tiles in a tile set. A tile property layer represents the association between a property name
61/// and its data type, along with other information.
62#[derive(Clone, Default, Debug, Reflect, Visit)]
63pub struct TileSetPropertyLayer {
64    /// The id number that identifies this property
65    pub uuid: Uuid,
66    /// The name of the property that will be shown in the editor and can be used access the value.
67    pub name: ImmutableString,
68    /// The data type
69    pub prop_type: TileSetPropertyType,
70    /// Pre-defined named values.
71    pub named_values: Vec<NamedValue>,
72}
73
74/// A value with an associated name. Often certain property values will have special meanings
75/// for the game that is using the values, so it is useful to be able to label those values
76/// so their special meaning can be visible in the editor.
77#[derive(Clone, Default, Debug, Reflect, Visit)]
78pub struct NamedValue {
79    /// The label associated with the value.
80    pub name: String,
81    /// The special value that is being named.
82    pub value: NamableValue,
83    /// The color to represent this value in the editor
84    pub color: Color,
85}
86
87/// Named values can be either an integer or a float.
88/// It would make little sense to name a string or a nine slice.
89#[derive(Copy, Clone, Debug, Reflect, Visit, PartialEq)]
90pub enum NamableValue {
91    /// A value for an element of a nine-slice
92    I8(i8),
93    /// An integer value
94    I32(i32),
95    /// A float value
96    F32(f32),
97}
98
99impl Display for NamableValue {
100    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101        match self {
102            Self::I8(value) => write!(f, "{value}"),
103            Self::I32(value) => write!(f, "{value}"),
104            Self::F32(value) => write!(f, "{value}"),
105        }
106    }
107}
108
109impl Default for NamableValue {
110    fn default() -> Self {
111        Self::I32(0)
112    }
113}
114
115impl NamableValue {
116    /// True if this value corresponds to the given property value.
117    pub fn matches(&self, other: &TileSetPropertyOptionValue) -> bool {
118        match (self, other) {
119            (Self::I32(x), TileSetPropertyOptionValue::I32(Some(y))) => *x == *y,
120            (Self::F32(x), TileSetPropertyOptionValue::F32(Some(y))) => *x == *y,
121            _ => false,
122        }
123    }
124}
125
126impl TileSetPropertyLayer {
127    /// Find the name associated with the given value.
128    pub fn value_to_name(&self, value: NamableValue) -> String {
129        self.named_values
130            .iter()
131            .find(|v| v.value == value)
132            .map(|v| v.name.clone())
133            .unwrap_or_else(|| format!("{value}"))
134    }
135    /// Find the color associated with the given value.
136    pub fn value_to_color(&self, value: NamableValue) -> Option<Color> {
137        self.named_values
138            .iter()
139            .find(|v| v.value == value)
140            .map(|v| v.color)
141    }
142    /// Return the index of the named value that matches the given value, if one exits.
143    pub fn find_value_index_from_property(
144        &self,
145        value: &TileSetPropertyOptionValue,
146    ) -> Option<usize> {
147        self.named_values
148            .iter()
149            .position(|v| v.value.matches(value))
150    }
151    /// Return the index of the named value that matches the given value, if one exits.
152    pub fn find_value_index(&self, value: NamableValue) -> Option<usize> {
153        self.named_values.iter().position(|v| v.value == value)
154    }
155    /// Return the appropriate highlight color for the tile at the given position when the
156    /// tile has the given property value and the user has selected the given element value.
157    /// If the value does not have a specified highlight color within this layer, then
158    /// the value is compared against the element value and it is given a highlight color
159    /// to acknowledge that the value matches the element value.
160    pub fn highlight_color(
161        &self,
162        position: Vector2<usize>,
163        value: &TileSetPropertyValue,
164        element_value: &TileSetPropertyValueElement,
165    ) -> Option<Color> {
166        use TileSetPropertyValue as PropValue;
167        use TileSetPropertyValueElement as Element;
168        if position != Vector2::new(1, 1) && !matches!(value, PropValue::NineSlice(_)) {
169            return None;
170        }
171        match (value, element_value) {
172            (&PropValue::I32(v0), &Element::I32(v1)) => {
173                self.value_to_color(NamableValue::I32(v0)).or({
174                    if v0 == v1 {
175                        Some(ELEMENT_MATCH_HIGHLIGHT_COLOR)
176                    } else {
177                        None
178                    }
179                })
180            }
181            (&PropValue::F32(v0), &Element::F32(v1)) => {
182                self.value_to_color(NamableValue::F32(v0)).or({
183                    if v0 == v1 {
184                        Some(ELEMENT_MATCH_HIGHLIGHT_COLOR)
185                    } else {
186                        None
187                    }
188                })
189            }
190            (PropValue::String(v0), Element::String(v1)) => {
191                if v0 == v1 {
192                    Some(ELEMENT_MATCH_HIGHLIGHT_COLOR)
193                } else {
194                    None
195                }
196            }
197            (PropValue::NineSlice(v0), &Element::I8(v1)) => {
198                let v = v0.value_at(position);
199                self.value_to_color(NamableValue::I8(v)).or({
200                    if v == v1 {
201                        Some(ELEMENT_MATCH_HIGHLIGHT_COLOR)
202                    } else {
203                        None
204                    }
205                })
206            }
207            _ => None,
208        }
209    }
210}
211
212/// Each tile property needs to store a value to indicate what type of data will
213/// be stored in that property, as the data type will affect how the editor
214/// allows users to edit the property on each tile.
215#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Reflect, Visit)]
216pub enum TileSetPropertyType {
217    /// The type for integer properties.
218    #[default]
219    I32,
220    /// The type for float properties.
221    F32,
222    /// The type for string properties.
223    String,
224    /// Nine-slice properties allow a tile to have nine separate values,
225    /// one value for each of its corners, each of its edges, and its center.
226    NineSlice,
227}
228
229impl TileSetPropertyType {
230    /// The default value for properties of the given type.
231    pub fn default_value(&self) -> TileSetPropertyValue {
232        use TileSetPropertyType as PropType;
233        use TileSetPropertyValue as PropValue;
234        match self {
235            PropType::I32 => PropValue::I32(0),
236            PropType::F32 => PropValue::F32(0.0),
237            PropType::String => PropValue::String(ImmutableString::default()),
238            PropType::NineSlice => PropValue::NineSlice(NineI8::default()),
239        }
240    }
241    /// The none value when no value is available.
242    pub fn default_option_value(&self) -> TileSetPropertyOptionValue {
243        use TileSetPropertyOptionValue as PropValue;
244        use TileSetPropertyType as PropType;
245        match self {
246            PropType::I32 => PropValue::I32(None),
247            PropType::F32 => PropValue::F32(None),
248            PropType::String => PropValue::String(None),
249            PropType::NineSlice => PropValue::NineSlice([None; 9]),
250        }
251    }
252}
253
254/// The data stored in a tile property.
255#[derive(Clone, Debug, PartialEq, Reflect, Visit)]
256pub enum TileSetPropertyValue {
257    /// Integer property data.
258    I32(i32),
259    /// Float property data.
260    F32(f32),
261    /// String property data.
262    String(ImmutableString),
263    /// Nine-slice properties allow a tile to have nine separate values,
264    /// one value for each of its corners, each of its edges, and its center.
265    NineSlice(NineI8),
266}
267
268/// Storing a slice of 9 values in a tile is critical to some automatic tiling algorithms
269/// that need to be able identify the content of the edges, corners, or center of each tile.
270/// [Wang tiles](https://en.wikipedia.org/wiki/Wang_tile) are an example of this, where each edge of each tile
271/// is assigned a color and tiles are arranged so that whenever two tiles are adjacent the touching edges
272/// are the same color.
273#[derive(Default, Clone, Copy, PartialEq, Reflect)]
274pub struct NineI8(pub [i8; 9]);
275
276impl Visit for NineI8 {
277    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
278        self.0.visit(name, visitor)
279    }
280}
281
282impl Debug for NineI8 {
283    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
284        let [v0, v1, v2, v3, v4, v5, v6, v7, v8] = self.0;
285        write!(f, "NineI8[{v0} {v1} {v2}/{v3} {v4} {v5}/{v6} {v7} {v8}]")
286    }
287}
288
289impl Display for NineI8 {
290    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
291        let [v0, v1, v2, v3, v4, v5, v6, v7, v8] = self.0;
292        write!(f, "[{v0} {v1} {v2}/{v3} {v4} {v5}/{v6} {v7} {v8}]")
293    }
294}
295
296impl From<[i8; 9]> for NineI8 {
297    fn from(value: [i8; 9]) -> Self {
298        NineI8(value)
299    }
300}
301
302impl From<NineI8> for [i8; 9] {
303    fn from(value: NineI8) -> Self {
304        value.0
305    }
306}
307
308impl NineI8 {
309    /// The value at the given position, with (1,1) being the center of the tile.
310    /// (1,2) represents the top edge of the tile.
311    /// (0,1) represents the left edge of the tile.
312    /// (2,1) represents the right edge of the tile.
313    /// (1,0) represents the bottom edge of the tile.
314    /// Other positions represent the four corners of the tile.
315    pub fn value_at(&self, position: Vector2<usize>) -> i8 {
316        let index = TileSetPropertyValue::nine_position_to_index(position);
317        self.0[index]
318    }
319    /// The value at the given position, with (1,1) being the center of the tile.
320    /// (1,2) represents the top edge of the tile.
321    /// (0,1) represents the left edge of the tile.
322    /// (2,1) represents the right edge of the tile.
323    /// (1,0) represents the bottom edge of the tile.
324    /// Other positions represent the four corners of the tile.
325    pub fn value_at_mut(&mut self, position: Vector2<usize>) -> &mut i8 {
326        let index = TileSetPropertyValue::nine_position_to_index(position);
327        &mut self.0[index]
328    }
329    /// Swap the values at two positions in the slice.
330    pub fn swap(&mut self, a: Vector2<usize>, b: Vector2<usize>) {
331        let a_index = TileSetPropertyValue::nine_position_to_index(a);
332        let b_index = TileSetPropertyValue::nine_position_to_index(b);
333        self.0.swap(a_index, b_index);
334    }
335}
336
337/// An element of data stored within a tile's property.
338/// For most value types, the element is the whole of the value,
339/// but a nine-slice value contains nine elements.
340#[derive(Clone, Debug, PartialEq)]
341pub enum TileSetPropertyValueElement {
342    /// Integer property data.
343    I32(i32),
344    /// Float property data.
345    F32(f32),
346    /// String property data.
347    String(ImmutableString),
348    /// Nine-slice property element.
349    I8(i8),
350}
351
352impl Default for TileSetPropertyValue {
353    fn default() -> Self {
354        Self::I32(0)
355    }
356}
357
358impl Default for TileSetPropertyValueElement {
359    fn default() -> Self {
360        Self::I32(0)
361    }
362}
363
364impl OrthoTransform for TileSetPropertyValue {
365    fn x_flipped(self) -> Self {
366        if let Self::NineSlice(mut v) = self {
367            fn pos(x: usize, y: usize) -> Vector2<usize> {
368                Vector2::new(x, y)
369            }
370            v.swap(pos(2, 0), pos(0, 0));
371            v.swap(pos(2, 1), pos(0, 1));
372            v.swap(pos(2, 2), pos(0, 2));
373            Self::NineSlice(v)
374        } else {
375            self
376        }
377    }
378
379    fn rotated(self, amount: i8) -> Self {
380        if let Self::NineSlice(mut v) = self {
381            let amount = amount.rem_euclid(4);
382            nine_rotate(&mut v, amount as usize * 2);
383            Self::NineSlice(v)
384        } else {
385            self
386        }
387    }
388}
389
390const fn nine_index(x: usize, y: usize) -> usize {
391    y * 3 + x
392}
393
394const NINE_ROTATE_LIST: [usize; 8] = [
395    nine_index(0, 0),
396    nine_index(1, 0),
397    nine_index(2, 0),
398    nine_index(2, 1),
399    nine_index(2, 2),
400    nine_index(1, 2),
401    nine_index(0, 2),
402    nine_index(0, 1),
403];
404
405fn nine_rotate(nine: &mut NineI8, amount: usize) {
406    let nine = &mut nine.0;
407    let copy = *nine;
408    for i in 0..(8 - amount) {
409        nine[NINE_ROTATE_LIST[i + amount]] = copy[NINE_ROTATE_LIST[i]];
410    }
411    for i in 0..amount {
412        nine[NINE_ROTATE_LIST[i]] = copy[NINE_ROTATE_LIST[8 - amount + i]];
413    }
414}
415
416impl TileSetPropertyValue {
417    /// The default value for property values of this one's type.
418    pub fn make_default(&self) -> TileSetPropertyValue {
419        match self {
420            TileSetPropertyValue::I32(_) => TileSetPropertyValue::I32(0),
421            TileSetPropertyValue::F32(_) => TileSetPropertyValue::F32(0.0),
422            TileSetPropertyValue::String(_) => {
423                TileSetPropertyValue::String(ImmutableString::default())
424            }
425            TileSetPropertyValue::NineSlice(_) => {
426                TileSetPropertyValue::NineSlice(Default::default())
427            }
428        }
429    }
430    /// Converts an x,y position into index in 0..9. Both x and y must be within 0..3.
431    #[inline]
432    pub fn nine_position_to_index(position: Vector2<usize>) -> usize {
433        if position.y > 2 || position.x > 2 {
434            panic!("Illegal nine slice position: {:?}", position);
435        }
436        position.y * 3 + position.x
437    }
438    /// Converts an index in 0..9 into an x,y position within a tile's nine slice value.
439    #[inline]
440    pub fn index_to_nine_position(index: usize) -> Vector2<usize> {
441        let (y, x) = index.div_rem_euclid(&3);
442        Vector2::new(x, y)
443    }
444    /// Update this value to match the given value, wherever that value is not None.
445    /// Wherever the given value is None, no change is made to this value.
446    pub fn set_from(&mut self, value: &TileSetPropertyOptionValue) {
447        use TileSetPropertyOptionValue as OptValue;
448        use TileSetPropertyValue as PropValue;
449        match (self, value) {
450            (PropValue::I32(x0), OptValue::I32(Some(x1))) => *x0 = *x1,
451            (PropValue::F32(x0), OptValue::F32(Some(x1))) => *x0 = *x1,
452            (PropValue::String(x0), OptValue::String(Some(x1))) => *x0 = x1.clone(),
453            (PropValue::NineSlice(arr0), OptValue::NineSlice(arr1)) => {
454                for (x0, x1) in arr0.0.iter_mut().zip(arr1.iter()) {
455                    if let Some(v) = x1 {
456                        *x0 = *v;
457                    }
458                }
459            }
460            _ => (),
461        }
462    }
463}
464
465/// A representation of data stored in a tile property, or the absence of that data
466/// when the data is unknown.
467#[derive(Clone, Debug, PartialEq, Reflect, Visit)]
468pub enum TileSetPropertyOptionValue {
469    /// Integer property data.
470    I32(Option<i32>),
471    /// Float property data.
472    F32(Option<f32>),
473    /// String property data.
474    String(Option<ImmutableString>),
475    /// Nine-slice properties allow a tile to have nine separate values,
476    /// one value for each of its corners, each of its edges, and its center.
477    NineSlice([Option<i8>; 9]),
478}
479
480impl Default for TileSetPropertyOptionValue {
481    fn default() -> Self {
482        Self::I32(None)
483    }
484}
485
486impl TryFrom<TileSetPropertyValue> for i32 {
487    type Error = TilePropertyError;
488
489    fn try_from(value: TileSetPropertyValue) -> Result<Self, Self::Error> {
490        use TilePropertyError::*;
491        use TileSetPropertyValue::*;
492        match value {
493            I32(v) => Ok(v),
494            F32(_) => Err(WrongType("Expected: i32, Found: f32")),
495            String(_) => Err(WrongType("Expected: i32, Found: ImmutableString")),
496            NineSlice(_) => Err(WrongType("Expected: i32, Found: NineI8")),
497        }
498    }
499}
500
501impl TryFrom<TileSetPropertyValue> for f32 {
502    type Error = TilePropertyError;
503
504    fn try_from(value: TileSetPropertyValue) -> Result<Self, Self::Error> {
505        use TilePropertyError::*;
506        use TileSetPropertyValue::*;
507        match value {
508            I32(_) => Err(WrongType("Expected: f32, Found: i32")),
509            F32(v) => Ok(v),
510            String(_) => Err(WrongType("Expected: f32, Found: ImmutableString")),
511            NineSlice(_) => Err(WrongType("Expected: f32, Found: NineI8")),
512        }
513    }
514}
515
516impl TryFrom<TileSetPropertyValue> for ImmutableString {
517    type Error = TilePropertyError;
518
519    fn try_from(value: TileSetPropertyValue) -> Result<Self, Self::Error> {
520        use TilePropertyError::*;
521        use TileSetPropertyValue::*;
522        match value {
523            I32(_) => Err(WrongType("Expected: ImmutableString, Found: i32")),
524            F32(_) => Err(WrongType("Expected: ImmutableString, Found: f32")),
525            String(v) => Ok(v),
526            NineSlice(_) => Err(WrongType("Expected: ImmutableString, Found: NineI8")),
527        }
528    }
529}
530
531impl TryFrom<TileSetPropertyValue> for NineI8 {
532    type Error = TilePropertyError;
533
534    fn try_from(value: TileSetPropertyValue) -> Result<Self, Self::Error> {
535        use TilePropertyError::*;
536        use TileSetPropertyValue::*;
537        match value {
538            I32(_) => Err(WrongType("Expected: NineI8, Found: i32")),
539            F32(_) => Err(WrongType("Expected: NineI8, Found: f32")),
540            String(_) => Err(WrongType("Expected: NineI8, Found: ImmutableString")),
541            NineSlice(v) => Ok(v),
542        }
543    }
544}
545
546impl From<TileSetPropertyValue> for TileSetPropertyOptionValue {
547    fn from(value: TileSetPropertyValue) -> Self {
548        use TileSetPropertyOptionValue as OValue;
549        use TileSetPropertyValue as Value;
550        match value {
551            Value::I32(x) => OValue::I32(Some(x)),
552            Value::F32(x) => OValue::F32(Some(x)),
553            Value::String(x) => OValue::String(Some(x)),
554            Value::NineSlice(arr) => OValue::NineSlice(arr.0.map(Some)),
555        }
556    }
557}
558
559impl From<TileSetPropertyOptionValue> for TileSetPropertyValue {
560    fn from(value: TileSetPropertyOptionValue) -> Self {
561        use TileSetPropertyOptionValue as OValue;
562        use TileSetPropertyValue as Value;
563        match value {
564            OValue::I32(x) => Value::I32(x.unwrap_or_default()),
565            OValue::F32(x) => Value::F32(x.unwrap_or_default()),
566            OValue::String(x) => Value::String(x.unwrap_or_default()),
567            OValue::NineSlice(arr) => Value::NineSlice(NineI8(arr.map(Option::unwrap_or_default))),
568        }
569    }
570}
571
572impl TileSetPropertyOptionValue {
573    /// Combines this value with the given value, replacing the content of this value with None
574    /// wherever it differs from the given value.
575    pub fn intersect(&mut self, value: &TileSetPropertyValue) {
576        use TileSetPropertyOptionValue as OptValue;
577        use TileSetPropertyValue as PropValue;
578        match self {
579            OptValue::I32(x0) => {
580                if let Some(x) = x0 {
581                    if *value != PropValue::I32(*x) {
582                        *x0 = None
583                    }
584                }
585            }
586            OptValue::F32(x0) => {
587                if let Some(x) = x0 {
588                    if *value != PropValue::F32(*x) {
589                        *x0 = None
590                    }
591                }
592            }
593            OptValue::String(x0) => {
594                if let Some(x) = x0 {
595                    if let PropValue::String(x1) = value {
596                        if *x != *x1 {
597                            *x0 = None
598                        }
599                    } else {
600                        *x0 = None
601                    }
602                }
603            }
604            OptValue::NineSlice(arr0) => {
605                if let PropValue::NineSlice(arr1) = value {
606                    for (x0, x1) in arr0.iter_mut().zip(arr1.0.iter()) {
607                        if let Some(x) = x0 {
608                            if *x != *x1 {
609                                *x0 = None
610                            }
611                        }
612                    }
613                } else {
614                    *arr0 = [None; 9];
615                }
616            }
617        }
618    }
619}