Skip to content
Snippets Groups Projects
Verified Commit 46ed4a2a authored by Louis's avatar Louis :fire:
Browse files

Enable enriched types for non-bevy environments

parent 06a94039
No related branches found
No related tags found
No related merge requests found
...@@ -1904,6 +1904,7 @@ version = "0.12.0" ...@@ -1904,6 +1904,7 @@ version = "0.12.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bevy", "bevy",
"glam",
"log", "log",
"micro_autotile", "micro_autotile",
"num-traits", "num-traits",
......
...@@ -45,4 +45,5 @@ serde_json = "1.0" ...@@ -45,4 +45,5 @@ serde_json = "1.0"
num-traits = "0.2" num-traits = "0.2"
quadtree_rs = "0.1" quadtree_rs = "0.1"
micro_autotile = { version = "0.2", optional = true } micro_autotile = { version = "0.2", optional = true }
glam = { version = "0.27", features = ["serde"] }
...@@ -4,6 +4,11 @@ mod assets; ...@@ -4,6 +4,11 @@ mod assets;
mod camera; mod camera;
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
mod map_query; mod map_query;
#[cfg(not(feature = "bevy"))]
mod map_query_neutral;
#[cfg(not(feature = "bevy"))]
use map_query_neutral as map_query;
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
mod pregen; mod pregen;
mod system; mod system;
...@@ -98,7 +103,6 @@ pub use camera::CameraBounder; ...@@ -98,7 +103,6 @@ pub use camera::CameraBounder;
pub use ldtk::{LdtkProject, Level, AutoLayerRuleGroup}; pub use ldtk::{LdtkProject, Level, AutoLayerRuleGroup};
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use ldtk::LdtkLoader; pub use ldtk::LdtkLoader;
#[cfg(feature = "bevy")]
pub use map_query::{CameraBounds, InstanceRef, MapQuery}; pub use map_query::{CameraBounds, InstanceRef, MapQuery};
#[cfg(feature = "autotile")] #[cfg(feature = "autotile")]
pub use micro_autotile as autotile; pub use micro_autotile as autotile;
......
use std::fmt::Debug;
use std::ops::Index;
use std::str::FromStr;
use crate::ldtk::EntityInstance;
use crate::{LdtkLayer, LdtkLevel};
pub struct MapQuery {}
#[derive(Clone)]
pub struct InstanceRef<'a> {
pub entity: &'a EntityInstance,
}
impl<'a> InstanceRef<'a> {
/// Get the leftmost pixel of this entity's anchor point
pub fn x(&self) -> i64 {
self.entity.px[0]
}
/// Get the topmost pixel of this entity's anchor point
pub fn y(&self) -> i64 {
self.entity.px[1]
}
/// Get the pixel width of this entity
pub fn width(&self) -> i64 {
self.entity.width
}
/// Get the pixel width of this entity
pub fn height(&self) -> i64 {
self.entity.height
}
/// Get the category that this instance belongs to. Exactly matches the string name
/// found in the LDTK entities list
pub fn get_type(&self) -> &'a String {
&self.entity.identifier
}
/// Try to get a type safe representation of this entity's type, as long as the target type
/// can be produced from a [str] representation
///
/// ## Example
///
/// ```
/// # use std::str::FromStr;
/// # use micro_ldtk::InstanceRef;
/// # use micro_ldtk::ldtk::EntityInstance;
///
/// #[derive(PartialEq, Debug)]
/// enum MyEntityType {
/// Player,
/// Monster,
/// }
///
/// impl FromStr for MyEntityType {
/// # type Err = ();
/// fn from_str(s: &str) -> Result<Self, Self::Err> {
/// match s {
/// "player" => Ok(Self::Player),
/// "monster" => Ok(Self::Monster),
/// # _ => panic!("Oh no")
/// }
/// }
/// }
///
/// let data_from_ldtk: EntityInstance = EntityInstance {
/// identifier: "player".to_string(),
/// // ...other properties
/// # smart_color: "".to_string(),
/// # grid: vec![],
/// # pivot: vec![],
/// # tags: vec![],
/// # tile: None,
/// # world_x: None,
/// # world_y: None,
/// # def_uid: 0,
/// # field_instances: vec![],
/// # height: 0,
/// # iid: "".to_string(),
/// # px: vec![],
/// # width: 0,
/// };
/// #
/// # let process_ldtk_data = || -> InstanceRef<'_> {
/// # InstanceRef {
/// # entity: &data_from_ldtk,
/// # }
/// # };
///
/// let my_entity_type: InstanceRef<'_> = process_ldtk_data();
/// assert_eq!(my_entity_type.try_get_typed_id(), Ok(MyEntityType::Player));
/// ```
pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> {
T::from_str(self.get_type().as_str())
}
/// Retrieve an associated property from this instance. Will return [serde_json::Value::Null]
/// if there is no property with the given name
pub fn property(&self, name: impl ToString) -> serde_json::Value {
self[name].clone()
}
/// Get a reference to the inner instance of this instance ref
pub fn instance_ref(&self) -> &EntityInstance {
self.entity
}
}
impl<'a, T: ToString> Index<T> for InstanceRef<'a> {
type Output = serde_json::Value;
fn index(&self, index: T) -> &Self::Output {
let name = index.to_string();
for field in &self.entity.field_instances {
if field.identifier == name {
return field.value.as_ref().unwrap_or(&serde_json::Value::Null);
}
}
&serde_json::Value::Null
}
}
#[derive(Copy, Clone, Debug)]
pub struct CameraBounds {
pub left: f32,
pub top: f32,
pub bottom: f32,
pub right: f32,
}
impl CameraBounds {
pub fn get_min_x(&self, camera_width: f32) -> f32 {
self.left + (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_x(&self, camera_width: f32) -> f32 {
self.right - (camera_width / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_min_y(&self, camera_height: f32) -> f32 {
self.bottom + (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_y(&self, camera_height: f32) -> f32 {
self.top - (camera_height / 2.0) // - (get_ldtk_tile_scale() / 2.0)
}
}
impl MapQuery {
// --- We put our logic in static accessors because we might source a level other
// --- than the currently active one. 'active' methods are a convenience to
// --- call the static accessors on whatever the current level is
/// Perform an action on each layer of the given LDTK level
pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) {
for layer in level.layers() {
cb(layer);
}
}
/// Retrieve an iterator over every layer in the given level, regardless of type
pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
level.layers()
}
/// Retrieve a reference to every entity stored in the given level, regardless of which layer it is found on
pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> {
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter())
.collect()
}
/// Retrieve an enhanced wrapper to every entity stored in the given level, regardless of which layer it is found on
pub fn get_instance_refs_of(level: &LdtkLevel) -> Vec<InstanceRef> {
level
.layers()
.flat_map(|layer| {
layer
.as_ref()
.entity_instances
.iter()
.map(|inst| InstanceRef { entity: inst })
})
.collect()
}
/// Retrieve a reference to every entity stored in the given level that matches the specified type name.
/// This must exactly match the name shown in the LDTK entity list
pub fn get_filtered_entities_of(
level: &LdtkLevel,
entity_type: impl ToString,
) -> Vec<&EntityInstance> {
let e_type = entity_type.to_string();
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter())
.filter(|inst| inst.identifier == e_type)
.collect()
}
/// Retrieve an enhanced wrapper to every entity stored in the given level that matches the specified type name.
/// This must exactly match the name shown in the LDTK entity list
pub fn get_filtered_instance_refs_of(
level: &LdtkLevel,
entity_type: impl ToString,
) -> Vec<InstanceRef> {
let e_type = entity_type.to_string();
level
.layers()
.flat_map(|layer| {
layer
.as_ref()
.entity_instances
.iter()
.map(|inst| InstanceRef { entity: inst })
})
.filter(|inst| inst.entity.identifier == e_type)
.collect()
}
/// Retrieve an owned copy of all entity data in the given level
pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> {
level
.layers()
.flat_map(|layer| layer.as_ref().entity_instances.iter().cloned())
.collect()
}
/// Use the size of the level to create a zero-based rectangle indicating the boundaries that a camera should
///stay within to avoid showing any out-of-level space
pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds {
let level = level.level_ref();
CameraBounds {
left: 0.0,
top: level.px_hei as f32,
bottom: 0.0,
right: level.px_wid as f32,
}
}
}
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
mod locator; mod locator;
#[cfg(feature = "bevy")]
mod types; mod types;
mod utils; mod utils;
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
pub use locator::*; pub use locator::*;
#[cfg(feature = "bevy")]
pub use types::*; pub use types::*;
pub use utils::*; pub use utils::*;
...@@ -4,9 +4,11 @@ use std::ops::{Deref, DerefMut, Index}; ...@@ -4,9 +4,11 @@ use std::ops::{Deref, DerefMut, Index};
use std::path::Path; use std::path::Path;
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
use bevy::math::{IVec2, Rect, UVec2, Vec2}; use bevy::math::Rect;
#[cfg(feature = "bevy")] #[cfg(not(feature = "bevy"))]
use bevy::prelude::Event; use compat::Rect;
use glam::{IVec2, UVec2, Vec2};
use num_traits::AsPrimitive; use num_traits::AsPrimitive;
use quadtree_rs::area::AreaBuilder; use quadtree_rs::area::AreaBuilder;
use quadtree_rs::point::Point; use quadtree_rs::point::Point;
...@@ -17,11 +19,12 @@ use serde_json::{Map, Number, Value}; ...@@ -17,11 +19,12 @@ use serde_json::{Map, Number, Value};
use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, TileInstance}; use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, TileInstance};
use crate::system::Indexer; use crate::system::Indexer;
use crate::{get_ldtk_tile_scale, px_to_grid}; use crate::{get_ldtk_tile_scale, px_to_grid};
#[cfg(feature = "bevy")]
use crate::MapQuery; use crate::MapQuery;
mod compat;
#[derive(Default, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)] #[derive(Default, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
#[cfg_attr(feature = "bevy", derive(Event))] #[cfg_attr(feature = "bevy", derive(bevy::prelude::Event))]
pub struct LevelDataUpdated(pub String); pub struct LevelDataUpdated(pub String);
pub struct TileRef<'a> { pub struct TileRef<'a> {
...@@ -93,13 +96,11 @@ where ...@@ -93,13 +96,11 @@ where
Self(value.0.as_(), value.1.as_()) Self(value.0.as_(), value.1.as_())
} }
} }
#[cfg(feature = "bevy")]
impl From<UVec2> for SpatialIndex { impl From<UVec2> for SpatialIndex {
fn from(value: UVec2) -> Self { fn from(value: UVec2) -> Self {
Self(value.x as i64, value.y as i64) Self(value.x as i64, value.y as i64)
} }
} }
#[cfg(feature = "bevy")]
impl From<IVec2> for SpatialIndex { impl From<IVec2> for SpatialIndex {
fn from(value: IVec2) -> Self { fn from(value: IVec2) -> Self {
Self(value.x as i64, value.y as i64) Self(value.x as i64, value.y as i64)
......
#![allow(dead_code)]
use glam::Vec2;
use serde::{Deserialize, Serialize};
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Rect {
pub min: Vec2,
pub max: Vec2,
}
impl Rect {
pub const EMPTY: Self = Self {
max: Vec2::NEG_INFINITY,
min: Vec2::INFINITY,
};
#[inline]
pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
Self::from_corners(Vec2::new(x0, y0), Vec2::new(x1, y1))
}
#[inline]
pub fn from_corners(p0: Vec2, p1: Vec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
#[inline]
pub fn from_center_size(origin: Vec2, size: Vec2) -> Self {
assert!(size.cmpge(Vec2::ZERO).all(), "Rect size must be positive");
let half_size = size / 2.;
Self::from_center_half_size(origin, half_size)
}
#[inline]
pub fn from_center_half_size(origin: Vec2, half_size: Vec2) -> Self {
assert!(
half_size.cmpge(Vec2::ZERO).all(),
"Rect half_size must be positive"
);
Self {
min: origin - half_size,
max: origin + half_size,
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
#[inline]
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
#[inline]
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
#[inline]
pub fn size(&self) -> Vec2 {
self.max - self.min
}
#[inline]
pub fn half_size(&self) -> Vec2 {
self.size() * 0.5
}
#[inline]
pub fn center(&self) -> Vec2 {
(self.min + self.max) * 0.5
}
#[inline]
pub fn contains(&self, point: Vec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline]
pub fn union_point(&self, other: Vec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
#[inline]
pub fn inflate(&self, expansion: f32) -> Self {
let mut r = Self {
min: self.min - expansion,
max: self.max + expansion,
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
pub fn normalize(&self, other: Self) -> Self {
let outer_size = other.size();
Self {
min: (self.min - other.min) / outer_size,
max: (self.max - other.min) / outer_size,
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment