Added chonks

Former-commit-id: a62fb321dbfb7541feaa9de4e641db9887b061fd
This commit is contained in:
Joshua Barretto 2019-05-17 18:44:30 +01:00
parent fa052a22b7
commit 91184356e7
16 changed files with 686 additions and 344 deletions

View File

@ -46,7 +46,7 @@ pub struct Client {
entity: EcsEntity,
view_distance: u64,
pending_chunks: HashMap<Vec3<i32>, Instant>,
pending_chunks: HashMap<Vec2<i32>, Instant>,
}
impl Client {
@ -221,18 +221,16 @@ impl Client {
'outer: for dist in 0..10 {
for i in chunk_pos.x - dist..chunk_pos.x + dist + 1 {
for j in chunk_pos.y - dist..chunk_pos.y + dist + 1 {
for k in 0..6 {
let key = Vec3::new(i, j, k);
if self.state.terrain().get_key(key).is_none()
&& !self.pending_chunks.contains_key(&key)
{
if self.pending_chunks.len() < 4 {
self.postbox
.send_message(ClientMsg::TerrainChunkRequest { key });
self.pending_chunks.insert(key, Instant::now());
} else {
break 'outer;
}
let key = Vec2::new(i, j);
if self.state.terrain().get_key(key).is_none()
&& !self.pending_chunks.contains_key(&key)
{
if self.pending_chunks.len() < 4 {
self.postbox
.send_message(ClientMsg::TerrainChunkRequest { key });
self.pending_chunks.insert(key, Instant::now());
} else {
break 'outer;
}
}
}

View File

@ -22,7 +22,7 @@ pub enum ClientMsg {
dir: comp::phys::Dir,
},
TerrainChunkRequest {
key: Vec3<i32>,
key: Vec2<i32>,
},
Disconnect,
}

View File

@ -34,7 +34,7 @@ pub enum ServerMsg {
animation_history: comp::AnimationHistory,
},
TerrainChunkUpdate {
key: Vec3<i32>,
key: Vec2<i32>,
chunk: Box<TerrainChunk>,
},
Disconnect,

View File

@ -42,9 +42,9 @@ pub struct DeltaTime(pub f32);
const MAX_DELTA_TIME: f32 = 0.15;
pub struct Changes {
pub new_chunks: HashSet<Vec3<i32>>,
pub changed_chunks: HashSet<Vec3<i32>>,
pub removed_chunks: HashSet<Vec3<i32>>,
pub new_chunks: HashSet<Vec2<i32>>,
pub changed_chunks: HashSet<Vec2<i32>>,
pub removed_chunks: HashSet<Vec2<i32>>,
}
impl Changes {
@ -181,7 +181,7 @@ impl State {
}
/// Insert the provided chunk into this state's terrain.
pub fn insert_chunk(&mut self, key: Vec3<i32>, chunk: TerrainChunk) {
pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: TerrainChunk) {
if self
.ecs
.write_resource::<TerrainMap>()
@ -195,7 +195,7 @@ impl State {
}
/// Remove the chunk with the given key from this state's terrain, if it exists
pub fn remove_chunk(&mut self, key: Vec3<i32>) {
pub fn remove_chunk(&mut self, key: Vec2<i32>) {
if self
.ecs
.write_resource::<TerrainMap>()

View File

@ -4,7 +4,7 @@ use vek::*;
// Crate
use crate::vol::Vox;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Block {
kind: u8,
color: [u8; 3],

139
common/src/terrain/chonk.rs Normal file
View File

@ -0,0 +1,139 @@
use vek::*;
use serde_derive::{Deserialize, Serialize};
use crate::{
vol::{
BaseVol,
ReadVol,
WriteVol,
VolSize,
},
volumes::chunk::{Chunk, ChunkErr},
};
use super::{
block::Block,
TerrainChunkSize,
TerrainChunkMeta,
};
#[derive(Debug)]
pub enum ChonkError {
ChunkError(ChunkErr),
OutOfBounds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chonk {
z_offset: i32,
sub_chunks: Vec<SubChunk>,
below: Block,
above: Block,
meta: TerrainChunkMeta,
}
impl Chonk {
pub fn new(z_offset: i32, below: Block, above: Block, meta: TerrainChunkMeta) -> Self {
Self {
z_offset,
sub_chunks: Vec::new(),
below,
above,
meta,
}
}
fn sub_chunk_idx(&self, z: i32) -> usize {
((z - self.z_offset) as u32 / TerrainChunkSize::SIZE.z as u32) as usize
}
}
impl BaseVol for Chonk {
type Vox = Block;
type Err = ChonkError;
}
impl ReadVol for Chonk {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&Block, ChonkError> {
if pos.z < self.z_offset {
// Below the terrain
Ok(&self.below)
} else if pos.z >= self.z_offset + TerrainChunkSize::SIZE.z as i32 * self.sub_chunks.len() as i32 {
// Above the terrain
Ok(&self.above)
} else {
// Within the terrain
let sub_chunk_idx = self.sub_chunk_idx(pos.z);
match &self.sub_chunks[sub_chunk_idx] { // Can't fail
SubChunk::Homogeneous(block) => Ok(block),
SubChunk::Heterogeneous(chunk) => {
let rpos = pos - Vec3::unit_z() * (
self.z_offset +
sub_chunk_idx as i32 * TerrainChunkSize::SIZE.z as i32
);
chunk
.get(rpos)
.map_err(|err| ChonkError::ChunkError(err))
},
}
}
}
}
impl WriteVol for Chonk {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, block: Block) -> Result<(), ChonkError> {
if pos.z < self.z_offset {
Err(ChonkError::OutOfBounds)
} else {
let sub_chunk_idx = self.sub_chunk_idx(pos.z);
while self.sub_chunks.get(sub_chunk_idx).is_none() {
self.sub_chunks.push(SubChunk::Homogeneous(self.above));
}
let rpos = pos - Vec3::unit_z() * (
self.z_offset +
sub_chunk_idx as i32 * TerrainChunkSize::SIZE.z as i32
);
match &mut self.sub_chunks[sub_chunk_idx] { // Can't fail
SubChunk::Homogeneous(cblock) if *cblock == block => Ok(()),
SubChunk::Homogeneous(cblock) => {
let mut new_chunk = Chunk::filled(*cblock, ());
match new_chunk
.set(rpos, block)
.map_err(|err| {
println!("Error!! {:?}", rpos);
ChonkError::ChunkError(err)
})
{
Ok(()) => {
self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk);
Ok(())
},
Err(err) => Err(err),
}
},
SubChunk::Heterogeneous(chunk) => chunk
.set(rpos, block)
.map_err(|err| ChonkError::ChunkError(err)),
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SubChunk {
Homogeneous(Block),
Heterogeneous(Chunk<Block, TerrainChunkSize, ()>),
}
impl SubChunk {
pub fn filled(block: Block) -> Self {
SubChunk::Homogeneous(block)
}
}

View File

@ -1,12 +1,13 @@
pub mod biome;
pub mod block;
pub mod chonk;
// Reexports
pub use self::{biome::BiomeKind, block::Block};
use crate::{
vol::VolSize,
volumes::{chunk::Chunk, vol_map::VolMap},
volumes::{chunk::Chunk, vol_map_2d::VolMap2d},
};
use serde_derive::{Deserialize, Serialize};
use vek::*;
@ -41,5 +42,5 @@ impl TerrainChunkMeta {
// Terrain type aliases
pub type TerrainChunk = Chunk<Block, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainMap = VolMap<Block, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainChunk = chonk::Chonk; //Chunk<Block, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainMap = VolMap2d<TerrainChunk, TerrainChunkSize>;

View File

@ -74,10 +74,7 @@ pub trait ReadVol: BaseVol {
}
/// A volume that provides the ability to sample (i.e: clone a section of) its voxel data.
pub trait SampleVol: BaseVol
where
Self::Vox: Clone,
{
pub trait SampleVol<I>: BaseVol {
type Sample: BaseVol + ReadVol;
/// Take a sample of the volume by cloning voxels within the provided range.
///
@ -86,7 +83,7 @@ where
///
/// Note that the resultant volume has a coordinate space relative to the sample, not the
/// original volume.
fn sample(&self, range: Aabb<i32>) -> Result<Self::Sample, Self::Err>;
fn sample(&self, range: I) -> Result<Self::Sample, Self::Err>;
}
/// A volume that provides write access to its voxel data.

View File

@ -1,3 +1,4 @@
pub mod chunk;
pub mod dyna;
pub mod vol_map;
pub mod vol_map_3d;
pub mod vol_map_2d;

View File

@ -1,227 +0,0 @@
// Standard
use std::{collections::HashMap, sync::Arc};
// Library
use vek::*;
// Crate
use crate::{
terrain::TerrainChunkMeta,
vol::{BaseVol, ReadVol, SampleVol, SizedVol, VolSize, Vox, WriteVol},
volumes::{
chunk::{Chunk, ChunkErr},
dyna::{Dyna, DynaErr},
},
};
#[derive(Debug)]
pub enum VolMapErr {
NoSuchChunk,
ChunkErr(ChunkErr),
DynaErr(DynaErr),
InvalidChunkSize,
}
// V = Voxel
// S = Size (replace with a const when const generics is a thing)
// M = Chunk metadata
#[derive(Clone)]
pub struct VolMap<V: Vox, S: VolSize, M> {
chunks: HashMap<Vec3<i32>, Arc<Chunk<V, S, M>>>,
}
impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
#[inline(always)]
pub fn chunk_key(pos: Vec3<i32>) -> Vec3<i32> {
pos.map2(S::SIZE, |e, sz| {
// Horrid, but it's faster than a cheetah with a red bull blood transfusion
let log2 = (sz - 1).count_ones();
((((e as i64 + (1 << 32)) as u64) >> log2) - (1 << (32 - log2))) as i32
})
}
#[inline(always)]
pub fn chunk_offs(pos: Vec3<i32>) -> Vec3<i32> {
pos.map2(S::SIZE, |e, sz| {
// Horrid, but it's even faster than the aforementioned cheetah
(((e as i64 + (1 << 32)) as u64) & (sz - 1) as u64) as i32
})
}
}
impl<V: Vox, S: VolSize, M> BaseVol for VolMap<V, S, M> {
type Vox = V;
type Err = VolMapErr;
}
impl<V: Vox, S: VolSize, M> ReadVol for VolMap<V, S, M> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V, VolMapErr> {
let ck = Self::chunk_key(pos);
self.chunks
.get(&ck)
.ok_or(VolMapErr::NoSuchChunk)
.and_then(|chunk| {
let co = Self::chunk_offs(pos);
chunk.get(co).map_err(|err| VolMapErr::ChunkErr(err))
})
}
}
impl<V: Vox + Clone, S: VolSize, M: Clone> SampleVol for VolMap<V, S, M> {
type Sample = VolMap<V, S, M>;
/// Take a sample of the terrain by cloning the voxels within the provided range.
///
/// Note that the resultant volume does not carry forward metadata from the original chunks.
fn sample(&self, range: Aabb<i32>) -> Result<Self::Sample, VolMapErr> {
// Return early if we don't have all the needed chunks that we need!
/*
let min_chunk = Self::chunk_key(range.min);
let max_chunk = Self::chunk_key(range.max - Vec3::one());
for x in min_chunk.x..=max_chunk.x {
for y in min_chunk.y..=max_chunk.y {
for z in min_chunk.z..=max_chunk.z {
if self.chunks.get(&Vec3::new(x, y, z)).is_none() {
return Err(VolMapErr::NoSuchChunk);
}
}
}
}
*/
// let mut sample = Dyna::filled(range.size().map(|e| e as u32).into(), V::empty(), ());
// let mut last_chunk_pos = self.pos_key(range.min);
// let mut last_chunk = self.get_key(last_chunk_pos);
// for pos in sample.iter_positions() {
// let new_chunk_pos = self.pos_key(range.min + pos);
// if last_chunk_pos != new_chunk_pos {
// last_chunk = self.get_key(new_chunk_pos);
// last_chunk_pos = new_chunk_pos;
// }
// sample
// .set(
// pos,
// if let Some(chunk) = last_chunk {
// chunk
// .get(Self::chunk_offs(range.min + pos))
// .map(|v| v.clone())
// .unwrap_or(V::empty())
// // Fallback in case the chunk doesn't exist
// } else {
// self.get(range.min + pos)
// .map(|v| v.clone())
// .unwrap_or(V::empty())
// },
// )
// .map_err(|err| VolMapErr::DynaErr(err))?;
// }
// Ok(sample)
let mut sample = VolMap::new()?;
let chunk_min = Self::chunk_key(range.min);
let chunk_max = Self::chunk_key(range.max);
for x in chunk_min.x..=chunk_max.x {
for y in chunk_min.y..=chunk_max.y {
for z in chunk_min.z..=chunk_max.z {
let chunk_key = Vec3::new(x, y, z);
let chunk = self.get_key_arc(chunk_key).map(|v| v.clone());
if let Some(chunk) = chunk {
sample.insert(chunk_key, chunk);
}
}
}
}
Ok(sample)
}
}
impl<V: Vox + Clone, S: VolSize + Clone, M: Clone> WriteVol for VolMap<V, S, M> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: V) -> Result<(), VolMapErr> {
let ck = Self::chunk_key(pos);
self.chunks
.get_mut(&ck)
.ok_or(VolMapErr::NoSuchChunk)
.and_then(|chunk| {
let co = Self::chunk_offs(pos);
Arc::make_mut(chunk)
.set(co, vox)
.map_err(|err| VolMapErr::ChunkErr(err))
})
}
}
impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
pub fn new() -> Result<Self, VolMapErr> {
if Self::chunk_size()
.map(|e| e.is_power_of_two() && e > 0)
.reduce_and()
{
Ok(Self {
chunks: HashMap::new(),
})
} else {
Err(VolMapErr::InvalidChunkSize)
}
}
pub fn chunk_size() -> Vec3<u32> {
S::SIZE
}
pub fn insert(
&mut self,
key: Vec3<i32>,
chunk: Arc<Chunk<V, S, M>>,
) -> Option<Arc<Chunk<V, S, M>>> {
self.chunks.insert(key, chunk)
}
pub fn get_key(&self, key: Vec3<i32>) -> Option<&Chunk<V, S, M>> {
match self.chunks.get(&key) {
Some(arc_chunk) => Some(arc_chunk.as_ref()),
None => None,
}
}
pub fn get_key_arc(&self, key: Vec3<i32>) -> Option<&Arc<Chunk<V, S, M>>> {
self.chunks.get(&key)
}
pub fn remove(&mut self, key: Vec3<i32>) -> Option<Arc<Chunk<V, S, M>>> {
self.chunks.remove(&key)
}
pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> {
key * S::SIZE.map(|e| e as i32)
}
pub fn pos_key(&self, pos: Vec3<i32>) -> Vec3<i32> {
Self::chunk_key(pos)
}
pub fn iter<'a>(&'a self) -> ChunkIter<'a, V, S, M> {
ChunkIter {
iter: self.chunks.iter(),
}
}
}
pub struct ChunkIter<'a, V: Vox, S: VolSize, M> {
iter: std::collections::hash_map::Iter<'a, Vec3<i32>, Arc<Chunk<V, S, M>>>,
}
impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkIter<'a, V, S, M> {
type Item = (Vec3<i32>, &'a Arc<Chunk<V, S, M>>);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(k, c)| (*k, c))
}
}

View File

@ -0,0 +1,185 @@
use std::{
collections::HashMap,
sync::Arc,
marker::PhantomData,
};
use vek::*;
use crate::{
terrain::TerrainChunkMeta,
vol::{BaseVol, ReadVol, SampleVol, SizedVol, VolSize, Vox, WriteVol},
volumes::{
chunk::{Chunk, ChunkErr},
dyna::{Dyna, DynaErr},
},
};
#[derive(Debug)]
pub enum VolMap2dErr<V: BaseVol> {
NoSuchChunk,
ChunkErr(V::Err),
DynaErr(DynaErr),
InvalidChunkSize,
}
// V = Voxel
// S = Size (replace with a const when const generics is a thing)
// M = Chunk metadata
#[derive(Clone)]
pub struct VolMap2d<V: BaseVol, S: VolSize> {
chunks: HashMap<Vec2<i32>, Arc<V>>,
phantom: PhantomData<S>,
}
impl<V: BaseVol, S: VolSize> VolMap2d<V, S> {
#[inline(always)]
pub fn chunk_key<P: Into<Vec2<i32>>>(pos: P) -> Vec2<i32> {
pos.into().map2(S::SIZE.into(), |e, sz: u32| {
// Horrid, but it's faster than a cheetah with a red bull blood transfusion
let log2 = (sz - 1).count_ones();
((((e as i64 + (1 << 32)) as u64) >> log2) - (1 << (32 - log2))) as i32
})
}
#[inline(always)]
pub fn chunk_offs(pos: Vec3<i32>) -> Vec3<i32> {
let offs = pos.map2(S::SIZE, |e, sz| {
// Horrid, but it's even faster than the aforementioned cheetah
(((e as i64 + (1 << 32)) as u64) & (sz - 1) as u64) as i32
});
Vec3::new(offs.x, offs.y, pos.z)
}
}
impl<V: BaseVol, S: VolSize> BaseVol for VolMap2d<V, S> {
type Vox = V::Vox;
type Err = VolMap2dErr<V>;
}
impl<V: BaseVol + ReadVol, S: VolSize> ReadVol for VolMap2d<V, S> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V::Vox, VolMap2dErr<V>> {
let ck = Self::chunk_key(pos);
self.chunks
.get(&ck)
.ok_or(VolMap2dErr::NoSuchChunk)
.and_then(|chunk| {
let co = Self::chunk_offs(pos);
chunk.get(co).map_err(|err| VolMap2dErr::ChunkErr(err))
})
}
}
// TODO: This actually breaks the API: samples are supposed to have an offset of zero!
// TODO: Should this be changed, perhaps?
impl<I: Into<Aabr<i32>>, V: BaseVol + ReadVol, S: VolSize> SampleVol<I> for VolMap2d<V, S> {
type Sample = VolMap2d<V, S>;
/// Take a sample of the terrain by cloning the voxels within the provided range.
///
/// Note that the resultant volume does not carry forward metadata from the original chunks.
fn sample(&self, range: I) -> Result<Self::Sample, VolMap2dErr<V>> {
let range = range.into();
let mut sample = VolMap2d::new()?;
let chunk_min = Self::chunk_key(range.min);
let chunk_max = Self::chunk_key(range.max);
for x in chunk_min.x..=chunk_max.x {
for y in chunk_min.y..=chunk_max.y {
let chunk_key = Vec2::new(x, y);
let chunk = self.get_key_arc(chunk_key).map(|v| v.clone());
if let Some(chunk) = chunk {
sample.insert(chunk_key, chunk);
}
}
}
Ok(sample)
}
}
impl<V: BaseVol + WriteVol + Clone, S: VolSize + Clone> WriteVol for VolMap2d<V, S> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: V::Vox) -> Result<(), VolMap2dErr<V>> {
let ck = Self::chunk_key(pos);
self.chunks
.get_mut(&ck)
.ok_or(VolMap2dErr::NoSuchChunk)
.and_then(|chunk| {
let co = Self::chunk_offs(pos);
Arc::make_mut(chunk)
.set(co, vox)
.map_err(|err| VolMap2dErr::ChunkErr(err))
})
}
}
impl<V: BaseVol, S: VolSize> VolMap2d<V, S> {
pub fn new() -> Result<Self, VolMap2dErr<V>> {
if Self::chunk_size()
.map(|e| e.is_power_of_two() && e > 0)
.reduce_and()
{
Ok(Self {
chunks: HashMap::new(),
phantom: PhantomData,
})
} else {
Err(VolMap2dErr::InvalidChunkSize)
}
}
pub fn chunk_size() -> Vec2<u32> {
S::SIZE.into()
}
pub fn insert(
&mut self,
key: Vec2<i32>,
chunk: Arc<V>,
) -> Option<Arc<V>> {
self.chunks.insert(key, chunk)
}
pub fn get_key(&self, key: Vec2<i32>) -> Option<&V> {
match self.chunks.get(&key) {
Some(arc_chunk) => Some(arc_chunk.as_ref()),
None => None,
}
}
pub fn get_key_arc(&self, key: Vec2<i32>) -> Option<&Arc<V>> {
self.chunks.get(&key)
}
pub fn remove(&mut self, key: Vec2<i32>) -> Option<Arc<V>> {
self.chunks.remove(&key)
}
pub fn key_pos(&self, key: Vec2<i32>) -> Vec2<i32> {
key * Vec2::<u32>::from(S::SIZE).map(|e| e as i32)
}
pub fn pos_key(&self, pos: Vec3<i32>) -> Vec2<i32> {
Self::chunk_key(pos)
}
pub fn iter<'a>(&'a self) -> ChunkIter<'a, V> {
ChunkIter {
iter: self.chunks.iter(),
}
}
}
pub struct ChunkIter<'a, V: BaseVol> {
iter: std::collections::hash_map::Iter<'a, Vec2<i32>, Arc<V>>,
}
impl<'a, V: BaseVol> Iterator for ChunkIter<'a, V> {
type Item = (Vec2<i32>, &'a Arc<V>);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(k, c)| (*k, c))
}
}

