Move chunk to separate module and minor refactor
This commit is contained in:
parent
1a702b7fd4
commit
fb56f03a74
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue