use bevy::{
app::{App, Plugin, Update},
ecs::{component::Component, entity::Entity, event::Event, system::Commands},
math::{IVec2, UVec2, Vec2},
reflect::Reflect,
utils::HashMap,
};
use bevy_xpbd_2d::{
components::{Friction, RigidBody},
plugins::collision::Collider,
};
use crate::math::{aabb::IAabb2d, TileArea};
use super::{
buffers::{PackedPhysicsTileBuffer, PhysicsTileBuffer, Tiles},
chunking::storage::{ChunkedStorage, EntityChunkedStorage, PackedPhysicsTileChunkedStorage},
};
pub mod systems;
pub struct EntiTilesPhysicsTilemapPlugin;
impl Plugin for EntiTilesPhysicsTilemapPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
systems::spawn_colliders,
systems::data_physics_tilemap_analyzer,
),
);
app.register_type::<PhysicsTileSpawn>()
.register_type::<PhysicsTilemap>()
.register_type::<DataPhysicsTilemap>()
.register_type::<PhysicsTile>();
app.add_event::<PhysicsTileSpawn>();
}
}
#[derive(Event, Debug, Clone, Copy, Reflect)]
pub struct PhysicsTileSpawn {
pub tile: Entity,
pub tilemap: Entity,
pub int_repr: Option<i32>,
}
#[cfg(feature = "serializing")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Reflect)]
pub enum SerializablePhysicsSource {
Data(DataPhysicsTilemap),
Buffer(super::buffers::PackedPhysicsTileBuffer),
}
#[derive(Debug, Clone, Reflect)]
#[cfg_attr(feature = "serializing", derive(serde::Serialize, serde::Deserialize))]
pub enum PhysicsCollider {
Convex(Vec<Vec2>),
Polyline(Vec<Vec2>),
}
impl PhysicsCollider {
pub fn as_verts(&self) -> &Vec<Vec2> {
match self {
PhysicsCollider::Convex(verts) => verts,
PhysicsCollider::Polyline(verts) => verts,
}
}
pub fn as_verts_mut(&mut self) -> &mut Vec<Vec2> {
match self {
PhysicsCollider::Convex(verts) => verts,
PhysicsCollider::Polyline(verts) => verts,
}
}
}
#[derive(Debug, Clone, Reflect)]
#[cfg_attr(feature = "serializing", derive(serde::Serialize, serde::Deserialize))]
pub struct PackedPhysicsTile {
pub parent: IVec2,
pub collider: PhysicsCollider,
pub physics_tile: PhysicsTile,
}
impl Into<PhysicsTile> for PackedPhysicsTile {
fn into(self) -> PhysicsTile {
self.physics_tile
}
}
impl Tiles for PackedPhysicsTile {}
impl PackedPhysicsTile {
pub fn spawn(&self, commands: &mut Commands) -> Entity {
let mut entity = commands.spawn(match self.collider.clone() {
PhysicsCollider::Convex(verts) => Collider::convex_hull(verts).unwrap(),
PhysicsCollider::Polyline(verts) => Collider::polyline(verts, None),
});
if self.physics_tile.rigid_body {
entity.insert(RigidBody::Static);
}
if let Some(friction) = &self.physics_tile.friction {
entity.insert(Friction::new(*friction));
}
entity.id()
}
}
#[derive(Debug, Clone, Reflect)]
#[cfg_attr(feature = "serializing", derive(serde::Serialize, serde::Deserialize))]
pub struct PhysicsTile {
pub rigid_body: bool,
pub friction: Option<f32>,
}
impl Default for PhysicsTile {
fn default() -> Self {
Self {
rigid_body: true,
friction: Default::default(),
}
}
}
impl Tiles for PhysicsTile {}
#[derive(Component, Debug, Clone, Reflect)]
#[cfg_attr(feature = "serializing", derive(serde::Serialize, serde::Deserialize))]
pub struct DataPhysicsTilemap {
pub(crate) origin: IVec2,
pub(crate) data: Vec<i32>,
pub(crate) size: UVec2,
pub(crate) air: i32,
pub(crate) tiles: HashMap<i32, PhysicsTile>,
}
impl DataPhysicsTilemap {
pub fn new(
origin: IVec2,
data: Vec<i32>,
size: UVec2,
air: i32,
tiles: HashMap<i32, PhysicsTile>,
) -> Self {
assert_eq!(
data.len(),
size.x as usize * size.y as usize,
"Data size mismatch!"
);
let mut flipped = Vec::with_capacity(data.len());
for y in 0..size.y {
for x in 0..size.x {
flipped.push(data[(x + (size.y - y - 1) * size.x) as usize]);
}
}
DataPhysicsTilemap {
origin,
data: flipped,
size,
air,
tiles,
}
}
pub fn new_flipped(
origin: IVec2,
flipped_data: Vec<i32>,
size: UVec2,
air: i32,
tiles: HashMap<i32, PhysicsTile>,
) -> Self {
assert_eq!(
flipped_data.len(),
size.x as usize * size.y as usize,
"Data size mismatch!"
);
DataPhysicsTilemap {
origin,
data: flipped_data,
size,
air,
tiles,
}
}
#[inline]
pub fn get_or_air(&self, index: UVec2) -> i32 {
if index.x >= self.size.x || index.y >= self.size.y {
self.air
} else {
self.data[(index.x + index.y * self.size.x) as usize]
}
}
#[inline]
pub fn get_tile(&self, value: i32) -> Option<PhysicsTile> {
self.tiles.get(&value).cloned()
}
#[inline]
pub fn set(&mut self, index: UVec2, value: i32) {
self.data[(index.x + index.y * self.size.x) as usize] = value;
}
}
#[derive(Component, Debug, Clone, Reflect)]
pub struct PhysicsTilemap {
pub(crate) storage: EntityChunkedStorage,
pub(crate) spawn_queue: Vec<(IAabb2d, PhysicsTile, Option<i32>)>,
pub(crate) data: PackedPhysicsTileChunkedStorage,
}
impl PhysicsTilemap {
pub fn new() -> Self {
PhysicsTilemap {
storage: ChunkedStorage::default(),
spawn_queue: Vec::new(),
data: ChunkedStorage::default(),
}
}
pub fn new_with_chunk_size(chunk_size: u32) -> Self {
PhysicsTilemap {
storage: ChunkedStorage::new(chunk_size),
spawn_queue: Vec::new(),
data: ChunkedStorage::new(chunk_size),
}
}
#[inline]
pub fn get(&self, index: IVec2) -> Option<Entity> {
self.storage.get_elem(index).cloned()
}
#[inline]
pub fn set(&mut self, index: IVec2, tile: PhysicsTile) {
self.spawn_queue.push((IAabb2d::splat(index), tile, None));
}
#[inline]
pub fn remove(&mut self, commands: &mut Commands, index: IVec2) {
if let Some(entity) = self.storage.remove_elem(index) {
commands.entity(entity).despawn();
}
}
#[inline]
pub fn remove_chunk(&mut self, commands: &mut Commands, index: IVec2) {
if let Some(chunk) = self.storage.remove_chunk(index) {
chunk.into_iter().filter_map(|e| e).for_each(|entity| {
commands.entity(entity).despawn();
});
}
}
#[inline]
pub fn remove_all(&mut self, commands: &mut Commands) {
for entity in self.storage.iter_some() {
commands.entity(*entity).despawn();
}
self.storage.clear();
}
pub fn fill_rect(&mut self, area: TileArea, tile: PhysicsTile, concat: bool) {
if concat {
self.spawn_queue.push((area.into(), tile, None));
} else {
self.spawn_queue.extend(
(area.origin.y..=area.dest.y)
.flat_map(|y| (area.origin.x..=area.dest.x).map(move |x| IVec2 { x, y }))
.map(|index| (IAabb2d::splat(index), tile.clone(), None)),
);
}
}
pub fn fill_rect_custom(
&mut self,
area: TileArea,
physics_tile: impl Fn(IVec2) -> Option<PhysicsTile>,
relative_index: bool,
) {
self.spawn_queue.reserve(area.size());
for y in area.origin.y..=area.dest.y {
for x in area.origin.x..=area.dest.x {
let index = IVec2 { x, y };
if let Some(tile) = physics_tile(if relative_index {
index - area.origin
} else {
index
}) {
self.spawn_queue.push((IAabb2d::splat(index), tile, None));
}
}
}
}
pub fn fill_with_buffer(&mut self, origin: IVec2, buffer: PhysicsTileBuffer) {
self.spawn_queue.extend(
buffer
.tiles
.into_iter()
.map(|(index, tile)| (IAabb2d::splat(index + origin), tile, None)),
);
}
pub fn fill_with_buffer_packed(&mut self, origin: IVec2, buffer: PackedPhysicsTileBuffer) {
self.spawn_queue.extend(
buffer
.tiles
.into_iter()
.map(|(index, tile)| (IAabb2d::splat(index + origin), tile.into(), None)),
);
}
}