361 lines
11 KiB
Rust
361 lines
11 KiB
Rust
use crate::world::cell::{Cell, MaterialId};
|
|
use crate::world::chunk::{world_to_chunk, CHUNK_SIZE};
|
|
use std::io;
|
|
use std::path::Path;
|
|
|
|
pub const WORLD_W: usize = 250;
|
|
pub const WORLD_H: usize = 250;
|
|
|
|
pub const MAX_WORLD_W: usize = 2048;
|
|
pub const MAX_WORLD_H: usize = 2048;
|
|
|
|
pub struct ChunkMeta {
|
|
pub active: bool,
|
|
pub modified: bool,
|
|
pub was_modified: bool,
|
|
pub dirty: Option<(i32, i32, i32, i32)>,
|
|
}
|
|
|
|
pub struct Grid {
|
|
pub cells: Vec<Cell>,
|
|
pub width: usize,
|
|
pub height: usize,
|
|
pub chunk_size: usize,
|
|
pub chunks_x: usize,
|
|
pub chunks_y: usize,
|
|
pub chunks: Vec<ChunkMeta>,
|
|
}
|
|
|
|
impl Grid {
|
|
pub fn new() -> Self {
|
|
Self::with_size(WORLD_W, WORLD_H)
|
|
}
|
|
|
|
pub fn with_size(width: usize, height: usize) -> Self {
|
|
let size = width * height;
|
|
let chunk_size = CHUNK_SIZE;
|
|
let chunks_x = (width + chunk_size - 1) / chunk_size;
|
|
let chunks_y = (height + chunk_size - 1) / chunk_size;
|
|
let mut chunks = Vec::with_capacity(chunks_x * chunks_y);
|
|
for _ in 0..chunks_x * chunks_y {
|
|
chunks.push(ChunkMeta {
|
|
active: true,
|
|
modified: false,
|
|
was_modified: false,
|
|
dirty: None,
|
|
});
|
|
}
|
|
Self {
|
|
cells: vec![Cell::empty(); size],
|
|
width,
|
|
height,
|
|
chunk_size,
|
|
chunks_x,
|
|
chunks_y,
|
|
chunks,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn idx(&self, x: i32, y: i32) -> usize {
|
|
(y as usize) * self.width + (x as usize)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn in_bounds(&self, x: i32, y: i32) -> bool {
|
|
x >= 0 && x < self.width as i32 && y >= 0 && y < self.height as i32
|
|
}
|
|
|
|
#[inline]
|
|
pub fn chunk_index(&self, cx: i32, cy: i32) -> usize {
|
|
(cy as usize) * self.chunks_x + (cx as usize)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn chunk_at(&self, x: i32, y: i32) -> (i32, i32, i32, i32) {
|
|
world_to_chunk(x, y)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_chunk_active(&self, cx: i32, cy: i32) -> bool {
|
|
if cx < 0 || cy < 0 || cx >= self.chunks_x as i32 || cy >= self.chunks_y as i32 {
|
|
return false;
|
|
}
|
|
self.chunks[self.chunk_index(cx, cy)].active
|
|
}
|
|
|
|
pub fn set_chunk_active(&mut self, cx: i32, cy: i32, active: bool) {
|
|
if cx < 0 || cy < 0 || cx >= self.chunks_x as i32 || cy >= self.chunks_y as i32 {
|
|
return;
|
|
}
|
|
let idx = self.chunk_index(cx, cy);
|
|
self.chunks[idx].active = active;
|
|
}
|
|
|
|
pub fn activate_around(&mut self, x: i32, y: i32, radius: i32) {
|
|
let (cx, cy, _, _) = self.chunk_at(x, y);
|
|
for dy in -radius..=radius {
|
|
for dx in -radius..=radius {
|
|
self.set_chunk_active(cx + dx, cy + dy, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn deactivate_all(&mut self) {
|
|
for c in &mut self.chunks {
|
|
c.active = false;
|
|
}
|
|
}
|
|
|
|
pub fn swap_modified_flags(&mut self) {
|
|
for c in &mut self.chunks {
|
|
c.was_modified = c.modified;
|
|
c.modified = false;
|
|
}
|
|
}
|
|
|
|
pub fn any_modified(&self) -> bool {
|
|
self.chunks.iter().any(|c| c.modified)
|
|
}
|
|
|
|
pub fn any_was_modified(&self) -> bool {
|
|
self.chunks.iter().any(|c| c.was_modified)
|
|
}
|
|
|
|
pub fn active_chunks(&self) -> Vec<(usize, usize)> {
|
|
let mut out = Vec::new();
|
|
for cy in 0..self.chunks_y {
|
|
for cx in 0..self.chunks_x {
|
|
if self.chunks[cy * self.chunks_x + cx].active {
|
|
out.push((cx, cy));
|
|
}
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn chunk_bounds(&self, cx: usize, cy: usize) -> (i32, i32, i32, i32) {
|
|
let x0 = (cx * self.chunk_size) as i32;
|
|
let y0 = (cy * self.chunk_size) as i32;
|
|
let x1 = (x0 + self.chunk_size as i32).min(self.width as i32);
|
|
let y1 = (y0 + self.chunk_size as i32).min(self.height as i32);
|
|
(x0, y0, x1, y1)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn cell_active(&self, x: i32, y: i32) -> bool {
|
|
if !self.in_bounds(x, y) {
|
|
return false;
|
|
}
|
|
let (cx, cy, _, _) = self.chunk_at(x, y);
|
|
self.is_chunk_active(cx, cy)
|
|
}
|
|
|
|
pub fn chunk_cells(&self, cx: usize, cy: usize) -> Vec<(i32, i32, Cell)> {
|
|
let (x0, y0, x1, y1) = self.chunk_bounds(cx, cy);
|
|
let mut out = Vec::with_capacity((x1 - x0) as usize * (y1 - y0) as usize);
|
|
for y in y0..y1 {
|
|
for x in x0..x1 {
|
|
out.push((x, y, self.get(x, y)));
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn load_chunk_cells(&mut self, cx: usize, cy: usize, cells: &[Cell]) {
|
|
let (x0, y0, x1, y1) = self.chunk_bounds(cx, cy);
|
|
let w = (x1 - x0) as usize;
|
|
for y in y0..y1 {
|
|
for x in x0..x1 {
|
|
let i = ((y - y0) as usize) * w + (x - x0) as usize;
|
|
if let Some(cell) = cells.get(i) {
|
|
self.set(x, y, *cell);
|
|
}
|
|
}
|
|
}
|
|
self.set_chunk_active(cx as i32, cy as i32, true);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get(&self, x: i32, y: i32) -> Cell {
|
|
if !self.in_bounds(x, y) {
|
|
return Cell::new(MaterialId::Stone);
|
|
}
|
|
self.cells[self.idx(x, y)]
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set(&mut self, x: i32, y: i32, cell: Cell) {
|
|
if self.in_bounds(x, y) {
|
|
let i = (y as usize) * self.width + (x as usize);
|
|
self.cells[i] = cell;
|
|
let (cx, cy, _, _) = self.chunk_at(x, y);
|
|
let idx = self.chunk_index(cx, cy);
|
|
if let Some(c) = self.chunks.get_mut(idx) {
|
|
c.modified = true;
|
|
}
|
|
self.mark_dirty(x, y);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_material(&mut self, x: i32, y: i32, mat: MaterialId) {
|
|
if self.in_bounds(x, y) {
|
|
let i = (y as usize) * self.width + (x as usize);
|
|
self.cells[i] = Cell::new(mat);
|
|
let (cx, cy, _, _) = self.chunk_at(x, y);
|
|
let idx = self.chunk_index(cx, cy);
|
|
if let Some(c) = self.chunks.get_mut(idx) {
|
|
c.modified = true;
|
|
}
|
|
self.mark_dirty(x, y);
|
|
}
|
|
}
|
|
|
|
pub fn fill_border(&mut self, mat: MaterialId) {
|
|
for x in 0..self.width {
|
|
self.set_material(x as i32, 0, mat);
|
|
self.set_material(x as i32, (self.height - 1) as i32, mat);
|
|
}
|
|
for y in 0..self.height {
|
|
self.set_material(0, y as i32, mat);
|
|
self.set_material((self.width - 1) as i32, y as i32, mat);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn mark_dirty(&mut self, x: i32, y: i32) {
|
|
if !self.in_bounds(x, y) {
|
|
return;
|
|
}
|
|
let (cx, cy, _, _) = self.chunk_at(x, y);
|
|
self.expand_chunk_dirty(cx, cy, x, y);
|
|
let cs = self.chunk_size as i32;
|
|
let lx = x - cx * cs;
|
|
let ly = y - cy * cs;
|
|
if lx <= 1 {
|
|
self.expand_chunk_dirty(cx - 1, cy, x - 1, y);
|
|
}
|
|
if lx >= cs - 2 {
|
|
self.expand_chunk_dirty(cx + 1, cy, x + 1, y);
|
|
}
|
|
if ly <= 1 {
|
|
self.expand_chunk_dirty(cx, cy - 1, x, y - 1);
|
|
}
|
|
if ly >= cs - 2 {
|
|
self.expand_chunk_dirty(cx, cy + 1, x, y + 1);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn expand_chunk_dirty(&mut self, cx: i32, cy: i32, x: i32, y: i32) {
|
|
if cx < 0 || cy < 0 || cx >= self.chunks_x as i32 || cy >= self.chunks_y as i32 {
|
|
return;
|
|
}
|
|
if !self.in_bounds(x, y) {
|
|
return;
|
|
}
|
|
let idx = self.chunk_index(cx, cy);
|
|
let min_x = (x - 1).max(0);
|
|
let min_y = (y - 1).max(0);
|
|
let max_x = (x + 1).min(self.width as i32 - 1);
|
|
let max_y = (y + 1).min(self.height as i32 - 1);
|
|
let chunk = &mut self.chunks[idx];
|
|
match chunk.dirty {
|
|
None => chunk.dirty = Some((min_x, min_y, max_x, max_y)),
|
|
Some((dx0, dy0, dx1, dy1)) => {
|
|
chunk.dirty = Some((
|
|
dx0.min(min_x),
|
|
dy0.min(min_y),
|
|
dx1.max(max_x),
|
|
dy1.max(max_y),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn cells_swap(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
|
|
if !self.in_bounds(x1, y1) || !self.in_bounds(x2, y2) {
|
|
return;
|
|
}
|
|
let i1 = self.idx(x1, y1);
|
|
let i2 = self.idx(x2, y2);
|
|
let tmp = self.cells[i1];
|
|
self.cells[i1] = self.cells[i2];
|
|
self.cells[i2] = tmp;
|
|
self.cells[i2].updated_this_tick = true;
|
|
self.mark_dirty(x1, y1);
|
|
self.mark_dirty(x2, y2);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_cell_index(&mut self, i: usize, cell: Cell) {
|
|
self.cells[i] = cell;
|
|
let x = (i % self.width) as i32;
|
|
let y = (i / self.width) as i32;
|
|
self.mark_dirty(x, y);
|
|
}
|
|
|
|
pub fn reset_tick_flags(&mut self) {
|
|
for cy in 0..self.chunks_y {
|
|
for cx in 0..self.chunks_x {
|
|
if !self.chunks[cy * self.chunks_x + cx].active {
|
|
continue;
|
|
}
|
|
let (x0, y0, x1, y1) = self.chunk_bounds(cx, cy);
|
|
for y in y0..y1 {
|
|
let row = y as usize * self.width;
|
|
for x in x0..x1 {
|
|
self.cells[row + x as usize].updated_this_tick = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn save_chunk(&self, path: &str, cx: i32, cy: i32) -> io::Result<()> {
|
|
if cx < 0 || cy < 0 || cx >= self.chunks_x as i32 || cy >= self.chunks_y as i32 {
|
|
return Err(io::Error::other("chunk out of bounds"));
|
|
}
|
|
let (x0, y0, x1, y1) = self.chunk_bounds(cx as usize, cy as usize);
|
|
let w = (x1 - x0) as usize;
|
|
let h = (y1 - y0) as usize;
|
|
let mut bytes = Vec::with_capacity(w * h * 12);
|
|
for y in y0..y1 {
|
|
for x in x0..x1 {
|
|
bytes.extend_from_slice(&self.get(x, y).to_bytes());
|
|
}
|
|
}
|
|
let dir = Path::new(path);
|
|
if let Some(parent) = dir.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
std::fs::write(path, bytes)
|
|
}
|
|
|
|
pub fn load_chunk(&mut self, path: &str, cx: i32, cy: i32) -> io::Result<()> {
|
|
if cx < 0 || cy < 0 || cx >= self.chunks_x as i32 || cy >= self.chunks_y as i32 {
|
|
return Err(io::Error::other("chunk out of bounds"));
|
|
}
|
|
let data = std::fs::read(path)?;
|
|
let (x0, y0, x1, y1) = self.chunk_bounds(cx as usize, cy as usize);
|
|
let w = (x1 - x0) as usize;
|
|
let h = (y1 - y0) as usize;
|
|
let expected = w * h * 12;
|
|
if data.len() != expected {
|
|
return Err(io::Error::other("chunk file size mismatch"));
|
|
}
|
|
let mut i = 0usize;
|
|
for y in y0..y1 {
|
|
for x in x0..x1 {
|
|
let cell = Cell::from_bytes(&data[i * 12..(i + 1) * 12]);
|
|
self.set(x, y, cell);
|
|
i += 1;
|
|
}
|
|
}
|
|
self.set_chunk_active(cx, cy, true);
|
|
Ok(())
|
|
}
|
|
}
|