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;
|
mod element;
|
||||||
pub mod rules;
|
pub mod rules;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
@ -15,12 +16,8 @@ use bevy::{
|
||||||
sprite::SpriteBundle,
|
sprite::SpriteBundle,
|
||||||
transform::components::Transform,
|
transform::components::Transform,
|
||||||
};
|
};
|
||||||
use ndarray::Array2;
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
use crate::util::DirtyRect;
|
use self::{chunk::Chunk, element::Element, rules::FallingSandRules};
|
||||||
|
|
||||||
use self::{element::Element, rules::FallingSandRules};
|
|
||||||
|
|
||||||
pub struct FallingSandPlugin;
|
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_bundle1);
|
||||||
commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle2);
|
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 {
|
impl RuleBuilder {
|
||||||
pub fn build(input: (Element, Element, Element, Element)) -> u32 {
|
pub fn build(input: &[Element; 4]) -> u32 {
|
||||||
let mut block = RuleBuilder {
|
let mut block = RuleBuilder {
|
||||||
elements: [input.0, input.1, input.2, input.3],
|
elements: *input,
|
||||||
processed: [false; 4],
|
processed: [false; 4],
|
||||||
position: 0,
|
position: 0,
|
||||||
};
|
};
|
||||||
|
@ -33,12 +33,7 @@ impl RuleBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
to_rule_state((
|
to_rule_state(&block.elements)
|
||||||
block.elements[0],
|
|
||||||
block.elements[1],
|
|
||||||
block.elements[2],
|
|
||||||
block.elements[3],
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, x: i8, y: i8) -> Element {
|
pub fn get(&self, x: i8, y: i8) -> Element {
|
||||||
|
@ -85,9 +80,9 @@ impl Default for FallingSandRules {
|
||||||
for b in 0..elements.len() {
|
for b in 0..elements.len() {
|
||||||
for c in 0..elements.len() {
|
for c in 0..elements.len() {
|
||||||
for d in 0..elements.len() {
|
for d in 0..elements.len() {
|
||||||
let input = (elements[a], elements[b], elements[c], elements[d]);
|
let input = [elements[a], elements[b], elements[c], elements[d]];
|
||||||
let in_rule = to_rule_state(input);
|
let in_rule = to_rule_state(&input);
|
||||||
let out_rule = RuleBuilder::build(input);
|
let out_rule = RuleBuilder::build(&input);
|
||||||
if in_rule != out_rule {
|
if in_rule != out_rule {
|
||||||
rules.insert(in_rule, out_rule);
|
rules.insert(in_rule, out_rule);
|
||||||
}
|
}
|
||||||
|
@ -101,11 +96,8 @@ impl Default for FallingSandRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FallingSandRules {
|
impl FallingSandRules {
|
||||||
pub fn get_result(
|
pub fn get_result(&self, input: &[Element; 4]) -> [Element; 4] {
|
||||||
&self,
|
let input_rule = to_rule_state(&input);
|
||||||
input: (Element, Element, Element, Element),
|
|
||||||
) -> (Element, Element, Element, Element) {
|
|
||||||
let input_rule = to_rule_state(input);
|
|
||||||
let output_rule = match self.rules.get(&input_rule) {
|
let output_rule = match self.rules.get(&input_rule) {
|
||||||
Some(&result) => result,
|
Some(&result) => result,
|
||||||
None => input_rule,
|
None => input_rule,
|
||||||
|
@ -114,15 +106,18 @@ impl FallingSandRules {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_rule_state(input: (Element, Element, Element, Element)) -> 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
|
((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 >> 24) & 0xFF),
|
||||||
Element::from((input >> 16) & 0xFF),
|
Element::from((input >> 16) & 0xFF),
|
||||||
Element::from((input >> 8) & 0xFF),
|
Element::from((input >> 8) & 0xFF),
|
||||||
Element::from(input & 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>) {
|
pub fn simulate_chunk(rules: Res<FallingSandRules>, mut query: Query<&mut Chunk>) {
|
||||||
for mut chunk in &mut query {
|
for mut chunk in &mut query {
|
||||||
// Determine which Margolus neighbourhood offset we're using this update
|
chunk.update(&rules);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue