Move chunk to separate module and minor refactor

This commit is contained in:
Jarrod Doyle 2024-03-11 12:57:02 +00:00
parent 1a702b7fd4
commit fb56f03a74
Signed by: Jayrude
GPG Key ID: 38B57B16E7C0ADF7
4 changed files with 116 additions and 127 deletions

97
src/falling_sand/chunk.rs Normal file
View File

@ -0,0 +1,97 @@
use bevy::ecs::{component::Component, system::Res};
use ndarray::Array2;
use rand::Rng;
use crate::util::DirtyRect;
use super::{element::Element, rules::FallingSandRules};
#[derive(Component)]
pub struct Chunk {
step: usize,
pub width: usize,
pub height: usize,
cells: Array2<Element>,
pub dirty_rect: DirtyRect,
}
impl Chunk {
pub fn new(width: usize, height: usize) -> Self {
let mut initial = Self {
step: 0,
width,
height,
cells: Array2::from_elem((width, height), Element::Air),
dirty_rect: DirtyRect::default(),
};
let max_y = height / rand::thread_rng().gen_range(2..10);
for y in 0..=max_y {
for x in 0..width {
initial.set_cell(x, y, Element::Water);
}
}
initial
}
pub fn set_cell(&mut self, x: usize, y: usize, element: Element) {
if x >= self.width || y >= self.height {
return;
}
self.cells[(x, y)] = element;
self.dirty_rect.add_point(x, y);
}
pub fn swap_cells(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
if x0 >= self.width || y0 >= self.height || x1 >= self.width || y1 >= self.height {
return;
}
self.cells.swap((x0, y0), (x1, y1));
self.dirty_rect.add_point(x0, y0);
self.dirty_rect.add_point(x1, y1);
}
pub fn get_cell(&self, x: usize, y: usize) -> Option<Element> {
if x >= self.width || y >= self.height {
return None;
}
Some(self.cells[(x, y)])
}
pub fn update(&mut self, rules: &Res<FallingSandRules>) {
// We operate on 2x2 blocks of cells. Each update we offset the blocks using a
// modified Margolus neighbourhood.
let offsets = [(0, 1), (1, 1), (0, 0), (1, 0)];
let block_offset = [(0, 0), (1, 1), (0, 1), (1, 0)][self.step];
self.step = (self.step + 1) % 4;
// Faster to reuse rather than keep remaking it within the loops
let mut in_elements: [Element; 4] = [Element::None; 4];
for block_y in 0..(self.height / 2) {
let y = block_y * 2 + block_offset.1;
for block_x in 0..(self.width / 2) {
let x = block_x * 2 + block_offset.0;
// Get all the cells in our block and convert them to a rule state for lookup
// Because our offset can cause cell look-ups to go ourside of the grid we have
// a default `Element::None`
for i in 0..offsets.len() {
let o = offsets[i];
in_elements[i] = self.get_cell(x + o.0, y + o.1).unwrap_or(Element::None);
}
let out_elements = rules.get_result(&in_elements);
// We only need to actually update things if the state changed
for i in 0..offsets.len() {
let o = offsets[i];
if in_elements[i] != out_elements[i] {
self.set_cell(x + o.0, y + o.1, out_elements[i]);
}
}
}
}
}
}

View File

@ -1,3 +1,4 @@
mod chunk;
mod element;
pub mod rules;
mod systems;
@ -15,12 +16,8 @@ use bevy::{
sprite::SpriteBundle,
transform::components::Transform,
};
use ndarray::Array2;
use rand::Rng;
use crate::util::DirtyRect;
use self::{element::Element, rules::FallingSandRules};
use self::{chunk::Chunk, element::Element, rules::FallingSandRules};
pub struct FallingSandPlugin;
@ -70,59 +67,3 @@ fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle1);
commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle2);
}
#[derive(Component)]
pub struct Chunk {
step: usize,
width: usize,
height: usize,
cells: Array2<Element>,
dirty_rect: DirtyRect,
}
impl Chunk {
pub fn new(width: usize, height: usize) -> Self {
let mut initial = Self {
step: 0,
width,
height,
cells: Array2::from_elem((width, height), Element::Air),
dirty_rect: DirtyRect::default(),
};
let max_y = height / rand::thread_rng().gen_range(2..10);
for y in 0..=max_y {
for x in 0..width {
initial.set_cell(x, y, Element::Water);
}
}
initial
}
pub fn set_cell(&mut self, x: usize, y: usize, element: Element) {
if x >= self.width || y >= self.height {
return;
}
self.cells[(x, y)] = element;
self.dirty_rect.add_point(x, y);
}
pub fn swap_cells(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
if x0 >= self.width || y0 >= self.height || x1 >= self.width || y1 >= self.height {
return;
}
self.cells.swap((x0, y0), (x1, y1));
self.dirty_rect.add_point(x0, y0);
self.dirty_rect.add_point(x1, y1);
}
pub fn get_cell(&self, x: usize, y: usize) -> Option<Element> {
if x >= self.width || y >= self.height {
return None;
}
Some(self.cells[(x, y)])
}
}

