1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
use core::range::Range;
use crate::{
Board,
sim::materials::{MATERIALS, MaterialId},
};
// TODO: chunks
pub fn sim_tick(board: &mut Board, seqno: u64, delta_time: f32) {
// scan bottom to top to enable contiguous falling
let seqno_parity = (seqno as u8) & 0b1;
let bx = (board.size_x / 2) as i32;
let by = (board.size_y / 2) as i32;
for y in (-by..by + 1).rev() {
// invert scan order on every other frame
for col in -bx..bx + 1 {
let x = if seqno_parity == 0 { col } else { -col };
let cell = board.cell_at_position(x, y);
if let Some(cell) = cell
&& cell.flags & 0b1 == seqno_parity
{
let mut cur = cell.clone();
// flip the parity bit
cur.flags = cur.flags ^ 0b1;
let material = &MATERIALS[cur.material as usize];
match cur.material {
MaterialId::Void => {}
// TODO abstract density based movement
MaterialId::Sand => {
for candidate in [
(x, y + 1),
(x - 1 + 2 * seqno_parity as i32, y + 1),
(x + 1 - 2 * seqno_parity as i32, y + 1),
] {
let target = board.cell_at_position(candidate.0, candidate.1);
if let Some(target) = target
&& MATERIALS[target.material as usize].density < material.density
{
// swap the cells
board.set_cell_at_position(x, y, target);
board.set_cell_at_position(candidate.0, candidate.1, cur);
break;
}
}
}
MaterialId::Water => 'water: {
// if the water can fall, do so
for candidate in [
(x, y + 1),
(x - 1 + 2 * seqno_parity as i32, y + 1),
(x + 1 - 2 * seqno_parity as i32, y + 1),
] {
let target = board.cell_at_position(candidate.0, candidate.1);
if let Some(target) = target
&& MATERIALS[target.material as usize].density < material.density
{
// swap the cells
board.set_cell_at_position(x, y, target);
board.set_cell_at_position(candidate.0, candidate.1, cur);
break 'water;
}
}
// if the water can't fall, check if we can move left or right
// these are inverted on parity so that we don't preference a direction
let left_target = board.cell_at_position(x - 1, y);
let can_move_left = left_target.is_some_and(|c| {
MATERIALS[c.material as usize].density < material.density
});
let right_target = board.cell_at_position(x + 1, y);
let can_move_right = right_target.is_some_and(|c| {
MATERIALS[c.material as usize].density < material.density
});
// we can't move down or to other side, so we're stuck
if !can_move_left && !can_move_right {
break 'water;
}
// find the closest hole within 20 pixels (TODO optimize)
// a hole is any space below us with a lesser density
// prevents equidistance stuck state
let starting_side = if seqno_parity == 0 { 1 } else { -1 };
for i in 0..20 {
let side = if i % 2 == 0 {
starting_side
} else {
-starting_side
};
if (side == 1 && !can_move_right) || (side == -1 && !can_move_left) {
continue;
}
let offset = side * (1 + i / 2);
let target = board.cell_at_position(x + offset, y + 1);
if let Some(target) = target
&& MATERIALS[target.material as usize].density < material.density
{
// we identified a hole and we know that the space on this side is open
// move toward the hole
let mut new_target =
if side == 1 { right_target } else { left_target }.clone();
// new_target.flags = new_target.flags ^ 0b1;
// safe to unwrap
board.set_cell_at_position(x, y, new_target.unwrap());
board.set_cell_at_position(x + side, y, cur);
break 'water;
}
}
// we didn't find a hole, so just move "randomly" on the same surface
// TODO when to settle?
let (target, target_x) = if !can_move_left {
(right_target, 1)
} else if !can_move_right {
(left_target, -1)
} else if seqno_parity % 2 == 1 {
(right_target, 1)
} else {
(left_target, -1)
};
board.set_cell_at_position(x, y, target.unwrap());
board.set_cell_at_position(x + target_x, y, cur);
}
}
}
}
}
}
|