use std::collections::VecDeque;
use crate::prelude::{geo::BooleanOps, *};
use bevy::prelude::*;
#[derive(Component, Reflect, Copy, PartialEq, Clone, Debug)]
#[reflect(Component, Debug)]
pub enum TiledColliderSource {
TilesLayer,
Object,
}
#[derive(Component, Reflect, Copy, PartialEq, Clone, Debug, Deref)]
#[reflect(Component, Debug)]
#[relationship(relationship_target = TiledColliders)]
pub struct TiledColliderOf(pub Entity);
#[derive(Component, Reflect, Debug, Deref)]
#[reflect(Component, Debug)]
#[relationship_target(relationship = TiledColliderOf)]
pub struct TiledColliders(Vec<Entity>);
#[derive(Component, PartialEq, Clone, Debug, Deref)]
#[require(Transform)]
pub struct TiledColliderPolygons(pub geo::MultiPolygon<f32>);
#[derive(Clone, Copy, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct ColliderCreated {
pub source: TiledColliderSource,
pub collider_of: TiledColliderOf,
}
impl ColliderCreated {
pub fn new(source: TiledColliderSource, parent: Entity) -> Self {
Self {
source,
collider_of: TiledColliderOf(parent),
}
}
}
pub(crate) fn plugin(app: &mut App) {
app.register_type::<TiledColliderSource>();
app.register_type::<TiledColliderOf>();
app.register_type::<TiledColliders>();
app.add_message::<TiledEvent<ColliderCreated>>()
.register_type::<TiledEvent<ColliderCreated>>();
}
impl<'a> TiledEvent<ColliderCreated> {
pub fn get_tiles(
&self,
assets: &'a Res<Assets<TiledMapAsset>>,
anchor: &TilemapAnchor,
) -> Vec<(Vec2, tiled::Tile<'a>)> {
let Some(map_asset) = self.get_map_asset(assets) else {
return vec![];
};
self.get_layer(assets)
.and_then(|layer| layer.as_tile_layer())
.map(|layer| {
let mut out = vec![];
map_asset.for_each_tile(&layer, |layer_tile, _, tile_pos, _| {
if let Some(tile) = layer_tile.get_tile() {
let tile_coords =
map_asset.tile_relative_position(&tile_pos, &tile_size(&tile), anchor);
let offset = Vec2::new(
tile.tileset().offset_x as f32,
-tile.tileset().offset_y as f32,
);
out.push((tile_coords + offset, tile));
}
});
out
})
.unwrap_or_default()
}
}
pub(crate) fn spawn_colliders<T: TiledPhysicsBackend>(
backend: &T,
commands: &mut Commands,
assets: &Res<Assets<TiledMapAsset>>,
anchor: &TilemapAnchor,
filter: &TiledFilter,
collider_created: TiledEvent<ColliderCreated>,
message_writer: &mut MessageWriter<TiledEvent<ColliderCreated>>,
) {
let Some(map_asset) = collider_created.get_map_asset(assets) else {
return;
};
let polygons = match collider_created.event.source {
TiledColliderSource::Object => {
if let Some(object) = collider_created.get_object(assets) {
match object.get_tile() {
None => {
let global_transform = &GlobalTransform::default();
TiledObject::from_object_data(&object)
.polygon(
global_transform,
matches!(
tilemap_type_from_map(&map_asset.map),
TilemapType::Isometric(..)
),
&map_asset.tilemap_size,
&grid_size_from_map(&map_asset.map),
map_asset.tiled_offset,
)
.map(|p| vec![p])
}
Some(object_tile) => object_tile.get_tile().map(|tile| {
let Some(object_layer_data) = &tile.collision else {
return vec![];
};
let ::tiled::ObjectShape::Rect { width, height } = object.shape else {
return vec![];
};
let tile_size = tile_size(&tile);
let mut scale =
Vec2::new(width, height) / Vec2::new(tile_size.x, tile_size.y);
let mut offset = Vec2::new(
tile.tileset().offset_x as f32,
-tile.tileset().offset_y as f32,
) * scale;
if object_tile.flip_h {
scale.x *= -1.;
offset.x += width;
}
if object_tile.flip_v {
scale.y *= -1.;
offset.y -= height;
}
polygons_from_tile(
object_layer_data,
filter,
&TilemapTileSize::new(width, height),
offset,
scale,
)
}),
}
.unwrap_or_default()
} else {
vec![]
}
}
TiledColliderSource::TilesLayer => {
let mut acc = vec![];
for (tile_position, tile) in collider_created.get_tiles(assets, anchor) {
if let Some(collision) = &tile.collision {
let tile_size = tile_size(&tile);
acc.extend(polygons_from_tile(
collision,
filter,
&tile_size,
Vec2::new(
tile_position.x - tile_size.x / 2.,
tile_position.y - tile_size.y / 2.,
),
Vec2::ONE,
));
}
}
acc
}
}
.into_iter()
.map(|p| geo::MultiPolygon::new(vec![p]))
.collect::<Vec<_>>();
let Some(polygons) = divide_reduce(polygons, |a, b| a.union(&b)) else {
return;
};
for entity in backend.spawn_colliders(commands, &collider_created, &polygons) {
commands.entity(entity).insert((
collider_created.event.source,
collider_created.event.collider_of,
TiledColliderPolygons(polygons.to_owned()),
ChildOf(*collider_created.event.collider_of),
));
let mut event = collider_created;
event.origin = entity;
event.send(commands, message_writer);
}
}
fn polygons_from_tile(
object_layer_data: &::tiled::ObjectLayerData,
filter: &TiledFilter,
tile_size: &TilemapTileSize,
offset: Vec2,
scale: Vec2,
) -> Vec<geo::Polygon<f32>> {
let mut polygons = vec![];
for object in object_layer_data.object_data() {
if !filter.matches(&object.name) {
continue;
}
let pos = offset + Vec2::new(object.x * scale.x, tile_size.y - object.y * scale.y);
let transform = GlobalTransform::from_isometry(Isometry3d {
rotation: Quat::from_rotation_z(f32::to_radians(-object.rotation)),
translation: pos.extend(0.).into(),
}) * Transform::from_scale(scale.extend(1.));
if let Some(p) = TiledObject::from_object_data(object).polygon(
&transform,
false, &TilemapSize::new(1, 1),
&TilemapGridSize::new(tile_size.x, tile_size.y),
Vec2::ZERO,
) {
polygons.push(p);
}
}
polygons
}
fn divide_reduce<T>(list: Vec<T>, mut reduction: impl FnMut(T, T) -> T) -> Option<T> {
let mut queue = VecDeque::from(list);
while queue.len() > 1 {
for _ in 0..(queue.len() / 2) {
let (one, two) = (queue.pop_front().unwrap(), queue.pop_front().unwrap());
queue.push_back(reduction(one, two));
}
}
queue.pop_back()
}