View File

@ -12,9 +12,9 @@ pub struct RuleBuilder {
}
impl RuleBuilder {
pub fn build(input: (Element, Element, Element, Element)) -> u32 {
pub fn build(input: &[Element; 4]) -> u32 {
let mut block = RuleBuilder {
elements: [input.0, input.1, input.2, input.3],
elements: *input,
processed: [false; 4],
position: 0,
};
@ -33,12 +33,7 @@ impl RuleBuilder {
}
}
to_rule_state((
block.elements[0],
block.elements[1],
block.elements[2],
block.elements[3],
))
to_rule_state(&block.elements)
}
pub fn get(&self, x: i8, y: i8) -> Element {
@ -85,9 +80,9 @@ impl Default for FallingSandRules {
for b in 0..elements.len() {
for c in 0..elements.len() {
for d in 0..elements.len() {
let input = (elements[a], elements[b], elements[c], elements[d]);
let in_rule = to_rule_state(input);
let out_rule = RuleBuilder::build(input);
let input = [elements[a], elements[b], elements[c], elements[d]];
let in_rule = to_rule_state(&input);
let out_rule = RuleBuilder::build(&input);
if in_rule != out_rule {
rules.insert(in_rule, out_rule);
}
@ -101,11 +96,8 @@ impl Default for FallingSandRules {
}
impl FallingSandRules {
pub fn get_result(
&self,
input: (Element, Element, Element, Element),
) -> (Element, Element, Element, Element) {
let input_rule = to_rule_state(input);
pub fn get_result(&self, input: &[Element; 4]) -> [Element; 4] {
let input_rule = to_rule_state(&input);
let output_rule = match self.rules.get(&input_rule) {
Some(&result) => result,
None => input_rule,
@ -114,15 +106,18 @@ impl FallingSandRules {
}
}
fn to_rule_state(input: (Element, Element, Element, Element)) -> u32 {
((input.0 as u32) << 24) + ((input.1 as u32) << 16) + ((input.2 as u32) << 8) + input.3 as u32
fn to_rule_state(input: &[Element; 4]) -> u32 {
((input[0] as u32) << 24)
+ ((input[1] as u32) << 16)
+ ((input[2] as u32) << 8)
+ input[3] as u32
}
fn from_rule_state(input: u32) -> (Element, Element, Element, Element) {
(
fn from_rule_state(input: u32) -> [Element; 4] {
[
Element::from((input >> 24) & 0xFF),
Element::from((input >> 16) & 0xFF),
Element::from((input >> 8) & 0xFF),
Element::from(input & 0xFF),
)
]
}

View File

@ -18,51 +18,7 @@ pub fn place_sand(mut query: Query<&mut Chunk>) {
pub fn simulate_chunk(rules: Res<FallingSandRules>, mut query: Query<&mut Chunk>) {
for mut chunk in &mut query {
// Determine which Margolus neighbourhood offset we're using this update
let offset = if chunk.step == 0 {
(0, 0)
} else if chunk.step == 1 {
(1, 1)
} else if chunk.step == 2 {
(0, 1)
} else {
(1, 0)
};
chunk.step = (chunk.step + 1) % 4;
// We're operating on 2x2 blocks of cells
for block_y in 0..(chunk.height / 2) {
let y = block_y * 2 + offset.1;
for block_x in 0..(chunk.width / 2) {
let x = block_x * 2 + offset.0;
// Get all the cells in our block and convert them to a rule state for lookup
// Because our offset can cause cell look-ups to go ourside of the grid we have
// a default `Element::None`
// Cells blocks are in the order top-left, top-right, bottom-left, bottom-right
let in_elements = (
chunk.get_cell(x, y + 1).unwrap_or(Element::None),
chunk.get_cell(x + 1, y + 1).unwrap_or(Element::None),
chunk.get_cell(x, y).unwrap_or(Element::None),
chunk.get_cell(x + 1, y).unwrap_or(Element::None),
);
let out_elements = rules.get_result(in_elements);
// We only need to actually update things if the state changed
if in_elements.0 != out_elements.0 {
chunk.set_cell(x, y + 1, out_elements.0);
}
if in_elements.1 != out_elements.1 {
chunk.set_cell(x + 1, y + 1, out_elements.1);
}
if in_elements.2 != out_elements.2 {
chunk.set_cell(x, y, out_elements.2);
}
if in_elements.3 != out_elements.3 {
chunk.set_cell(x + 1, y, out_elements.3);
}
}
}
chunk.update(&rules);
}
}