View File

@ -0,0 +1,185 @@
use std::{
collections::HashMap,
sync::Arc,
marker::PhantomData,
};
use vek::*;
use crate::{
terrain::TerrainChunkMeta,
vol::{BaseVol, ReadVol, SampleVol, SizedVol, VolSize, Vox, WriteVol},
volumes::{
chunk::{Chunk, ChunkErr},
dyna::{Dyna, DynaErr},
},
};
#[derive(Debug)]
pub enum VolMap3dErr<V: BaseVol> {
NoSuchChunk,
ChunkErr(V::Err),
DynaErr(DynaErr),
InvalidChunkSize,
}
// V = Voxel
// S = Size (replace with a const when const generics is a thing)
// M = Chunk metadata
#[derive(Clone)]
pub struct VolMap3d<V: BaseVol, S: VolSize> {
chunks: HashMap<Vec3<i32>, Arc<V>>,
phantom: PhantomData<S>,
}
impl<V: BaseVol, S: VolSize> VolMap3d<V, S> {
#[inline(always)]
pub fn chunk_key(pos: Vec3<i32>) -> Vec3<i32> {
pos.map2(S::SIZE, |e, sz| {
// Horrid, but it's faster than a cheetah with a red bull blood transfusion
let log2 = (sz - 1).count_ones();
((((e as i64 + (1 << 32)) as u64) >> log2) - (1 << (32 - log2))) as i32
})
}
#[inline(always)]
pub fn chunk_offs(pos: Vec3<i32>) -> Vec3<i32> {
pos.map2(S::SIZE, |e, sz| {
// Horrid, but it's even faster than the aforementioned cheetah
(((e as i64 + (1 << 32)) as u64) & (sz - 1) as u64) as i32
})
}
}
impl<V: BaseVol, S: VolSize> BaseVol for VolMap3d<V, S> {
type Vox = V::Vox;
type Err = VolMap3dErr<V>;
}
impl<V: BaseVol + ReadVol, S: VolSize> ReadVol for VolMap3d<V, S> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V::Vox, VolMap3dErr<V>> {
let ck = Self::chunk_key(pos);
self.chunks
.get(&ck)
.ok_or(VolMap3dErr::NoSuchChunk)
.and_then(|chunk| {
let co = Self::chunk_offs(pos);
chunk.get(co).map_err(|err| VolMap3dErr::ChunkErr(err))
})
}
}
// TODO: This actually breaks the API: samples are supposed to have an offset of zero!
// TODO: Should this be changed, perhaps?
impl<I: Into<Aabb<i32>>, V: BaseVol + ReadVol, S: VolSize> SampleVol<I> for VolMap3d<V, S> {
type Sample = VolMap3d<V, S>;
/// Take a sample of the terrain by cloning the voxels within the provided range.
///
/// Note that the resultant volume does not carry forward metadata from the original chunks.
fn sample(&self, range: I) -> Result<Self::Sample, VolMap3dErr<V>> {
let range = range.into();
let mut sample = VolMap3d::new()?;
let chunk_min = Self::chunk_key(range.min);
let chunk_max = Self::chunk_key(range.max);
for x in chunk_min.x..=chunk_max.x {
for y in chunk_min.y..=chunk_max.y {
for z in chunk_min.z..=chunk_max.z {
let chunk_key = Vec3::new(x, y, z);
let chunk = self.get_key_arc(chunk_key).map(|v| v.clone());
if let Some(chunk) = chunk {
sample.insert(chunk_key, chunk);
}
}
}
}
Ok(sample)
}
}
impl<V: BaseVol + WriteVol + Clone, S: VolSize + Clone> WriteVol for VolMap3d<V, S> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: V::Vox) -> Result<(), VolMap3dErr<V>> {
let ck = Self::chunk_key(pos);
self.chunks
.get_mut(&ck)
.ok_or(VolMap3dErr::NoSuchChunk)
.and_then(|chunk| {
let co = Self::chunk_offs(pos);
Arc::make_mut(chunk)
.set(co, vox)
.map_err(|err| VolMap3dErr::ChunkErr(err))
})
}
}
impl<V: BaseVol, S: VolSize> VolMap3d<V, S> {
pub fn new() -> Result<Self, VolMap3dErr<V>> {
if Self::chunk_size()
.map(|e| e.is_power_of_two() && e > 0)
.reduce_and()
{
Ok(Self {
chunks: HashMap::new(),
phantom: PhantomData,
})
} else {
Err(VolMap3dErr::InvalidChunkSize)
}
}
pub fn chunk_size() -> Vec3<u32> {
S::SIZE
}
pub fn insert(
&mut self,
key: Vec3<i32>,
chunk: Arc<V>,
) -> Option<Arc<V>> {
self.chunks.insert(key, chunk)
}
pub fn get_key(&self, key: Vec3<i32>) -> Option<&V> {
match self.chunks.get(&key) {
Some(arc_chunk) => Some(arc_chunk.as_ref()),
None => None,
}
}
pub fn get_key_arc(&self, key: Vec3<i32>) -> Option<&Arc<V>> {
self.chunks.get(&key)
}
pub fn remove(&mut self, key: Vec3<i32>) -> Option<Arc<V>> {
self.chunks.remove(&key)
}
pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> {
key * S::SIZE.map(|e| e as i32)
}
pub fn pos_key(&self, pos: Vec3<i32>) -> Vec3<i32> {
Self::chunk_key(pos)
}
pub fn iter<'a>(&'a self) -> ChunkIter<'a, V> {
ChunkIter {
iter: self.chunks.iter(),
}
}
}
pub struct ChunkIter<'a, V: BaseVol> {
iter: std::collections::hash_map::Iter<'a, Vec3<i32>, Arc<V>>,
}
impl<'a, V: BaseVol> Iterator for ChunkIter<'a, V> {
type Item = (Vec3<i32>, &'a Arc<V>);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(k, c)| (*k, c))
}
}

