Test feature generation
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"model": {
|
||||
"simple": "iron_ore"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"model": {
|
||||
"simple": "leaves"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"model": {
|
||||
"simple": "log_side"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 347 B |
+1
-1
@@ -1,5 +1,5 @@
|
||||
#![feature(duration_constants)]
|
||||
#![allow(clippy::new_without_default)]
|
||||
#![allow(clippy::new_without_default, clippy::let_unit_value)]
|
||||
|
||||
use std::{process::ExitCode, sync::Arc, time::Instant};
|
||||
|
||||
|
||||
+13
-7
@@ -46,12 +46,16 @@ impl WorldMeshState {
|
||||
&mut self,
|
||||
allocators: &Arc<Allocators>,
|
||||
coords: ChunkCoords,
|
||||
world: &World,
|
||||
world: &mut World,
|
||||
) -> Result<Option<&ChunkMesh>, Error> {
|
||||
match self.chunks.entry(coords) {
|
||||
Entry::Occupied(entry) => Ok(Some(entry.into_mut())),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Some(chunk) = world.get(coords) {
|
||||
if let Some(chunk) = world.get_mut(coords) {
|
||||
if chunk.clear_dirty() {
|
||||
self.chunks.remove(&coords);
|
||||
}
|
||||
|
||||
match self.chunks.entry(coords) {
|
||||
Entry::Occupied(entry) => Ok(Some(entry.into_mut())),
|
||||
Entry::Vacant(entry) => {
|
||||
let mesh = ChunkMesh::from_chunk(
|
||||
allocators,
|
||||
&self.block_model_registry,
|
||||
@@ -59,10 +63,12 @@ impl WorldMeshState {
|
||||
chunk,
|
||||
)?;
|
||||
Ok(Some(entry.insert(mesh)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.chunks.remove(&coords);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::NeighborQuery;
|
||||
pub struct Chunk {
|
||||
blocks: [u8; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
||||
height_map: [u32; Self::SIZE * Self::SIZE],
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -12,6 +13,7 @@ pub struct ChunkCoords {
|
||||
}
|
||||
|
||||
impl ChunkCoords {
|
||||
#[inline]
|
||||
pub const fn new(x: i32, z: i32) -> Self {
|
||||
Self { x, z }
|
||||
}
|
||||
@@ -24,6 +26,12 @@ impl ChunkCoords {
|
||||
let (cx, cz) = self.origin_block();
|
||||
(cx + x as i32, cz + z as i32)
|
||||
}
|
||||
|
||||
pub fn from_block_coords(x: i32, z: i32) -> Self {
|
||||
let cx = if x < 0 { x - Chunk::SIZE as i32 + 1 } else { x } / Chunk::SIZE as i32;
|
||||
let cz = if z < 0 { z - Chunk::SIZE as i32 + 1 } else { z } / Chunk::SIZE as i32;
|
||||
Self::new(cx, cz)
|
||||
}
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
@@ -34,9 +42,16 @@ impl Chunk {
|
||||
Self {
|
||||
blocks: [0; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
||||
height_map: [0; Self::SIZE * Self::SIZE],
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_dirty(&mut self) -> bool {
|
||||
let dirty = self.dirty;
|
||||
self.dirty = false;
|
||||
dirty
|
||||
}
|
||||
|
||||
pub fn fill_layer<F: Fn(u32, u32) -> u8>(&mut self, y: u32, f: F) {
|
||||
for x in 0..Self::SIZE as u32 {
|
||||
for z in 0..Self::SIZE as u32 {
|
||||
@@ -50,7 +65,12 @@ impl Chunk {
|
||||
}
|
||||
|
||||
pub fn set_id(&mut self, x: u32, y: u32, z: u32, id: u8) {
|
||||
if x >= Self::SIZE as u32 || z >= Self::SIZE as u32 || y >= Self::HEIGHT as u32 {
|
||||
panic!("Invalid coords: {x}, {y}, {z}");
|
||||
}
|
||||
|
||||
self.blocks[Self::to_index(x, y, z)] = id;
|
||||
self.dirty = true;
|
||||
|
||||
if id != 0 && y >= self.top_height_at(x, z) {
|
||||
self.set_top_height_at(x, z, y + 1);
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
use std::{
|
||||
mem,
|
||||
ops::{Add, Deref, Range},
|
||||
};
|
||||
|
||||
use super::{level::BoundingBox, Chunk, ChunkCoords};
|
||||
|
||||
pub enum FeaturePlacementFilter {
|
||||
ReplaceAll,
|
||||
ReplaceAir,
|
||||
Replace(u8),
|
||||
}
|
||||
|
||||
pub struct FeatureLayer {
|
||||
x_range: Range<i32>,
|
||||
z_range: Range<i32>,
|
||||
content: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct FeatureBuilder {
|
||||
y_range: Range<i32>,
|
||||
layers: Vec<FeatureLayer>,
|
||||
}
|
||||
|
||||
pub struct Feature {
|
||||
width: u32,
|
||||
height: u32,
|
||||
depth: u32,
|
||||
filter: FeaturePlacementFilter,
|
||||
content: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct PositionedFeature {
|
||||
inner: Feature,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
}
|
||||
|
||||
impl Deref for PositionedFeature {
|
||||
type Target = Feature;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl FeaturePlacementFilter {
|
||||
pub fn should_place(&self, from: u8) -> bool {
|
||||
match self {
|
||||
Self::ReplaceAir => from == 0,
|
||||
Self::ReplaceAll => true,
|
||||
&Self::Replace(id) => from == id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PositionedFeature {
|
||||
pub fn aabb(&self) -> BoundingBox {
|
||||
BoundingBox {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
w: self.width as i32,
|
||||
h: self.height as i32,
|
||||
d: self.depth as i32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place(&self, coords: ChunkCoords, chunk: &mut Chunk) {
|
||||
let in_chunk_aabb = self.aabb().in_chunk(coords);
|
||||
let Some(in_chunk_aabb) = in_chunk_aabb else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (sx, sz) = coords.origin_block();
|
||||
|
||||
for ix in 0..in_chunk_aabb.w {
|
||||
// Coords in chunk
|
||||
let cx = ix + in_chunk_aabb.x;
|
||||
// Coords in feature
|
||||
let fx = cx as i32 + sx - self.x;
|
||||
assert!(fx >= 0);
|
||||
assert!(fx < self.width as i32);
|
||||
|
||||
for iy in 0..in_chunk_aabb.h {
|
||||
let cy = iy + in_chunk_aabb.y;
|
||||
|
||||
for iz in 0..in_chunk_aabb.d {
|
||||
let cz = iz + in_chunk_aabb.z;
|
||||
let fz = cz as i32 + sz - self.z;
|
||||
assert!(fz >= 0);
|
||||
assert!(fz < self.depth as i32);
|
||||
|
||||
let chunk_id = chunk.get_id(cx, cy, cz);
|
||||
let feature_id = self.get(fx as u32, iy, fz as u32);
|
||||
|
||||
if self.filter.should_place(chunk_id) {
|
||||
chunk.set_id(cx, cy, cz, feature_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Feature {
|
||||
pub fn position(self, x: i32, y: i32, z: i32) -> PositionedFeature {
|
||||
PositionedFeature {
|
||||
inner: self,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position_centered_horizontally(self, x: i32, y: i32, z: i32) -> PositionedFeature {
|
||||
let x = x - self.width as i32 / 2;
|
||||
let z = z - self.width as i32 / 2;
|
||||
PositionedFeature {
|
||||
inner: self,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, x: u32, y: u32, z: u32) -> u8 {
|
||||
let i = x + (z + y * self.depth) * self.width;
|
||||
self.content[i as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl FeatureLayer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
x_range: 0..0,
|
||||
z_range: 0..0,
|
||||
content: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> u32 {
|
||||
range_width(&self.x_range)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn depth(&self) -> u32 {
|
||||
range_width(&self.z_range)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> (u32, u32) {
|
||||
(self.width(), self.depth())
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: i32, z: i32, id: u8) {
|
||||
self.adjust_for_placement(x, z);
|
||||
let i = self.index(x, z);
|
||||
self.content[i] = id;
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, x: i32, z: i32, from: u8, to: u8) {
|
||||
self.adjust_for_placement(x, z);
|
||||
if self.get(x, z) == from {
|
||||
self.set(x, z, to);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, x: i32, z: i32) -> u8 {
|
||||
self.content[self.index(x, z)]
|
||||
}
|
||||
|
||||
pub fn try_get(&self, x: i32, z: i32) -> Option<u8> {
|
||||
if !self.x_range.contains(&x) || !self.z_range.contains(&z) {
|
||||
None
|
||||
} else {
|
||||
Some(self.get(x, z))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, x_range: Range<i32>, z_range: Range<i32>) {
|
||||
let new_x_range = x_range.start.min(self.x_range.start)..x_range.end.max(self.x_range.end);
|
||||
let new_z_range = z_range.start.min(self.z_range.start)..z_range.end.max(self.z_range.end);
|
||||
|
||||
if new_x_range == self.x_range && new_z_range == self.z_range {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_width = range_width(&new_x_range);
|
||||
let new_depth = range_width(&new_z_range);
|
||||
|
||||
let old_width = self.width();
|
||||
let old_depth = self.depth();
|
||||
|
||||
let shift_x = (self.x_range.start - new_x_range.start) as u32;
|
||||
let shift_z = (self.z_range.start - new_z_range.start) as u32;
|
||||
|
||||
let old = mem::replace(&mut self.content, vec![0; (new_width * new_depth) as usize]);
|
||||
|
||||
for abs_z in 0..old_depth {
|
||||
let new_abs_z = shift_z + abs_z;
|
||||
let abs_x = 0..old_width as usize;
|
||||
let new_abs_x = advance_range(&abs_x, shift_x as usize);
|
||||
|
||||
let old_i = advance_range(&abs_x, (abs_z * old_width) as usize);
|
||||
let new_i = advance_range(&new_abs_x, (new_abs_z * new_width) as usize);
|
||||
|
||||
self.content[new_i].copy_from_slice(&old[old_i]);
|
||||
}
|
||||
|
||||
self.x_range = new_x_range;
|
||||
self.z_range = new_z_range;
|
||||
}
|
||||
|
||||
pub fn adjust_for_placement(&mut self, x: i32, z: i32) {
|
||||
// Start x,z ranges: 0..0
|
||||
let new_x_range = adjust_range(x, &self.x_range);
|
||||
let new_z_range = adjust_range(z, &self.z_range);
|
||||
|
||||
self.reserve(new_x_range, new_z_range);
|
||||
}
|
||||
|
||||
fn index(&self, x: i32, z: i32) -> usize {
|
||||
assert!(self.x_range.contains(&x));
|
||||
assert!(self.z_range.contains(&z));
|
||||
|
||||
let abs_x = (x - self.x_range.start) as usize;
|
||||
let abs_z = (z - self.z_range.start) as usize;
|
||||
|
||||
abs_x + abs_z * self.width() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl FeatureBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
y_range: 0..0,
|
||||
layers: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
(self.y_range.end - self.y_range.start) as u32
|
||||
}
|
||||
|
||||
pub fn reserve_height(&mut self, y_range: Range<i32>) {
|
||||
let new_y_range = y_range.start.min(self.y_range.start)..y_range.end.max(self.y_range.end);
|
||||
|
||||
if new_y_range == self.y_range {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_height = range_width(&new_y_range);
|
||||
|
||||
let shift_y = (self.y_range.start - new_y_range.start) as usize;
|
||||
|
||||
// -1..3 -> -2..3, shift_y = 1
|
||||
// [??, -1, 0, 1, 2]
|
||||
// [-2, -1, 0, 1, 2]
|
||||
self.layers
|
||||
.resize_with(new_height as usize, FeatureLayer::new);
|
||||
self.layers.rotate_right(shift_y);
|
||||
self.y_range = new_y_range;
|
||||
}
|
||||
|
||||
pub fn adjust_height_for_placement(&mut self, y: i32) {
|
||||
let new_y_range = adjust_range(y, &self.y_range);
|
||||
self.reserve_height(new_y_range);
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: i32, y: i32, z: i32, id: u8) {
|
||||
self.adjust_height_for_placement(y);
|
||||
let i = (y - self.y_range.start) as usize;
|
||||
self.layers[i].set(x, z, id);
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, x: i32, y: i32, z: i32, from: u8, to: u8) {
|
||||
self.adjust_height_for_placement(y);
|
||||
let i = (y - self.y_range.start) as usize;
|
||||
self.layers[i].replace(x, z, from, to);
|
||||
}
|
||||
|
||||
pub fn get(&self, x: i32, y: i32, z: i32) -> u8 {
|
||||
assert!(self.y_range.contains(&y));
|
||||
self.layers[(y - self.y_range.start) as usize].get(x, z)
|
||||
}
|
||||
|
||||
pub fn build(self, filter: FeaturePlacementFilter) -> Feature {
|
||||
assert!(!self.layers.is_empty());
|
||||
|
||||
let x_range = largest_range_bounds(self.layers.iter().map(|l| &l.x_range));
|
||||
let z_range = largest_range_bounds(self.layers.iter().map(|l| &l.z_range));
|
||||
let width = range_width(&x_range);
|
||||
let depth = range_width(&z_range);
|
||||
let height = self.layers.len() as u32;
|
||||
|
||||
let mut content = vec![0; (width * depth) as usize * self.layers.len()];
|
||||
|
||||
for (y, layer) in self.layers.into_iter().enumerate() {
|
||||
for x in x_range.clone() {
|
||||
for z in z_range.clone() {
|
||||
let id = layer.try_get(x, z).unwrap_or(0);
|
||||
|
||||
let ix = (x - x_range.start) as usize;
|
||||
let iz = (z - z_range.start) as usize;
|
||||
let i = ix + (iz + y * depth as usize) * width as usize;
|
||||
|
||||
content[i] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Feature {
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
content,
|
||||
filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn largest_range_bounds<'a, I: IntoIterator<Item = &'a Range<i32>>>(ranges: I) -> Range<i32> {
|
||||
let mut result = 0..0;
|
||||
for range in ranges {
|
||||
if range.start < result.start {
|
||||
result.start = range.start;
|
||||
}
|
||||
if range.end > result.end {
|
||||
result.end = range.end;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn advance_range<T: Copy + Add<Output = T>>(range: &Range<T>, x: T) -> Range<T> {
|
||||
range.start + x..range.end + x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn adjust_range(x: i32, axis: &Range<i32>) -> Range<i32> {
|
||||
let mut new_x = axis.clone();
|
||||
if x < axis.start {
|
||||
new_x.start = x;
|
||||
} else if x >= axis.end {
|
||||
new_x.end = x + 1;
|
||||
}
|
||||
new_x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn range_width(axis: &Range<i32>) -> u32 {
|
||||
assert!(axis.end >= axis.start);
|
||||
(axis.end - axis.start) as u32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FeatureBuilder, FeatureLayer};
|
||||
|
||||
#[test]
|
||||
fn featuer_build() {
|
||||
let mut builder = FeatureBuilder::new();
|
||||
|
||||
builder.set(0, 0, 0, 1);
|
||||
builder.set(0, 1, 0, 1);
|
||||
for y in 2..5 {
|
||||
for x in -1..=1 {
|
||||
for z in -1..=1 {
|
||||
let id = if x == 0 && z == 0 { 3 } else { 2 };
|
||||
builder.set(x, y, z, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should yield a 3x5x3 feature
|
||||
let feature = builder.build();
|
||||
assert_eq!(feature.width, 3);
|
||||
assert_eq!(feature.height, 5);
|
||||
assert_eq!(feature.depth, 3);
|
||||
assert_eq!(
|
||||
&feature.content[..],
|
||||
&[
|
||||
// y = 0
|
||||
0, 0, 0, // z = -1
|
||||
0, 1, 0, // z = 0
|
||||
0, 0, 0, // z = 1
|
||||
// y = 1
|
||||
0, 0, 0, // z = -1
|
||||
0, 1, 0, // z = 0
|
||||
0, 0, 0, // z = 1
|
||||
// y = 2
|
||||
2, 2, 2, // z = -1
|
||||
2, 3, 2, // z = 0
|
||||
2, 2, 2, // z = 1
|
||||
// y = 3
|
||||
2, 2, 2, // z = -1
|
||||
2, 3, 2, // z = 0
|
||||
2, 2, 2, // z = 1
|
||||
// y = 4
|
||||
2, 2, 2, // z = -1
|
||||
2, 3, 2, // z = 0
|
||||
2, 2, 2, // z = 1
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feature_layer_resize() {
|
||||
let mut layer = FeatureLayer::new();
|
||||
assert_eq!(layer.size(), (0, 0));
|
||||
|
||||
// 0x0 -> 1x1
|
||||
layer.set(0, 0, 1);
|
||||
assert_eq!(layer.size(), (1, 1));
|
||||
assert_eq!(layer.get(0, 0), 1);
|
||||
|
||||
// 1x1 -> 3x1
|
||||
layer.set(-2, 0, 2);
|
||||
assert_eq!(layer.size(), (3, 1));
|
||||
assert_eq!(layer.get(0, 0), 1);
|
||||
assert_eq!(layer.get(-1, 0), 0);
|
||||
assert_eq!(layer.get(-2, 0), 2);
|
||||
|
||||
// 3x1 -> 3x2
|
||||
layer.set(-1, -1, 3);
|
||||
assert_eq!(layer.size(), (3, 2));
|
||||
assert_eq!(
|
||||
&layer.content[..],
|
||||
&[
|
||||
0, 3, 0, // z = -1
|
||||
2, 0, 1, // z = 0
|
||||
]
|
||||
);
|
||||
|
||||
// 3x2 -> 4x4
|
||||
layer.set(-3, -3, 4);
|
||||
assert_eq!(layer.size(), (4, 4));
|
||||
assert_eq!(
|
||||
&layer.content[..],
|
||||
&[
|
||||
4, 0, 0, 0, // z = -3
|
||||
0, 0, 0, 0, // z = -2
|
||||
0, 0, 3, 0, // z = -1
|
||||
0, 2, 0, 1, // z = 0
|
||||
]
|
||||
);
|
||||
|
||||
// 4x4 -> 5x6
|
||||
layer.set(1, 2, 5);
|
||||
assert_eq!(layer.size(), (5, 6));
|
||||
assert_eq!(
|
||||
&layer.content[..],
|
||||
&[
|
||||
4, 0, 0, 0, 0, // z = -3
|
||||
0, 0, 0, 0, 0, // z = -2
|
||||
0, 0, 3, 0, 0, // z = -1
|
||||
0, 2, 0, 1, 0, // z = 0
|
||||
0, 0, 0, 0, 0, // z = 1
|
||||
0, 0, 0, 0, 5, // z = 2
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ impl ChunkGenerator for NoiseChunkGenerator {
|
||||
) {
|
||||
let grass = block_registry.get("grass").unwrap_or(0);
|
||||
let dirt = block_registry.get("dirt").unwrap_or(0);
|
||||
let stone = block_registry.get("stone").unwrap_or(0);
|
||||
// let stone = block_registry.get("stone").unwrap_or(0);
|
||||
|
||||
for ix in 0..Chunk::SIZE as u32 {
|
||||
for iz in 0..Chunk::SIZE as u32 {
|
||||
@@ -37,7 +37,8 @@ impl ChunkGenerator for NoiseChunkGenerator {
|
||||
let id = if y + 1 == h {
|
||||
grass
|
||||
} else if h - y > stone_depth {
|
||||
stone
|
||||
0
|
||||
// stone
|
||||
} else {
|
||||
dirt
|
||||
};
|
||||
|
||||
+226
-18
@@ -1,20 +1,95 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::render::asset::block::BlockRegistry;
|
||||
|
||||
use super::{Chunk, ChunkCoords, ChunkGenerator};
|
||||
use super::{
|
||||
feature::{FeatureBuilder, FeaturePlacementFilter, PositionedFeature},
|
||||
Chunk, ChunkCoords, ChunkGenerator,
|
||||
};
|
||||
|
||||
pub struct World {
|
||||
chunks: HashMap<ChunkCoords, Chunk>,
|
||||
block_registry: BlockRegistry,
|
||||
|
||||
// populated_chunk_features: HashSet<ChunkCoords>,
|
||||
unpopulated_chunks: HashSet<ChunkCoords>,
|
||||
pending_chunk_features: HashMap<ChunkCoords, Vec<Arc<PositionedFeature>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BoundingBox {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
pub d: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct InChunkBoundingBox {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub z: u32,
|
||||
pub w: u32,
|
||||
pub h: u32,
|
||||
pub d: u32,
|
||||
}
|
||||
|
||||
impl BoundingBox {
|
||||
pub fn map_chunks<F: FnMut(ChunkCoords)>(self, mut f: F) {
|
||||
let start = ChunkCoords::from_block_coords(self.x, self.z);
|
||||
let end = ChunkCoords::from_block_coords(
|
||||
self.x + self.w + Chunk::SIZE as i32 - 1,
|
||||
self.z + self.d + Chunk::SIZE as i32 - 1,
|
||||
);
|
||||
|
||||
for cx in start.x..end.x {
|
||||
for cz in start.z..end.z {
|
||||
f(ChunkCoords::new(cx, cz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_chunk(self, chunk: ChunkCoords) -> Option<InChunkBoundingBox> {
|
||||
let cx = chunk.x * Chunk::SIZE as i32;
|
||||
let cz = chunk.z * Chunk::SIZE as i32;
|
||||
|
||||
let sx = (self.x - cx).clamp(0, Chunk::SIZE as i32) as u32;
|
||||
let sz = (self.z - cz).clamp(0, Chunk::SIZE as i32) as u32;
|
||||
|
||||
let dx = (self.x + self.w - cx).clamp(0, Chunk::SIZE as i32) as u32;
|
||||
let dz = (self.z + self.d - cz).clamp(0, Chunk::SIZE as i32) as u32;
|
||||
|
||||
let sy = self.y.clamp(0, Chunk::HEIGHT as i32) as u32;
|
||||
let dy = (self.y + self.h).clamp(0, Chunk::HEIGHT as i32) as u32;
|
||||
|
||||
if sx < dx && sy < dy && sz < dz {
|
||||
Some(InChunkBoundingBox {
|
||||
x: sx,
|
||||
y: sy,
|
||||
z: sz,
|
||||
w: dx - sx,
|
||||
h: dy - sy,
|
||||
d: dz - sz,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(block_registry: BlockRegistry) -> Self {
|
||||
Self {
|
||||
chunks: HashMap::new(),
|
||||
pending_chunk_features: HashMap::new(),
|
||||
unpopulated_chunks: HashSet::new(),
|
||||
block_registry,
|
||||
}
|
||||
}
|
||||
@@ -25,36 +100,169 @@ impl World {
|
||||
|
||||
let mut chunk = Chunk::empty();
|
||||
g.generate_terrain(coords, &mut chunk, &self.block_registry);
|
||||
|
||||
// Populate chunk with pending features
|
||||
if let Some(pending) = self.pending_chunk_features.remove(&coords) {
|
||||
for pending in pending {
|
||||
pending.place(coords, &mut chunk);
|
||||
}
|
||||
}
|
||||
|
||||
self.unpopulated_chunks.insert(coords);
|
||||
self.chunks.insert(coords, chunk);
|
||||
}
|
||||
|
||||
fn populate_terrain<G: ChunkGenerator + ?Sized>(&mut self, coords: ChunkCoords, g: &mut G) {
|
||||
if !self.chunks.contains_key(&coords) {
|
||||
self.generate(coords, g);
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_terrain_at<G: ChunkGenerator + ?Sized>(
|
||||
&mut self,
|
||||
center: ChunkCoords,
|
||||
radius: i32,
|
||||
g: &mut G,
|
||||
) {
|
||||
for cx in -radius..=radius {
|
||||
for cz in -radius..=radius {
|
||||
let coords = ChunkCoords::new(center.x + cx, center.z + cz);
|
||||
self.populate_terrain(coords, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn place_feature(&mut self, feature: Arc<PositionedFeature>) {
|
||||
let aabb = feature.aabb();
|
||||
aabb.map_chunks(|coords| {
|
||||
if let Some(chunk) = self.chunks.get_mut(&coords) {
|
||||
feature.place(coords, chunk);
|
||||
} else {
|
||||
let pending = self.pending_chunk_features.entry(coords).or_default();
|
||||
pending.push(feature.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn populate_features(&mut self) {
|
||||
let mut features = vec![];
|
||||
|
||||
let log = self.block_registry.get("log").unwrap_or(0);
|
||||
let leaves = self.block_registry.get("leaves").unwrap_or(0);
|
||||
|
||||
for coords in self.unpopulated_chunks.drain() {
|
||||
let Some(chunk) = self.chunks.get_mut(&coords) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// For each unpopulated chunk, generate a set of per-chunk features
|
||||
let trees = rand::random_range(3..8);
|
||||
|
||||
for _ in 0..trees {
|
||||
// Generate trees
|
||||
// TODO collision detection for these
|
||||
let trunk_height = rand::random_range(5..8);
|
||||
let leaves_radius = trunk_height / 2 + rand::random_range(0..2);
|
||||
|
||||
let fx = rand::random_range(0..Chunk::SIZE as u32);
|
||||
let fz = rand::random_range(0..Chunk::SIZE as u32);
|
||||
let fy = chunk.top_height_at(fx, fz);
|
||||
|
||||
let mut builder = FeatureBuilder::new();
|
||||
|
||||
for y in 0..trunk_height {
|
||||
builder.set(0, y, 0, log);
|
||||
}
|
||||
for x in -leaves_radius..=leaves_radius {
|
||||
for y in -leaves_radius..=leaves_radius {
|
||||
for z in -leaves_radius..=leaves_radius {
|
||||
let p = Vec3::new(x as f32, y as f32, z as f32);
|
||||
if p.distance(Vec3::ZERO) > leaves_radius as f32 - 0.1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.replace(
|
||||
x,
|
||||
y + trunk_height / 2 + leaves_radius / 2 + 1,
|
||||
z,
|
||||
0,
|
||||
leaves,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (root_x, root_z) = coords.block_coords(fx, fz);
|
||||
|
||||
features.push(Arc::new(
|
||||
builder
|
||||
.build(FeaturePlacementFilter::ReplaceAir)
|
||||
.position_centered_horizontally(root_x, fy as i32, root_z),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Place features
|
||||
for feature in features {
|
||||
self.place_feature(feature);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, coords: ChunkCoords) -> Option<&Chunk> {
|
||||
self.chunks.get(&coords)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, coords: ChunkCoords) -> Option<&mut Chunk> {
|
||||
self.chunks.get_mut(&coords)
|
||||
}
|
||||
|
||||
pub fn update_with_camera<G: ChunkGenerator + ?Sized>(
|
||||
&mut self,
|
||||
camera_position: Vec3,
|
||||
g: &mut G,
|
||||
) {
|
||||
const VIEW_DISTANCE: i32 = 5;
|
||||
const VIEW_DISTANCE: i32 = 1;
|
||||
|
||||
fn cpos(x: f32) -> i32 {
|
||||
(x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32
|
||||
}
|
||||
let camera_chunk =
|
||||
ChunkCoords::from_block_coords(camera_position.x as i32, camera_position.z as i32);
|
||||
|
||||
let camera_cx = cpos(camera_position.x);
|
||||
let camera_cz = cpos(camera_position.z);
|
||||
|
||||
for cx in camera_cx - VIEW_DISTANCE..=camera_cx + VIEW_DISTANCE {
|
||||
for cz in camera_cz - VIEW_DISTANCE..=camera_cz + VIEW_DISTANCE {
|
||||
let coords = ChunkCoords::new(cx, cz);
|
||||
|
||||
if !self.chunks.contains_key(&coords) {
|
||||
self.generate(coords, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.populate_terrain_at(camera_chunk, VIEW_DISTANCE, g);
|
||||
self.populate_features();
|
||||
// self.populate_features_at(camera_chunk, VIEW_DISTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::world::ChunkCoords;
|
||||
|
||||
use super::BoundingBox;
|
||||
|
||||
#[test]
|
||||
fn test_bounding_box_chunks() {
|
||||
let aabb = BoundingBox {
|
||||
x: -1,
|
||||
y: 31,
|
||||
z: 15,
|
||||
w: 3,
|
||||
h: 10,
|
||||
d: 3,
|
||||
};
|
||||
|
||||
let mut v0 = false;
|
||||
let mut v1 = false;
|
||||
|
||||
aabb.map_chunks(|coords| {
|
||||
if coords == ChunkCoords::new(-1, 0) {
|
||||
v0 = true;
|
||||
} else if coords == ChunkCoords::new(0, 0) {
|
||||
v1 = true;
|
||||
} else {
|
||||
panic!("Unexpected chunk coords: {coords:?}");
|
||||
}
|
||||
});
|
||||
|
||||
assert!(v0 && v1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
pub mod chunk;
|
||||
pub mod feature;
|
||||
pub mod generator;
|
||||
pub mod level;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user