feat: basic liquid simulation
parent
5f2b2b9d06
commit
4fea9d4220
|
|
@ -15,7 +15,7 @@ noise = "0.8.2"
|
|||
|
||||
# Enable a small amount of optimization in debug mode
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
opt-level = 1
|
||||
|
||||
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
|
||||
[profile.dev.package."*"]
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use bevy::prelude::*;
|
|||
use bevy_rapier2d::prelude::*;
|
||||
|
||||
use crate::{
|
||||
terrain2d::{Chunk2D, Terrain2D, Terrain2DPlugin, TerrainGen2D},
|
||||
util::Vector2I,
|
||||
terrain2d::*,
|
||||
util::{frame_counter::FrameCounterPlugin, Vector2I},
|
||||
};
|
||||
|
||||
use self::{
|
||||
|
|
@ -21,6 +21,7 @@ pub mod player;
|
|||
pub fn init() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(FrameCounterPlugin)
|
||||
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
|
||||
.add_plugin(Terrain2DPlugin)
|
||||
.add_plugin(DebugPlugin)
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ impl Plugin for TerrainDebugPlugin {
|
|||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(TerrainBrush2D::default())
|
||||
.add_system(debug_painter)
|
||||
.add_system_to_stage(
|
||||
TerrainStages::First,
|
||||
dirty_rect_visualizer,
|
||||
);
|
||||
.add_system_to_stage(TerrainStages::EventHandler, dirty_rect_visualizer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,16 +89,18 @@ fn debug_painter(
|
|||
if key_input.just_pressed(KeyCode::Key3) {
|
||||
brush.tile = 3;
|
||||
}
|
||||
|
||||
let colors = vec![
|
||||
Color::rgba(1.0, 0.25, 0.25, 1.0),
|
||||
Color::rgba(0.25, 1.0, 0.25, 1.0),
|
||||
Color::rgba(0.25, 0.25, 1.0, 1.0),
|
||||
];
|
||||
if key_input.just_pressed(KeyCode::Key4) {
|
||||
brush.tile = 4;
|
||||
}
|
||||
if key_input.just_pressed(KeyCode::Key5) {
|
||||
brush.tile = 5;
|
||||
}
|
||||
if key_input.just_pressed(KeyCode::Key6) {
|
||||
brush.tile = 6;
|
||||
}
|
||||
|
||||
let origin = Vector2I::from(world_pos);
|
||||
let radius = brush.radius;
|
||||
let color = colors[brush.tile as usize % colors.len()];
|
||||
let id = match (
|
||||
mouse_input.pressed(MouseButton::Left),
|
||||
mouse_input.pressed(MouseButton::Right),
|
||||
|
|
@ -109,6 +108,8 @@ fn debug_painter(
|
|||
(true, false) => brush.tile,
|
||||
(_, _) => 0,
|
||||
};
|
||||
let color = TexelBehaviour2D::from_id(&brush.tile)
|
||||
.map_or(Color::rgba(0.0, 0.0, 0.0, 0.0), |tb| tb.color);
|
||||
|
||||
for y in origin.y - (radius - 1)..origin.y + radius {
|
||||
for x in origin.x - (radius - 1)..origin.x + radius {
|
||||
|
|
@ -118,14 +119,19 @@ fn debug_painter(
|
|||
if dx * dx + dy * dy <= (radius - 1) * (radius - 1) {
|
||||
let pos: Vector2I = Vector2I { x, y };
|
||||
debug_draw.line_colored(
|
||||
Vec3::from(pos) + Vec3::new(0.45, 0.45, 0.0),
|
||||
Vec3::from(pos) + Vec3::new(0.55, 0.55, 0.0),
|
||||
Vec3::from(pos) + Vec3::new(0.45, 0.45, 1.0),
|
||||
Vec3::from(pos) + Vec3::new(0.55, 0.55, 1.0),
|
||||
0.0,
|
||||
color,
|
||||
);
|
||||
if mouse_input.pressed(MouseButton::Left) || mouse_input.pressed(MouseButton::Right)
|
||||
{
|
||||
terrain.set_texel(&pos, id)
|
||||
// 6 is special
|
||||
if id == 6 {
|
||||
terrain.mark_dirty(&pos)
|
||||
} else {
|
||||
terrain.set_texel(&pos, id, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
181
src/terrain2d.rs
181
src/terrain2d.rs
|
|
@ -17,37 +17,38 @@ pub use terrain_gen2d::*;
|
|||
pub use texel2d::*;
|
||||
pub use texel_behaviour2d::*;
|
||||
|
||||
use crate::util::{math::*, Vector2I};
|
||||
use crate::util::{frame_counter::FrameCounter, math::*, Vector2I};
|
||||
|
||||
pub struct Terrain2DPlugin;
|
||||
|
||||
impl Plugin for Terrain2DPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// Add terrain stages. They should go between CoreStage::Update and Rapier's own stages
|
||||
// Add terrain stages
|
||||
app.add_stage_before(
|
||||
CoreStage::Update,
|
||||
TerrainStages::Simulation,
|
||||
SystemStage::parallel(),
|
||||
);
|
||||
// After update, but before rapier
|
||||
app.add_stage_before(
|
||||
PhysicsStages::SyncBackend,
|
||||
TerrainStages::First,
|
||||
SystemStage::parallel(),
|
||||
).add_stage_after(
|
||||
TerrainStages::First,
|
||||
TerrainStages::EventHandler,
|
||||
SystemStage::parallel(),
|
||||
).add_stage_after(
|
||||
)
|
||||
.add_stage_after(
|
||||
TerrainStages::EventHandler,
|
||||
TerrainStages::ChunkSync,
|
||||
SystemStage::parallel(),
|
||||
).add_stage_after(
|
||||
TerrainStages::ChunkSync,
|
||||
TerrainStages::Last,
|
||||
SystemStage::parallel(),
|
||||
);
|
||||
|
||||
app.register_type::<TerrainChunk2D>()
|
||||
.insert_resource(Terrain2D::new())
|
||||
.add_event::<TerrainEvent2D>()
|
||||
.add_system_to_stage(TerrainStages::Simulation, terrain_simulation)
|
||||
.add_system_to_stage(TerrainStages::EventHandler, emit_terrain_events)
|
||||
.add_system_to_stage(
|
||||
TerrainStages::EventHandler,
|
||||
// TODO: Figure out why .after() creates a lagspike for the first frame
|
||||
chunk_spawner.before(emit_terrain_events),
|
||||
)
|
||||
.add_system_to_stage(TerrainStages::ChunkSync, chunk_sprite_sync)
|
||||
|
|
@ -57,14 +58,115 @@ impl Plugin for Terrain2DPlugin {
|
|||
|
||||
#[derive(StageLabel)]
|
||||
pub enum TerrainStages {
|
||||
/// First of terrain stages to be called
|
||||
First,
|
||||
/// The stage that Handles collected events and creates new chunk entities as needed
|
||||
/// Terrain simulation stage. Should run before update.
|
||||
Simulation,
|
||||
/// The stage that Handles collected events and creates new chunk entities as needed. Should run after update.
|
||||
EventHandler,
|
||||
/// Chunk sync systems (e.g. collsion and sprite) run in this stage
|
||||
/// Chunk sync systems (e.g. collsion and sprite) run in this stage.
|
||||
ChunkSync,
|
||||
/// Last of terrain stages to be called
|
||||
Last,
|
||||
}
|
||||
|
||||
// TODO: Add simulation boundaries
|
||||
fn terrain_simulation(mut terrain: ResMut<Terrain2D>, frame_counter: Res<FrameCounter>) {
|
||||
let simulation_frame = (frame_counter.frame % u8::MAX as u64) as u8 + 1;
|
||||
|
||||
let indices = terrain
|
||||
.chunk_iter()
|
||||
.map(|(chunk_index, _)| *chunk_index)
|
||||
.collect::<Vec<Chunk2DIndex>>()
|
||||
.clone();
|
||||
|
||||
for chunk_index in indices.iter() {
|
||||
// // DEBUG: mark few chunks dirty in interval
|
||||
// if let Some(chunk) = terrain.index_to_chunk_mut(&chunk_index) {
|
||||
// let interval = 2;
|
||||
// if frame_counter.frame % interval == 0 {
|
||||
// let i = ((frame_counter.frame / interval) % 100) as i32;
|
||||
// if (chunk_index.y % 10) * 10 + (chunk_index.x % 10) == i {
|
||||
// chunk.mark_all_dirty();
|
||||
// println!("chunk {:?} is now dirty", chunk_index);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
if let Some(rect) = &terrain
|
||||
.index_to_chunk(&chunk_index)
|
||||
.map_or(None, |chunk| chunk.dirty_rect.clone())
|
||||
{
|
||||
if let Some(chunk) = terrain.index_to_chunk_mut(&chunk_index) {
|
||||
chunk.mark_clean();
|
||||
};
|
||||
let mut y_range: Vec<_> = (rect.min.y..rect.max.y + 1).collect();
|
||||
let mut x_range: Vec<_> = (rect.min.x..rect.max.x + 1).collect();
|
||||
|
||||
if frame_counter.frame % 2 == 0 {
|
||||
y_range.reverse();
|
||||
}
|
||||
if frame_counter.frame / 2 % 2 == 0 {
|
||||
x_range.reverse();
|
||||
}
|
||||
|
||||
for y in y_range.iter() {
|
||||
'texel_loop: for x in x_range.iter() {
|
||||
let local = Vector2I::new(*x, *y);
|
||||
let global = local_to_global(&local, &chunk_index);
|
||||
|
||||
let texel = if let Some(texel) = terrain.get_texel(&global) {
|
||||
if texel.last_simulation == simulation_frame {
|
||||
continue 'texel_loop;
|
||||
}
|
||||
texel
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let tb = if let Some(tb) = TexelBehaviour2D::from_id(&texel.id) {
|
||||
tb
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match tb.form {
|
||||
TexelForm::Liquid => {
|
||||
// Check if there is space below
|
||||
{
|
||||
let below_pos = global + Vector2I::DOWN;
|
||||
if terrain.get_texel(&below_pos).map_or(true, |texel| {
|
||||
TexelBehaviour2D::is_empty(&texel.id)
|
||||
|| TexelBehaviour2D::is_gas(&texel.id)
|
||||
}) {
|
||||
let below_id =
|
||||
terrain.get_texel(&below_pos).map_or(0, |texel| texel.id);
|
||||
terrain.set_texel(&below_pos, texel.id, Some(simulation_frame));
|
||||
terrain.set_texel(&global, below_id, Some(simulation_frame));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is space to the side
|
||||
let mut dirs = vec![Vector2I::RIGHT, Vector2I::LEFT];
|
||||
if (frame_counter.frame / 3) % 2 == 0 {
|
||||
dirs.reverse();
|
||||
}
|
||||
for dir in dirs.iter() {
|
||||
let side_pos = global + *dir;
|
||||
if terrain.get_texel(&side_pos).map_or(true, |texel| {
|
||||
TexelBehaviour2D::is_empty(&texel.id)
|
||||
|| TexelBehaviour2D::is_gas(&texel.id)
|
||||
}) {
|
||||
let side_id =
|
||||
terrain.get_texel(&side_pos).map_or(0, |texel| texel.id);
|
||||
terrain.set_texel(&side_pos, texel.id, Some(simulation_frame));
|
||||
terrain.set_texel(&global, side_id, Some(simulation_frame));
|
||||
continue 'texel_loop;
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_terrain_events(
|
||||
|
|
@ -77,7 +179,6 @@ fn emit_terrain_events(
|
|||
for (chunk_index, chunk) in terrain.chunk_iter_mut() {
|
||||
if let Some(rect) = &chunk.dirty_rect {
|
||||
terrain_events.send(TerrainEvent2D::TexelsUpdated(*chunk_index, *rect));
|
||||
chunk.mark_clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,15 +251,34 @@ impl Terrain2D {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_texel(&mut self, global: &Vector2I, id: TexelID) {
|
||||
pub fn mark_dirty(&mut self, global: &Vector2I) {
|
||||
let index = global_to_chunk_index(global);
|
||||
match self.index_to_chunk_mut(&index) {
|
||||
Some(chunk) => chunk.set_texel(&global_to_local(global), id),
|
||||
if let Some(chunk) = self.index_to_chunk_mut(&index) {
|
||||
chunk.mark_dirty(&global_to_local(global));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texel(&self, global: &Vector2I) -> Option<Texel2D> {
|
||||
self.global_to_chunk(global)
|
||||
.map_or(None, |chunk| chunk.get_texel(&global_to_local(global)))
|
||||
}
|
||||
|
||||
pub fn set_texel(&mut self, global: &Vector2I, id: TexelID, simulation_frame: Option<u8>) {
|
||||
let index = global_to_chunk_index(global);
|
||||
let changed = match self.index_to_chunk_mut(&index) {
|
||||
Some(chunk) => chunk.set_texel(&global_to_local(global), id, simulation_frame),
|
||||
None => {
|
||||
let mut chunk = Chunk2D::new();
|
||||
chunk.set_texel(&global_to_local(global), id);
|
||||
let changed = chunk.set_texel(&global_to_local(global), id, simulation_frame);
|
||||
self.add_chunk(index, chunk);
|
||||
changed
|
||||
}
|
||||
};
|
||||
if changed {
|
||||
self.mark_dirty(&(*global + Vector2I::UP));
|
||||
self.mark_dirty(&(*global + Vector2I::RIGHT));
|
||||
self.mark_dirty(&(*global + Vector2I::DOWN));
|
||||
self.mark_dirty(&(*global + Vector2I::LEFT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,10 +301,21 @@ pub fn texel_index_to_local(i: usize) -> Vector2I {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn global_to_local(position: &Vector2I) -> Vector2I {
|
||||
pub fn texel_index_to_global(i: usize, chunk_index: &Chunk2DIndex) -> Vector2I {
|
||||
Vector2I {
|
||||
x: wrapping_remainder(position.x, Chunk2D::SIZE.x),
|
||||
y: wrapping_remainder(position.y, Chunk2D::SIZE.y),
|
||||
x: i as i32 % Chunk2D::SIZE.x,
|
||||
y: i as i32 / Chunk2D::SIZE.y,
|
||||
} + chunk_index_to_global(chunk_index)
|
||||
}
|
||||
|
||||
pub fn local_to_global(local: &Vector2I, chunk_index: &Chunk2DIndex) -> Vector2I {
|
||||
chunk_index_to_global(chunk_index) + *local
|
||||
}
|
||||
|
||||
pub fn global_to_local(global: &Vector2I) -> Vector2I {
|
||||
Vector2I {
|
||||
x: wrapping_remainder(global.x, Chunk2D::SIZE.x),
|
||||
y: wrapping_remainder(global.y, Chunk2D::SIZE.y),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,15 @@ pub struct ChunkRect {
|
|||
pub max: Vector2I,
|
||||
}
|
||||
|
||||
impl ChunkRect {
|
||||
pub fn include_point(&self, point: Vector2I) -> Self {
|
||||
ChunkRect {
|
||||
min: Vector2I::min(&self.min, &point),
|
||||
max: Vector2I::max(&self.max, &point),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Chunk2D {
|
||||
pub texels: [Texel2D; (Self::SIZE_X * Self::SIZE_Y) as usize],
|
||||
// TODO: handle multiple dirty rects
|
||||
|
|
@ -115,7 +124,7 @@ impl Chunk2D {
|
|||
};
|
||||
for y in 0..Self::SIZE_Y {
|
||||
for x in 0..Self::SIZE_X {
|
||||
chunk.set_texel(&Vector2I::new(x as i32, y as i32), 1);
|
||||
chunk.set_texel(&Vector2I::new(x as i32, y as i32), 1, None);
|
||||
}
|
||||
}
|
||||
chunk
|
||||
|
|
@ -129,7 +138,7 @@ impl Chunk2D {
|
|||
for y in 0..Self::SIZE_Y {
|
||||
for x in 0..Self::SIZE_X {
|
||||
if x <= Self::SIZE_Y - y {
|
||||
chunk.set_texel(&Vector2I::new(x as i32, y as i32), 1);
|
||||
chunk.set_texel(&Vector2I::new(x as i32, y as i32), 1, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +157,7 @@ impl Chunk2D {
|
|||
let dx = (x as i32 - origin.x).abs();
|
||||
let dy = (y as i32 - origin.y).abs();
|
||||
if dx * dx + dy * dy <= (radius - 1) * (radius - 1) {
|
||||
chunk.set_texel(&Vector2I::new(x as i32, y as i32), 1);
|
||||
chunk.set_texel(&Vector2I::new(x as i32, y as i32), 1, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,18 +184,13 @@ impl Chunk2D {
|
|||
pub fn mark_all_dirty(&mut self) {
|
||||
self.dirty_rect = Some(ChunkRect {
|
||||
min: Vector2I::ZERO,
|
||||
max: Self::SIZE,
|
||||
max: Self::SIZE - Vector2I::ONE,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn mark_dirty(&mut self, position: &Vector2I) {
|
||||
match &self.dirty_rect {
|
||||
Some(rect) => {
|
||||
self.dirty_rect = Some(ChunkRect {
|
||||
min: Vector2I::min(&rect.min, position),
|
||||
max: Vector2I::max(&rect.max, position),
|
||||
})
|
||||
}
|
||||
Some(rect) => self.dirty_rect = Some(rect.include_point(*position)),
|
||||
None => {
|
||||
self.dirty_rect = Some(ChunkRect {
|
||||
min: *position,
|
||||
|
|
@ -208,18 +212,18 @@ impl Chunk2D {
|
|||
local_to_texel_index(position).map(|i| &mut self.texels[i])
|
||||
}
|
||||
|
||||
pub fn set_texel(&mut self, position: &Vector2I, id: TexelID) {
|
||||
pub fn set_texel(&mut self, position: &Vector2I, id: TexelID, simulation_frame: Option<u8>) -> bool {
|
||||
let i = local_to_texel_index(position).expect("Texel index out of range");
|
||||
if self.texels[i].id != id {
|
||||
self.mark_dirty(position);
|
||||
}
|
||||
let update_neighbours = self.texels[i].is_empty()
|
||||
!= (Texel2D {
|
||||
id,
|
||||
..self.texels[i]
|
||||
})
|
||||
.is_empty();
|
||||
let update_neighbours =
|
||||
TexelBehaviour2D::is_solid(&self.texels[i].id) != TexelBehaviour2D::is_solid(&id);
|
||||
let changed = self.texels[i].id != id;
|
||||
self.texels[i].id = id;
|
||||
if let Some(simulation_frame) = simulation_frame {
|
||||
self.texels[i].last_simulation = simulation_frame;
|
||||
}
|
||||
// Update neighbour mask
|
||||
if update_neighbours {
|
||||
for offset in Texel2D::NEIGHBOUR_OFFSET_VECTORS {
|
||||
|
|
@ -232,6 +236,7 @@ impl Chunk2D {
|
|||
}
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn create_texture_data(&self) -> Vec<u8> {
|
||||
|
|
@ -275,7 +280,7 @@ impl Chunk2D {
|
|||
| if local.x == 0 { 1 << 3 } else { 0 };
|
||||
|
||||
let mut sides: Vec<Segment2I>;
|
||||
if self.texels[i].is_empty() {
|
||||
if !TexelBehaviour2D::is_solid(&self.texels[i].id) {
|
||||
sides = MST_CASE_MAP[self.texels[i].neighbour_mask as usize]
|
||||
.iter()
|
||||
.clone()
|
||||
|
|
@ -284,7 +289,7 @@ impl Chunk2D {
|
|||
to: side.to + local,
|
||||
})
|
||||
.collect();
|
||||
} else if !self.texels[i].is_empty() && edge_mask != 0 {
|
||||
} else if TexelBehaviour2D::is_solid(&self.texels[i].id) && edge_mask != 0 {
|
||||
sides = Vec::with_capacity(Chunk2D::SIZE_X * 2 + Chunk2D::SIZE_Y * 2);
|
||||
for i in 0..MST_EDGE_CASE_MAP.len() {
|
||||
if edge_mask & (1 << i) != 0 {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ impl TerrainGen2D {
|
|||
id = 3;
|
||||
}
|
||||
|
||||
chunk.set_texel(&local, id);
|
||||
chunk.set_texel(&local, id, None);
|
||||
}
|
||||
chunk
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub struct Texel2D {
|
|||
pub id: TexelID,
|
||||
/// bitmask of empty/non-empty neighbours, see NEIGHBOUR_OFFSET_VECTORS for the order
|
||||
pub neighbour_mask: NeighbourMask,
|
||||
pub last_simulation: u8,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
|
|
@ -31,8 +32,4 @@ impl Texel2D {
|
|||
Vector2I { x: 0, y: -1 },
|
||||
Vector2I { x: -1, y: 0 },
|
||||
];
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.id == 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,13 @@ lazy_static! {
|
|||
});
|
||||
|
||||
result.insert(4, TexelBehaviour2D {
|
||||
color: Color::rgb(1.0, 0.0, 0.0),
|
||||
color: Color::rgb(0.0, 0.0, 1.0),
|
||||
form: TexelForm::Liquid,
|
||||
..default()
|
||||
});
|
||||
|
||||
result.insert(5, TexelBehaviour2D {
|
||||
color: Color::rgb(0.0, 1.0, 0.0),
|
||||
form: TexelForm::Gas,
|
||||
..default()
|
||||
});
|
||||
|
|
@ -32,7 +38,7 @@ lazy_static! {
|
|||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
pub enum TexelForm {
|
||||
#[default]
|
||||
Solid,
|
||||
|
|
@ -48,8 +54,25 @@ pub struct TexelBehaviour2D {
|
|||
pub color: Color,
|
||||
}
|
||||
|
||||
// TODO: change form-based functions like is_solid to behaviour based (e.g. has_collision)
|
||||
impl TexelBehaviour2D {
|
||||
pub fn from_id(id: &TexelID) -> Option<Self> {
|
||||
ID_MAP.get(id).copied()
|
||||
}
|
||||
|
||||
pub fn is_empty(id: &TexelID) -> bool {
|
||||
ID_MAP.get(id).is_none()
|
||||
}
|
||||
|
||||
pub fn is_solid(id: &TexelID) -> bool {
|
||||
ID_MAP.get(id).map_or(false, |tb| tb.form == TexelForm::Solid)
|
||||
}
|
||||
|
||||
pub fn is_liquid(id: &TexelID) -> bool {
|
||||
ID_MAP.get(id).map_or(false, |tb| tb.form == TexelForm::Liquid)
|
||||
}
|
||||
|
||||
pub fn is_gas(id: &TexelID) -> bool {
|
||||
ID_MAP.get(id).map_or(false, |tb| tb.form == TexelForm::Gas)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub mod math;
|
|||
mod segment2_i32;
|
||||
mod vector2;
|
||||
mod vector2_i32;
|
||||
pub mod frame_counter;
|
||||
|
||||
pub use collision_layers::*;
|
||||
pub use segment2_i32::*;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub struct FrameCounterPlugin;
|
||||
|
||||
impl Plugin for FrameCounterPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(FrameCounter { frame: 0 })
|
||||
.add_system_to_stage(CoreStage::First, frame_increment);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct FrameCounter {
|
||||
pub frame: u64,
|
||||
}
|
||||
|
||||
fn frame_increment(mut frame_counter: ResMut<FrameCounter>) {
|
||||
frame_counter.frame += 1;
|
||||
}
|
||||
Loading…
Reference in New Issue