View File

@ -44,9 +44,9 @@ pub struct Server {
clients: Clients,
thread_pool: ThreadPool,
chunk_tx: mpsc::Sender<(Vec3<i32>, TerrainChunk)>,
chunk_rx: mpsc::Receiver<(Vec3<i32>, TerrainChunk)>,
pending_chunks: HashSet<Vec3<i32>>,
chunk_tx: mpsc::Sender<(Vec2<i32>, TerrainChunk)>,
chunk_rx: mpsc::Receiver<(Vec2<i32>, TerrainChunk)>,
pending_chunks: HashSet<Vec2<i32>>,
}
impl Server {
@ -555,7 +555,7 @@ impl Server {
.clear();
}
pub fn generate_chunk(&mut self, key: Vec3<i32>) {
pub fn generate_chunk(&mut self, key: Vec2<i32>) {
if self.pending_chunks.insert(key) {
let chunk_tx = self.chunk_tx.clone();
self.thread_pool

View File

@ -4,8 +4,8 @@ use vek::*;
// Project
use common::{
terrain::Block,
vol::{ReadVol, SizedVol, VolSize, Vox},
volumes::{dyna::Dyna, vol_map::VolMap},
vol::{BaseVol, ReadVol, SizedVol, VolSize, Vox},
volumes::{dyna::Dyna, vol_map_2d::VolMap2d, vol_map_3d::VolMap3d},
};
// Crate
@ -44,7 +44,71 @@ impl<M> Meshable for Dyna<Block, M> {
}
}
impl<S: VolSize + Clone, M: Clone> Meshable for VolMap<Block, S, M> {
impl<V: BaseVol<Vox=Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap2d<V, S> {
type Pipeline = TerrainPipeline;
type Supplement = Aabb<i32>;
fn generate_mesh(&self, range: Self::Supplement) -> Mesh<Self::Pipeline> {
let mut mesh = Mesh::new();
let mut last_chunk_pos = self.pos_key(range.min);
let mut last_chunk = self.get_key(last_chunk_pos);
let size = range.max - range.min;
for x in 1..size.x - 1 {
for y in 1..size.y - 1 {
for z in 0..size.z {
let pos = Vec3::new(x, y, z);
let new_chunk_pos = self.pos_key(range.min + pos);
if last_chunk_pos != new_chunk_pos {
last_chunk = self.get_key(new_chunk_pos);
last_chunk_pos = new_chunk_pos;
}
let offs = pos.map(|e| e as f32) - Vec3::new(1.0, 1.0, 0.0);
if let Some(chunk) = last_chunk {
let chunk_pos = Self::chunk_offs(range.min + pos);
if let Some(col) = chunk.get(chunk_pos).ok().and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
vol::push_vox_verts(
&mut mesh,
self,
range.min + pos,
offs,
col,
TerrainVertex::new,
false,
);
}
} else {
if let Some(col) = self
.get(range.min + pos)
.ok()
.and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
vol::push_vox_verts(
&mut mesh,
self,
range.min + pos,
offs,
col,
TerrainVertex::new,
false,
);
}
}
}
}
}
mesh
}
}
impl<V: BaseVol<Vox=Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap3d<V, S> {
type Pipeline = TerrainPipeline;
type Supplement = Aabb<i32>;

View File

@ -1,14 +1,7 @@
// Standard
use std::{collections::HashMap, sync::mpsc, time::Duration};
// Library
use vek::*;
// Project
use client::Client;
use common::{terrain::TerrainMap, vol::SampleVol, volumes::vol_map::VolMapErr};
// Crate
use common::{terrain::TerrainMap, vol::SampleVol, volumes::vol_map_2d::VolMap2dErr};
use crate::{
mesh::Meshable,
render::{Consts, Globals, Mesh, Model, Renderer, TerrainLocals, TerrainPipeline},
@ -21,40 +14,40 @@ struct TerrainChunk {
}
struct ChunkMeshState {
pos: Vec3<i32>,
pos: Vec2<i32>,
started_tick: u64,
active_worker: bool,
}
/// A type produced by mesh worker threads corresponding to the position and mesh of a chunk
struct MeshWorkerResponse {
pos: Vec3<i32>,
pos: Vec2<i32>,
mesh: Mesh<TerrainPipeline>,
started_tick: u64,
}
/// Function executed by worker threads dedicated to chunk meshing
fn mesh_worker(
pos: Vec3<i32>,
pos: Vec2<i32>,
started_tick: u64,
volume: <TerrainMap as SampleVol>::Sample,
supplement: Aabb<i32>,
volume: <TerrainMap as SampleVol<Aabr<i32>>>::Sample,
range: Aabb<i32>,
) -> MeshWorkerResponse {
MeshWorkerResponse {
pos,
mesh: volume.generate_mesh(supplement),
mesh: volume.generate_mesh(range),
started_tick,
}
}
pub struct Terrain {
chunks: HashMap<Vec3<i32>, TerrainChunk>,
chunks: HashMap<Vec2<i32>, TerrainChunk>,
// The mpsc sender and receiver used for talking to meshing worker threads.
// We keep the sender component for no reason othe than to clone it and send it to new workers.
mesh_send_tmp: mpsc::Sender<MeshWorkerResponse>,
mesh_recv: mpsc::Receiver<MeshWorkerResponse>,
mesh_todo: HashMap<Vec3<i32>, ChunkMeshState>,
mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
}
impl Terrain {
@ -90,28 +83,26 @@ impl Terrain {
// elision information changes too!
for i in -1..2 {
for j in -1..2 {
for k in -1..2 {
let pos = pos + Vec3::new(i, j, k);
let pos = pos + Vec2::new(i, j);
if !self.chunks.contains_key(&pos) {
let mut neighbours = true;
for i in -1..2 {
for j in -1..2 {
neighbours &= client
.state()
.terrain()
.get_key(pos + Vec2::new(i, j))
.is_some();
}
if !self.chunks.contains_key(&pos) {
let mut neighbours = true;
for i in -1..2 {
for j in -1..2 {
neighbours &= client
.state()
.terrain()
.get_key(pos + Vec2::new(i, j))
.is_some();
}
}
if neighbours {
self.mesh_todo.entry(pos).or_insert(ChunkMeshState {
pos,
started_tick: current_tick,
active_worker: false,
});
}
if neighbours {
self.mesh_todo.entry(pos).or_insert(ChunkMeshState {
pos,
started_tick: current_tick,
active_worker: false,
});
}
}
}
@ -132,7 +123,7 @@ impl Terrain {
// Find the area of the terrain we want. Because meshing needs to compute things like
// ambient occlusion and edge elision, we also need to borders of the chunk's
// neighbours too (hence the `- 1` and `+ 1`).
let aabb = Aabb {
let aabr = Aabr {
min: todo
.pos
.map2(TerrainMap::chunk_size(), |e, sz| e * sz as i32 - 1),
@ -141,13 +132,18 @@ impl Terrain {
.map2(TerrainMap::chunk_size(), |e, sz| (e + 1) * sz as i32 + 1),
};
let aabb = Aabb {
min: Vec3::from(aabr.min),
max: Vec3::from(aabr.max) + Vec3::unit_z() * 256,
};
// Copy out the chunk data we need to perform the meshing. We do this by taking a
// sample of the terrain that includes both the chunk we want and
let volume = match client.state().terrain().sample(aabb) {
let volume = match client.state().terrain().sample(aabr) {
Ok(sample) => sample,
// If either this chunk or its neighbours doesn't yet exist, so we keep it in the
// todo queue to be processed at a later date when we have its neighbours.
Err(VolMapErr::NoSuchChunk) => return,
Err(VolMap2dErr::NoSuchChunk) => return,
_ => panic!("Unhandled edge case"),
};
@ -178,11 +174,11 @@ impl Terrain {
.expect("Failed to upload chunk mesh to the GPU"),
locals: renderer
.create_consts(&[TerrainLocals {
model_offs: response
model_offs: Vec3::from(response
.pos
.map2(TerrainMap::chunk_size(), |e, sz| {
e as f32 * sz as f32
})
}))
.into_array(),
}])
.expect("Failed to upload chunk locals to the GPU"),

View File

@ -4,8 +4,8 @@ use vek::*;
// Project
use common::{
terrain::{Block, TerrainChunk, TerrainChunkMeta},
vol::{SizedVol, Vox, WriteVol},
terrain::{Block, TerrainChunk, TerrainChunkSize, TerrainChunkMeta},
vol::{SizedVol, Vox, WriteVol, VolSize},
};
#[derive(Debug)]
@ -20,60 +20,63 @@ impl World {
Self
}
pub fn generate_chunk(chunk_pos: Vec3<i32>) -> TerrainChunk {
pub fn generate_chunk(chunk_pos: Vec2<i32>) -> TerrainChunk {
// TODO: This is all test code, remove/improve this later
let mut chunk = TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void());
let air = Block::empty();
let stone = Block::new(1, Rgb::new(200, 220, 255));
let grass = Block::new(2, Rgb::new(75, 150, 0));
//let grass = Block::new(2, Rgb::new(50, 255, 0));
let dirt = Block::new(3, Rgb::new(128, 90, 0));
let sand = Block::new(4, Rgb::new(180, 150, 50));
let mut chunk = TerrainChunk::new(0, stone, air, TerrainChunkMeta::void());
let perlin_nz = Perlin::new().set_seed(1);
let temp_nz = Perlin::new().set_seed(2);
let chaos_nz = Perlin::new().set_seed(3);
for lpos in chunk.iter_positions() {
let wpos = lpos + chunk_pos * chunk.get_size().map(|e| e as i32);
let wposf = wpos.map(|e| e as f64);
for x in 0..TerrainChunkSize::SIZE.x as i32 {
for y in 0..TerrainChunkSize::SIZE.y as i32 {
for z in 0..256 {
let lpos = Vec3::new(x, y, z);
let wpos = lpos + Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32);
let wposf = wpos.map(|e| e as f64);
let chaos_freq = 1.0 / 100.0;
let freq = 1.0 / 128.0;
let ampl = 75.0;
let small_freq = 1.0 / 32.0;
let small_ampl = 6.0;
let offs = 32.0;
let chaos_freq = 1.0 / 100.0;
let freq = 1.0 / 128.0;
let ampl = 75.0;
let small_freq = 1.0 / 32.0;
let small_ampl = 6.0;
let offs = 32.0;
let chaos = chaos_nz
.get(Vec2::from(wposf * chaos_freq).into_array())
.max(0.0)
+ 0.5;
let chaos = chaos_nz
.get(Vec2::from(wposf * chaos_freq).into_array())
.max(0.0)
+ 0.5;
let height = perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl * chaos
+ perlin_nz.get((wposf * small_freq).into_array())
* small_ampl
* 3.0
* chaos.powf(2.0)
+ offs;
let temp = (temp_nz.get(Vec2::from(wposf * (1.0 / 64.0)).into_array()) + 1.0) * 0.5;
let height = perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl * chaos
+ perlin_nz.get((wposf * small_freq).into_array())
* small_ampl
* 3.0
* chaos.powf(2.0)
+ offs;
let temp = (temp_nz.get(Vec2::from(wposf * (1.0 / 64.0)).into_array()) + 1.0) * 0.5;
chunk
.set(
lpos,
if wposf.z < height - 4.0 {
stone
} else if wposf.z < height - 2.0 {
dirt
} else if wposf.z < height {
Block::new(2, Rgb::new(10 + (150.0 * temp) as u8, 150, 0))
} else {
air
},
)
.unwrap();
let _ = chunk
.set(
lpos,
if wposf.z < height - 4.0 {
stone
} else if wposf.z < height - 2.0 {
dirt
} else if wposf.z < height {
Block::new(2, Rgb::new(10 + (150.0 * temp) as u8, 150, 0))
} else {
air
},
);
}
}
}
chunk