use crate::prelude::*;
use ::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 Vec<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>(
settings: &TiledPhysicsSettings<T>,
commands: &mut Commands,
assets: &Res<Assets<TiledMapAsset>>,
anchor: &TilemapAnchor,
filter: &TiledFilter,
collider_created: TiledEvent<ColliderCreated>,
message_writer: &mut MessageWriter<TiledEvent<ColliderCreated>>,
disable_geometry_simplification: bool,
) {
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<_>>();
if polygons.is_empty() {
return;
}
let polygons = if !disable_geometry_simplification && settings.simplify_geometry {
let Some(polygons) = simplify_geometry(polygons, |a, b| a.union(&b)) else {
warn!(
"Failed to simplify geometry, skipping this polygon (entity {})",
collider_created.origin
);
return;
};
vec![polygons]
} else {
polygons
};
if let Some(entity) = settings.backend.spawn_colliders(
commands,
collider_created.event.source,
collider_created.event.collider_of.0,
polygons.to_owned(),
) {
commands.entity(entity).insert((
collider_created.event.source,
TiledColliderPolygons(polygons),
));
if entity != collider_created.event.collider_of.0 {
commands
.entity(entity)
.insert(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
}